In Solidity, there is a way to extend builtin types with using A for B
directive.
Please note that the extention is active only within the current scope, meaning there is not risk to pollute other scopes by defining native builtin types. This design allows developers to avoid writing bugs that conflicts with other behaviours among libraries.
In this example, let's extend uint[]
types using Libraries. Here is a user of that library, that includes uint[] public data
that needs to be extended.
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.7.0 <0.9.0;
contract SearchArray {
uint[] public data;
constructor() {
data.push(0);
data.push(1);
data.push(2);
data.push(4);
data.push(32);
data.push(16);
data.push(8);
}
function getData() public view returns (uint[] memory) {
return data;
}
function append(uint value) public {
data.push(value);
}
function has(uint x) public view returns (bool) {
return data.includes(x);
}
}
In this SearchArray
contract, there is a public data
uint array. It is initialized with the following values:
[0, 1, 2, 4, 32, 16, 8]
Let's extend uint[]
to have max()
function that retuns the maximum number in the array.
assert(32 == data.max());
A new library, Search
, is created as follows. Please note that the function accepts uint[] storage self
as a first argument. It iterates through its own data, and return the max number in the array.
library Search {
function max(uint[] storage self)
public
view
returns (uint)
{
uint _max = type(uint).min;
for (uint i = 0; i < self.length; i++) {
if (self[i] > _max) {
_max = self[i];
}
}
return _max;
}
}
Now, how can we extend the native builin types? After importing the library, add using A for B
directive as follows.
using Search for uint[];
contract SearchArray {
//...
function getMax() public view returns (uint) {
return data.max();
}
//...
}
Now SearchArray
can get the maximum value from the data
array by calling data.max()
.
$ truffle console
truffle(development)> let i = await SearchArray.deployed()
undefined
truffle(development)> i.getMax()
BN { negative: 0, words: [ 32, <1 empty item> ], length: 1, red: null }
In a similar fasion, you can define Search.min()
function as well.
assert(0 == data.min());
Here is the implementation:
library Search {
//...
function min(uint[] storage self)
public
view
returns (uint)
{
uint _min = type(uint).max;
for (uint i = 0; i < self.length; i++) {
if (self[i] < _min) {
_min = self[i];
}
}
return _min;
}
//...
}
contract SearchArray {
//...
function getMin() public view returns (uint) {
return data.min();
}
//...
}
Now data.min()
returns 0
as an outcome:
$ truffle console
truffle(development)> let i = await SearchArray.deployed()
undefined
truffle(development)> i.getMin()
BN { negative: 0, words: [ 0, <1 empty item> ], length: 1, red: null }
Now let's add an another example. Add Search.includes()
that returns true
when a passed uint value
is in the array, otherwise false
.
assert(true == data.includes(32));
assert(false == data.includes(64));
Here is the implementation:
library Search {
//...
function includes(uint[] storage self, uint value)
public
view
returns (bool)
{
for (uint i = 0; i < self.length; i++) {
if (self[i] == value) {
return true;
}
}
return false;
}
//...
}
contract SearchArray {
//...
function has(uint x) public view returns (bool) {
return data.includes(x);
}
//...
}
Now you can call SearchArray.has()
to check if a specific value is included in the array or not.
$ truffle console
truffle(development)> let i = await SearchArray.deployed()
undefined
truffle(development)> i.has(32)
true
truffle(development)> i.has(64)
false
using A for B
is a powerful tool to extend native types only in the current scope. There is a potential to make your source code clearner by using this pattern.