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.

Was this page helpful?