什么是私有泛型函数
私有泛型函数是专门为 Rooch 设计的一种泛型函数,它相比于一般的泛型函数,拥有更严格的约束。
简单来说,私有泛型函数就是拥有 #[private_generics(T1 [, T2, ... Tn])]
属性标注的泛型函数。
作用
私有泛型函数可以判断泛型类型是否是调用者所在的模块内定义的,如果不是则无法使用。对于一般的泛型函数,其他模块定义的类型,通过 use
语句导入到当前模块后,仍然能够正常使用。
这意味着,私有泛型函数只适用于自定义类型,对于其他模块定义的类型和内置类型均无法使用。
例子
私有泛型函数不接受内置类型
我们先来看一个不使用私有泛型函数的例子。
module rooch_examples::module1 {
struct Box<T> has drop {
v: T
}
public fun new_box<T, U>(value: T): Box<T> {
Box { v: value }
}
public fun get_box_value<T>(box: &Box<T>): &T {
&box.v
}
#[test]
fun test1() {
let box = new_box<u32, u64>(123);
assert!(get_box_value(&box) == &123, 1000);
}
}
首先定义一个 Box<T>
类型,它是一个泛型结构体,包含了一个类型为 T
的字段 v
。
接着定义两个泛型函数 new_box
和 get_box_value
。函数 new_box
用来创建 Box<T>
类型的值,函数 get_box_value
用来获取 Box<T>
类型中的字段值 v: T
。
注意:函数
new_box
添加了额外的类型U
,实际上函数并没有使用,但为了后续演示私有泛型约束特性,特意添加了U
类型约束。
我们简单地写一个单元测试来验证我们的代码逻辑。我们给 new_box
传递一个整数字面量 123
,并创建一个包装了 u32
类型的 Box<u32>
类型的值 box
。在断言表达式中,整数字面量引用 &123
会隐式推断为 &123u32
,get_box_value
从 box
中获取到 &123u32
,两者相等,能够顺利通过测试。
运行单元测试:
$ rooch move test
INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY MoveosStdlib
INCLUDING DEPENDENCY RoochFramework
BUILDING private_generics
Running Move unit tests
2024-06-26T15:35:02.835842Z INFO moveos_common::utils: set max open fds 8192
[ PASS ] 0xfc3c1fa4f1538deee1048fa066a1b0029f2cf428e21667e5a7d4d570626c112e::module1::test1
Test result: OK. Total tests: 1; passed: 1; failed: 0
接下来我们对泛型函数 new_box
进行私有泛型约束,在函数的上一行添加 #[private_generics(T)]
属性标注,其他地方保持不变:
#[private_generics(T)]
public fun new_box<T, U>(value: T): Box<T> {
Box { v: value }
}
添加了私有泛型约束后,在调用函数时,类型 T
必须在当前模块内定义。显然内置类型 u32
并不是在当前模块定义的,所以此时再运行代码,就会报错,如下:
$ rooch move test
error: resource type "U32" in function "0xfc3c1fa4f1538deee1048fa066a1b0029f2cf428e21667e5a7d4d570626c112e::module1::new_box" not defined in current module or not allowed
┌─ ./sources/module1.move:17:19
│
17 │ let box = new_box<u32, u64>(123);
│ ^^^^^^^^^^^^^^^^^^^^^^
Error: extended checks failed
私有泛型并没有约束类型 U
,所以不会检查类型 U
声明的位置是否在当前模块。
私有泛型函数使用当前模块定义的自定义类型
基于上面的代码,我们稍作修改:
module rooch_examples::module1 {
struct Data has drop {
v: u64
}
struct Box<T> has drop {
v: T
}
#[private_generics(T)]
public fun new_box<T, U>(value: T): Box<T> {
Box { v: value }
}
public fun get_box_value<T>(box: &Box<T>): &T {
&box.v
}
#[test]
fun test1() {
let data = Data { v: 123 };
let box = new_box<Data, u64>(data);
assert!(get_box_value(&box).v == 123, 1000);
}
}
首先,新增一个自定义类型 Data
,它是一个常见的结构体,包含一个 u64
类型的字段 v
。
接着修改一下单元测试的函数 test1
,构造一个 Data
类型的数据 data
,并将私有泛型函数 new_box
的泛型类型参数 u32
修改为 Data
,断言中,使用成员运算符 .
获取 data
内的 u64
整数值进行比较。
此时再次运行测试,测试例子又能顺利通过测试了:
$ rooch move test
INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY MoveosStdlib
INCLUDING DEPENDENCY RoochFramework
BUILDING private_generics
Running Move unit tests
2024-06-26T15:38:13.522724Z INFO moveos_common::utils: set max open fds 8192
[ PASS ] 0xfc3c1fa4f1538deee1048fa066a1b0029f2cf428e21667e5a7d4d570626c112e::module1::test1
Test result: OK. Total tests: 1; passed: 1; failed: 0
私有泛型函数是在 module1
调用的,自定义类型 Data
也是在当前模块内定义的,因此 Data
能通过函数的私有泛型约束。
接下来我们将演示在另一个模块定义一个自定义类型,并通过 use
语句将这个自定义类型导入到当前模块,并将其传递给私有泛型函数。猜猜看它是否能同正常使用?
私有泛型函数不接受其他模块定义的自定义类型
我们定义一个名为 module2
的新模块:
module rooch_examples::module2 {
struct Data2 has copy, drop {
v: u64
}
public fun new_data(value: u64): Data2 {
Data2 {
v: value
}
}
}
在这个模块里,我们定义一个新的类型 Data2
,并定义一个创建这个类型的函数 new_data
。因为在 Move 中,所有的结构体只能在声明它们的模块中构造,字段只能在结构体的模块内部访问,所以要想被外部模块构造,必须要有相应的函数。
接下来,我们继续修改 module1
:
module rooch_examples::module1 {
#[test_only]
use rooch_examples::module2::{new_data, Data2};
struct Data has drop {
v: u64
}
struct Box<T> has drop {
v: T
}
#[private_generics(T)]
public fun new_box<T, U>(value: T): Box<T> {
Box { v: value }
}
public fun get_box_value<T>(box: &Box<T>): &T {
&box.v
}
#[test]
fun test1() {
let data = Data { v: 123 };
let box = new_box<Data, u64>(data);
assert!(get_box_value(&box).v == 123, 1000);
}
#[test]
fun test2() {
let data2 = new_data(456);
let box2 = new_box<Data2, Data2>(data2);
assert!(get_box_value(&box2) == &new_data(456), 2000)
}
}
我们导入 module2
定义的类型和函数,然后创建单元测试 test2
。
测试中我们调用 new_data
函数创建 module2::Data2
类型的值 data2
,并在将私有泛型函数的类型参数修改为 Data2
。
注意,这个时候调用私有泛型函数所传递的类型参数 Data2
是在外部模块定义的,显然无法通过私有泛型约束!
此时,运行单元测试:
$ rooch move test
error: resource type "Data2" in function "0xfc3c1fa4f1538deee1048fa066a1b0029f2cf428e21667e5a7d4d570626c112e::module1::new_box" not defined in current module or not allowed
┌─ ./sources/module1.move:32:20
│
32 │ let box2 = new_box<Data2, Data2>(data2);
│ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Error: extended checks failed
模块可以调用其他模块定义的私有泛型函数
接下来,我们再做一个小测试,检查其他模块是否可以调用私有泛型函数。
我们再新建一个模块 module3
:
module rooch_examples::module3 {
#[test_only]
use rooch_examples::module1::{new_box, get_box_value};
struct Data3 has copy, drop {
v: u64
}
#[test]
fun test3() {
let data3 = Data3 { v: 789 };
let box3 = new_box<Data3, u8>(data3);
assert!(get_box_value(&box3) == &Data3 { v: 789 }, 3000);
}
}
可以看到,此时 module3
导入了 module1
定义的私有泛型函数 new_box
和获取 Box<T>
类型的泛型函数 get_box_value
。
这个时候我们将 module1
中的单元测试 test2
注释掉,并重新运行单元测试:
$ rooch move test
INCLUDING DEPENDENCY MoveStdlib
INCLUDING DEPENDENCY MoveosStdlib
INCLUDING DEPENDENCY RoochFramework
BUILDING private_generics
Running Move unit tests
2024-06-26T15:59:25.520858Z INFO moveos_common::utils: set max open fds 8192
[ PASS ] 0xfc3c1fa4f1538deee1048fa066a1b0029f2cf428e21667e5a7d4d570626c112e::module3::test3
[ PASS ] 0xfc3c1fa4f1538deee1048fa066a1b0029f2cf428e21667e5a7d4d570626c112e::module1::test1
Test result: OK. Total tests: 2; passed: 2; failed: 0
应用
在 MoveOS 标准库中定义了这样一个函数:
#[private_generics(T)]
/// Borrow a mut resource from the account's storage
/// This function equates to `borrow_global_mut<T>(address)` instruction in Move
public fun borrow_mut_resource<T: key>(account: address): &mut T {
let account_obj = borrow_mut_account_internal(account);
account_borrow_mut_resource_interal<T>(account_obj)
}
函数 borrow_mut_resource
是一个私有泛型函数,被私有泛型约束的类型 T
是一种资源类型。
像这类私有泛型函数,调用的它的模块必定定义了私有泛型约束的类型,那么接下来看一看在 Rooch Framework 中使用的一个例子:
public fun install_auth_validator<ValidatorType: store>(account_signer: &signer) {
features::ensure_testnet_enabled();
let validator = auth_validator_registry::borrow_validator_by_type<ValidatorType>();
let validator_id = auth_validator::validator_id(validator);
let account_addr = signer::address_of(account_signer);
assert!(
!is_auth_validator_installed(account_addr, validator_id),
ErrorAuthValidatorAlreadyInstalled);
if (!account::exists_resource<InstalledAuthValidator>(account_addr)) {
let installed_auth_validator = InstalledAuthValidator {
validators: vector::empty(),
};
account::move_resource_to<InstalledAuthValidator>(account_signer, installed_auth_validator);
};
let installed_auth_validator = account::borrow_mut_resource<InstalledAuthValidator>(account_addr);
vector::push_back(&mut installed_auth_validator.validators, validator_id);
}
account_authentication.move
中的 install_auth_validator
函数用到了 account::borrow_mut_resource
私有泛型函数,那么意味者 InstalledAuthValidator
也必须在当前模块定义。
我们可以在当前模块开头找到 InstalledAuthValidator
类型的定义:
/// A resource that holds the auth validator ids for this account has installed.
struct InstalledAuthValidator has key {
validators: vector<u64>,
}
总结
我们通过了四个小例子,来演示了私有泛型的函数如何定义、如何使用,并带你看了一下在 Rooch Framework 中是如何使用私有泛型函数的,看到这里,相信您已经完全掌握了 Rooch 中私有泛型函数的概念、作用和用法了。