BigUint Operations Demystified
In the realm of BigUint
operations, it's crucial to understand that a BigUint
is essentially a handle, akin to how system file handles operate. It's essentially a struct with an i32
member representing the handle. All operations must be carried out through API functions by passing handles for the result, the first operand, and the second operand. Leveraging Rust's operator overloading feature, we can redefine arithmetic operators, making it convenient to add BigUint
values, much like primitive number types.
However, you may encounter scenarios where you need to use the same number multiple times. Consider having four BigUint
values: a
, b
, c
, and d
, and you want to perform the following operations:
c = a + b;
d = c + a;
You'll quickly realize that this doesn't work due to Rust's ownership system. After the first operation, a
is consumed, and you're likely to encounter an error like this:
move occurs because `a` has type `<Self as ContractBase>::BigUint`, which does not implement the `Copy` trait
The simplest solution is to clone a
:
c = a.clone() + b;
d = c + a;
Although the errors are now resolved, behind the scenes, this involves more than just copying the handle. a.clone()
creates an entirely new BigUint
, copying the bytes from the original a
.
A more efficient approach is to borrow a
. +
and other operations are defined for references of BigUint
, so you can rewrite it as follows:
c = &a + &b;
d = c + a;
Another situation where you can avoid creating additional BigUint
instances is when performing operations with multiple arguments:
e = a + b + c + d;
Or, if you prefer to keep the instances (you can't add an owned BigUint
to &BigUint
, so you must also borrow the results):
e = &((&(&a + &b) + &c) + &d);
In both cases, the underlying API calls are as follows:
temp1 = bigIntNew();
bigIntAdd(temp1, a, b);
temp2 = bigIntNew();
bigIntAdd(temp2, temp1, c);
temp3 = bigIntNew();
bigIntAdd(temp3, temp2, d);
This results in creating three new BigUint
instances—one for the result of a + b
, one for (a + b) + c
, and one for the final result that ends up in e
. You can optimize this by rewriting the code as follows:
e = BigUint::zero();
e += &a;
e += &b;
e += &c;
e += &d;
This approach creates a single BigUint
instead of three.
While these examples may seem straightforward, they provide clarity on how BigUint
operations work and how to maximize their efficiency.