Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

可见性修饰符

可见性修饰符控制模块成员(函数、结构体等)的访问范围,是 Move 模块化设计的核心机制。Move 提供四种可见性级别:私有(private)、公共(public)、包内可见(public(package))和入口(entry),每种级别对应不同的访问权限和使用场景。合理使用可见性可以实现良好的封装,保护模块的内部实现细节。

私有可见性(private)

默认访问级别

不添加任何修饰符的函数默认是私有的,只能在定义它的模块内部调用:

module book::private_example;

// 私有函数 —— 只有本模块内部可以调用
fun internal_helper(): u64 {
    42
}

fun another_helper(): u64 {
    // 同一模块内,可以调用私有函数
    internal_helper() + 8
}

#[test]
fun private_only_in_module() {
    assert_eq!(internal_helper(), 42);
    assert_eq!(another_helper(), 50);
}

私有函数是模块封装的基础。将实现细节隐藏在私有函数中,只暴露必要的公共接口,是良好 API 设计的关键。

公共可见性(public)

对外开放的接口

使用 public 修饰的函数可以被任何模块调用,是模块对外暴露的 API:

module book::public_example;

const EInvalid: u64 = 0;

// 公共函数 —— 任何模块都可以调用
public fun calculate(a: u64, b: u64): u64 {
    validate(a, b);
    a + b
}

// 内部验证逻辑保持私有
fun validate(a: u64, b: u64) {
    assert!(a > 0 && b > 0, EInvalid);
}

#[test]
fun public_calculate() {
    assert_eq!(calculate(3, 7), 10);
}

需要注意:一旦函数被标记为 public,它就成为模块的公共 API。在包升级时,不能删除或更改已有的 public 函数签名,否则会破坏依赖它的外部模块。

包内可见性(public(package))

包级别的共享

public(package) 允许同一个包(package)内的所有模块调用该函数,但包外的模块无法访问。它取代了早期版本中的 friend 机制:

module book::package_example;

// 包内可见 —— 同一个包的模块可以调用,外部包不行
public(package) fun package_helper(): u64 {
    100
}

// 公共函数调用包内函数
public fun public_api(): u64 {
    package_helper() * 2
}

#[test]
fun package_visibility() {
    assert_eq!(package_helper(), 100);
    assert_eq!(public_api(), 200);
}

public(package) 非常适合用于包内多个模块之间的协作函数,这些函数需要被包内其他模块使用,但不应该暴露给外部。

入口可见性(entry)

交易的入口点

entry 函数可以直接从 Sui 交易中被调用,但不能从其他 Move 模块调用。它是连接链下客户端和链上逻辑的桥梁:

module book::entry_example;

public struct Greeting has key {
    id: UID,
    text: vector<u8>,
}

// 入口函数 —— 只能从交易调用,不能被其他模块调用
entry fun create_greeting(text: vector<u8>, ctx: &mut TxContext) {
    let greeting = Greeting {
        id: object::new(ctx),
        text,
    };
    transfer::transfer(greeting, ctx.sender());
}

// entry 函数作为交易入口,不可被其他模块调用,也不返回值(与 public 二选一,不要写 public entry)
entry fun update_greeting(greeting: &mut Greeting, text: vector<u8>) {
    greeting.text = text;
}

可见性对比示例

完整的可见性示例

将所有可见性级别放在一个模块中对比:

module book::visibility_example;

// 私有 —— 仅本模块可调用
fun internal_helper(): u64 { 42 }

// 公共 —— 任何模块都可调用
public fun public_api(): u64 { internal_helper() }

// 包内可见 —— 同一包的模块可调用
public(package) fun package_helper(): u64 { 100 }

// 入口 —— 仅从交易调用
entry fun do_something(ctx: &mut TxContext) {
    let _ = ctx;
}

从其他模块调用

下面展示在同一包的另一个模块中,哪些函数可以调用,哪些不行:

module book::try_access;

use book::visibility_example;

fun test() {
    visibility_example::public_api();      // OK —— 公共函数
    visibility_example::package_helper();   // OK —— 同一包内
    // visibility_example::internal_helper(); // 错误!私有函数不可调用
    // visibility_example::do_something();    // 错误!entry 函数不能被模块调用
}

结构体字段的可见性

字段始终是私有的

Move 中结构体的字段始终是私有的,无法直接从模块外部访问。需要通过公共函数来提供读写接口:

module book::field_visibility;

public struct User has drop {
    name: vector<u8>,
    age: u64,
}

public fun new(name: vector<u8>, age: u64): User {
    User { name, age }
}

// 通过公共函数提供读取接口
public fun name(user: &User): &vector<u8> {
    &user.name
}

public fun age(user: &User): u64 {
    user.age
}

// 通过公共函数提供修改接口
public fun set_age(user: &mut User, new_age: u64) {
    user.age = new_age;
}

#[test]
fun field_access() {
    let mut user = new(b"Alice", 25);
    assert_eq!(age(&user), 25);

    set_age(&mut user, 26);
    assert_eq!(age(&user), 26);
}

重要提示:虽然结构体字段在代码层面是私有的,但这并不意味着数据是机密的。链上对象的所有数据都是公开可读的。字段的私有性是一种编程封装,不是数据隐私保护。

小结

Move 提供四种可见性级别来控制函数的访问范围:private(默认)仅模块内部可见,public 对所有模块开放,public(package) 限制在同一包内,entry 仅供交易直接调用。结构体的字段始终是私有的,需要通过公共函数暴露读写接口。合理运用可见性修饰符是良好模块设计的基础——暴露最小必要的接口,隐藏内部实现细节。