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 语言采用所有权(Ownership)模型来管理值的生命周期。每个变量都有一个所有者和一个作用域,当作用域结束时,变量会被丢弃(drop)。所有权可以通过赋值或函数调用来转移,这种机制从根本上杜绝了悬垂引用和双重释放等内存安全问题。

作用域

函数作用域

每个函数定义一个作用域。在函数内声明的变量属于该函数所有,当函数执行结束时,所有局部变量都会被丢弃:

module book::scope_basic;

public struct Ticket has drop {
    event: vector<u8>,
}

fun create_and_drop() {
    let _ticket = Ticket { event: b"Concert" };
    // 函数结束时,_ticket 自动被丢弃(需要 drop 能力)
}

#[test]
fun scope() {
    create_and_drop();
}

块作用域

花括号 { } 创建子作用域(block scope)。子作用域中声明的变量在块结束时被丢弃,但可以通过块的最后一个表达式将值转移出去:

module book::block_scope;

#[test]
fun block_scope() {
    let x = {
        let inner = 42u64;
        inner  // 将所有权转移到外部作用域
    };
    // inner 在这里不可用,但它的值已经转移给了 x
    assert_eq!(x, 42);

    let result = {
        let a = 10u64;
        let b = 20u64;
        a + b  // 块的返回值
    };
    assert_eq!(result, 30);
}

嵌套作用域

作用域可以嵌套。内层作用域可以访问外层作用域的变量,但外层作用域无法访问内层的局部变量:

module book::nested_scope;

#[test]
fun nested() {
    let outer = 100u64;

    {
        let _inner = outer + 1;  // 可以访问外层变量
        assert_eq!(_inner, 101);
        // _inner 在块结束时被丢弃
    };

    // _inner 在这里不可用
    assert_eq!(outer, 100);  // outer 依然有效
}

所有权转移

函数调用时的所有权转移

当将一个不可复制的值作为参数传递给函数时,所有权会转移到被调用的函数。原变量变得无效,不能再使用:

module book::ownership_example;

public struct Ticket has drop {
    event: vector<u8>,
}

public struct UniqueItem {
    value: u64,
}

public fun create_ticket(): Ticket {
    Ticket { event: b"Concert" }  // 所有权转移给调用者
}

public fun use_ticket(ticket: Ticket) {
    let Ticket { event: _ } = ticket;  // ticket 在这里被消耗
}

#[test]
fun ownership() {
    let ticket = create_ticket();    // ticket 归当前函数所有
    // ticket 的所有权转移给 use_ticket,此后不再有效
    use_ticket(ticket);
    // let _ = ticket.event;  // 错误!ticket 已经被移动
}

赋值时的所有权转移

将一个不可复制的值赋给另一个变量时,所有权也会转移:

module book::ownership_transfer;

public struct Token has drop {
    value: u64,
}

#[test]
fun assignment_move() {
    let token_a = Token { value: 100 };
    let _token_b = token_a;  // 所有权从 token_a 转移到 token_b
    // assert!(token_a.value == 100);  // 错误!token_a 已经被移动
    assert_eq!(_token_b.value, 100);    // token_b 是有效的所有者
}

返回值的所有权转移

函数的返回值将所有权转移给调用者:

module book::ownership_return;

public struct Wrapper has drop {
    value: u64,
}

fun make_wrapper(): Wrapper {
    Wrapper { value: 42 }
    // 所有权转移给调用者,不会在函数结束时被丢弃
}

#[test]
fun return_ownership() {
    let wrapper = make_wrapper();  // 接收所有权
    assert_eq!(wrapper.value, 42);
    // wrapper 在测试函数结束时被丢弃
}

复制与移动

可复制类型

拥有 copy 能力的类型在赋值和传参时会自动复制,原变量仍然有效:

module book::copy_example;

#[test]
fun copy_vs_move() {
    // u64 拥有 copy 能力,赋值时自动复制
    let a = 10u64;
    let b = a;      // a 被复制,仍然有效
    assert_eq!(a, 10);
    assert_eq!(b, 10);

    // bool 也拥有 copy 能力
    let flag = true;
    let flag_copy = flag;
    assert_eq!(flag, true);
    assert_eq!(flag_copy, true);
}

不可复制类型

没有 copy 能力的类型在赋值时会移动,原变量失效:

module book::move_example;

public struct UniqueItem has drop {
    value: u64,
}

#[test]
fun unique_move() {
    let item = UniqueItem { value: 1 };
    let item2 = item;  // item 被移动到 item2
    // assert!(item.value == 1);  // 错误!item 已被移动
    assert_eq!(item2.value, 1);
}

move 关键字

可以使用 move 关键字显式地表达所有权转移的意图,让代码更加清晰:

module book::explicit_move;

public struct Resource has drop {
    data: u64,
}

fun consume(resource: Resource) {
    let Resource { data: _ } = resource;
}

#[test]
fun explicit_move() {
    let resource = Resource { data: 42 };
    consume(move resource);  // 显式移动
    // resource 在这里不再有效
}

析构与 drop

显式析构

对于没有 drop 能力的类型,必须显式析构(解包)来消耗它们:

module book::destruct_example;

public struct Receipt {
    amount: u64,
    paid: bool,
}

public fun create_receipt(amount: u64): Receipt {
    Receipt { amount, paid: true }
}

const ENotPaid: u64 = 0;

// 必须通过解包来消耗 Receipt
public fun verify_and_consume(receipt: Receipt): u64 {
    let Receipt { amount, paid } = receipt;
    assert!(paid, ENotPaid);
    amount
}

#[test]
fun destruct() {
    let receipt = create_receipt(500);
    let amount = verify_and_consume(receipt);
    assert_eq!(amount, 500);
}

drop 能力

拥有 drop 能力的类型可以在作用域结束时自动丢弃,无需显式析构:

module book::drop_example;

public struct Droppable has drop {
    value: u64,
}

public struct NotDroppable {
    value: u64,
}

#[test]
fun auto_drop() {
    let _d = Droppable { value: 1 };
    // 函数结束时自动丢弃,无需处理

    let nd = NotDroppable { value: 2 };
    // 必须显式析构
    let NotDroppable { value: _ } = nd;
}

小结

Move 的所有权模型确保了每个值在任意时刻只有一个所有者。值通过赋值、函数参数和返回值来转移所有权。拥有 copy 能力的类型可以复制,不可复制类型在赋值时会移动,原变量随即失效。作用域(函数和块)限定了变量的生命周期,作用域结束时变量被丢弃。没有 drop 能力的类型必须显式析构,这一机制可以用来实现“不可丢弃“的资源模式,保证重要操作不会被遗漏。