using for
using for 用法
Solidity
提供了 using for
,用于为任何类型添加成员函数。
using for
的使用方法如下:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
library L {
// 下面需要将 isEven 附加到 int 类型
// 将调用 int a = 3; a.isEven() 时,a 会做为第一个参数 self 传入
function isEven(int self) internal pure returns (bool) {
return self % 2 == 0;
}
// 下面需要将 isEven 附加到 int 类型
// 将调用 int a = 3; a.isEven() 时,self 值为 a 自身
function isOdd(int self) internal pure returns (bool) {
return self % 2 != 0;
}
// 将库 L 中的所有非 private 函数附加到 int 类型
using L for int;
// 或者,只指定 L.isEven 附加到 int 类型
using {L.isEven, L.isOdd} for int;
contract C {
function test() public pure returns (bool, bool) {
int a = 3;
return (a.isEven(), a.isOdd());
}
}
使用 using L for Type
来将库 L
中的所有 private
函数附加到 Type
类型。
使用 using {fn1, fn2, ...} for Type
来显式将多个函数附加到 Type
类型。
由于我们要将 isEven
附加到 int
类型,因此我们把它的第一个参数声明为对应的类型:int self
。当我们调用 a.isEven()
时,a
会作为第一个参数传给 isEven
。
上面的 using for
是在文件层级声明的,将在当前文件中生效。
如果是自定义类型,可以使用 using for UserType global
为自定义类型附加成员函数,全局生效:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
// 自定义类型 Int
type Int is int256;
function isEvenForInt(Int a) pure returns (bool) {
return Int.unwrap(a) % 2 == 0;
}
// 将 isEvenForInt 附加到 Int 类型,全局生效
// 指定全局生效时,只能是自定义的类型
using {isEvenForInt} for Int global;
contract C {
function test() public pure returns (bool) {
Int a = Int.wrap(3);
return a.isEvenForInt();
}
}
除了在文件层级使用 using for
,也可以合约层级使用,此时仅当前合约生效:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
function sayHi(string memory self) pure returns (string memory) {
return string.concat("Hi, I am a string, my value is: ", self);
}
contract C {
using {sayHi} for string;
function test() public pure returns (string memory) {
string memory str = "USING FOR STRING";
return str.sayHi();
}
}
改进 SmallSet
与上一节中的集合实现不同。在这一节,我们实现一个 SmallSet
。该 SmallSet
使用 type SmallSet is bytes32
声明。
bytes32
是一个32位长的字节数组,每一个元素长8位,用于保存 uint8
元素,最多存 32 个元素。每个元素取值为 1-255
,0
表示该位置无元素:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
/// @dev bytes32 实现的小集合,每个元素占1个字节,最多32个元素,元素值 1-255,0 视为空位
type SmallSet is bytes32;
/// @dev 数组实现的集合操作
library SmallSetOp {
/// @notice 判断集合内是否存在存在某元素
/// @param self 集合结构体
/// @param v 要查找的元素
/// @return bool true 表示存在,false 表示不存在
function has(SmallSet self, uint8 v) internal pure returns (bool) {
if (v == 0) return false;
for (uint256 i = 0; i < 32; i++)
if (uint8(SmallSet.unwrap(self)[i]) == v) return true;
return false;
}
/// @notice 在原集合的基础上,插入新元素,并返回插入后的新集合
/// @param self 原集合
/// @param v 要插入的元素
/// @return 返回原集合插入元素后的集合
function insert(SmallSet self, uint8 v) internal pure returns (SmallSet) {
if (has(self, v)) return self;
bytes32 b = SmallSet.unwrap(self);
for (uint256 i = 0; i < 32; i++)
if (b[i] == 0x00)
return SmallSet.wrap(b | (bytes32(bytes1(v)) >> (8 * i)));
return self;
}
/// @notice 从集合中移除元素,并返回移除目标元素后的新集合
/// @param self 集合
/// @param v 要移除的元素
/// @return SmallSet 返回移除目标元素后的新集合
function remove(SmallSet self, uint8 v) internal pure returns (SmallSet) {
bytes32 b = SmallSet.unwrap(self);
bytes1 target = bytes1(v);
for (uint256 i = 0; i < 32; i++)
if (b[i] == target)
return SmallSet.wrap(b & (~(bytes32(hex"ff") >> (8 * i))));
return self;
}
/// @notice 并集操作
/// @param self 并集集合1
/// @param unionSet 并集集合2
/// @return 返回集合1与集合2并集后的结果集合
function union(SmallSet self, SmallSet unionSet) internal pure returns (SmallSet) {
bytes32 b = SmallSet.unwrap(unionSet);
for (uint256 i = 0; i < 32; i++) self = insert(self, uint8(b[i]));
return self;
}
/// @notice 交集操作
/// @param self 集合1
/// @param diffSet 集合2:集合1中要移除的元素的集合
/// @return 返回集合1中移除集合2中的元素之后的新集合
function diff(SmallSet self,SmallSet diffSet) internal pure returns (SmallSet) {
bytes32 b = SmallSet.unwrap(diffSet);
for (uint256 i = 0; i < 32; i++) self = remove(self, uint8(b[i]));
return self;
}
}
未使用 using for
时,SmallSet
的使用如下:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
// 导入 SetOp 库
import {SmallSetOp, SmallSet} from "./SmallSet.sol";
contract TestSet {
SmallSet public set1;
SmallSet public set2;
function test() public returns (SmallSet, SmallSet) {
set1 = SmallSetOp.insert(set1, 2);
set1 = SmallSetOp.insert(set1, 4);
set1 = SmallSetOp.insert(set1, 6);
set1 = SmallSetOp.insert(set1, 8);
// set1: 2, 4, 6, 8
set2 = SmallSetOp.insert(set2, 1);
set2 = SmallSetOp.insert(set2, 3);
set2 = SmallSetOp.insert(set2, 5);
set2 = SmallSetOp.insert(set2, 7);
// set2: 1, 3, 5, 7
set1 = SmallSetOp.remove(set1, 2);
set2 = SmallSetOp.remove(set2, 5);
// set1: 4, 6, 8
// set2: 1, 3, 7
set1 = SmallSetOp.union(set1, set2);
// set1: 4, 6, 8, 1, 3, 7
// set2: 1, 3, 7
set2 = SmallSetOp.diff(set2, set1);
// set1: 4, 6, 8, 1, 3, 7
// set2:
return (set1, set2);
}
}
现在我们使用 using for
将 SmallSetOp
中的函数作为成员函数添加到类型 SmallSet
中去:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
/// @dev bytes32 实现的小集合,每个元素占1个字节,最多32个元素,元素值 1-255,0 视为空位
type SmallSet is bytes32;
// 将 SmallSetOp 库中的函数作为 SmallSet 的成员函数
using SmallSetOp for SmallSet global;
/// @dev 数组实现的集合操作
library SmallSetOp {
...
}
现在,不需要再导入 SmallSetOp
了,只需要导入使用 SmallSet
即可。并且直接通过调用 SmallSet
的成员函数来操纵集合:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
// 导入 SetOp 库
import {SmallSet} from "./SmallSetUsingFor.sol";
contract TestSet {
SmallSet public set1;
SmallSet public set2;
function test() public returns (SmallSet, SmallSet) {
set1 = set1.insert(2);
set1 = set1.insert(4);
set1 = set1.insert(6);
set1 = set1.insert(8);
// set1: 2, 4, 6, 8
set2 = set2.insert(1);
set2 = set2.insert(3);
set2 = set2.insert(5);
set2 = set2.insert(7);
// set2: 1, 3, 5, 7
set1 = set1.remove(2);
set2 = set2.remove(5);
// set1: 4, 6, 8
// set2: 1, 3, 7
set1 = set1.union(set2);
// set1: 4, 6, 8, 1, 3, 7
// set2: 1, 3, 7
set2 = set2.diff(set1);
// set1: 4, 6, 8, 1, 3, 7
// set2:
return (set1, set2);
}
}
操作符重载
using for
还支持操作符重载,使用语法为:
using { 加法函数 as +, 减法函数 as -, ... } for 自定义值类型 global;
操作符重载只支持自定义的值类型。这就是为什么我们使用底层类型为 bytes32
(值类型)的自定义类型 SmallSet
。
我们的想法是,将加法 set1 + set2
重载为求并集, 将减法 set1 - set2
重载为求差集:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
/// @dev bytes32 实现的小集合,每个元素占1个字节,最多32个元素,元素值 1-255,0 视为空位
type SmallSet is bytes32;
// 将 SetOp 库中的函数附加到 Set 结构体
using SmallSetOp for SmallSet global;
/// @dev 数组实现的集合操作
library SmallSetOp {
...
}
// 用于重载为 SmallSet 加法操作(操作符+)
// 重载操作符的函数必须是文件层级声明的 pure 自由函数
function unionAdd(SmallSet self, SmallSet unionSet) pure returns (SmallSet) {
return SmallSetOp.union(self, unionSet);
}
// 用于重载为 SmallSet 减法操作(操作符-)
// 重载操作符的函数必须是文件层级声明的 pure 自由函数
function diffSub(SmallSet self, SmallSet diffSet) pure returns (SmallSet) {
return SmallSetOp.diff(self, diffSet);
}
// 操作符重载
using {unionAdd as +, diffSub as -} for SmallSet global;
由于重载操作符函数必须是自由函数,因此我们额外定义了 unionAdd
diffSub
用于重载加、减法操作符。
现在 union
diff
操作可以这样写:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
// 导入 SetOp 库
import {SmallSet} from "./SmallSetOperatorOverload.sol";
contract TestSet {
SmallSet public set1;
SmallSet public set2;
function test() public returns (SmallSet, SmallSet) {
...
set1 = set1 + set2; // => set1 = set1.union(set2);
// set1: 4, 6, 8, 1, 3, 7
// set2: 1, 3, 7
set2 = set2 - set1; // => set2 = set2.diff(set1);
// set1: 4, 6, 8, 1, 3, 7
// set2:
return (set1, set2);
}
}