一文读懂私有泛型函数

什么是私有泛型函数

私有泛型函数是专门为 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_boxget_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 会隐式推断为 &123u32get_box_valuebox 中获取到 &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 中私有泛型函数的概念、作用和用法了。