Move语言 所有权

Move VM 实现了类似 Rust 的所有权功能。

每个变量只有一个所有者作用域。当所有者作用域结束时,变量将被删除。

变量的寿命与它的作用域一样长,我们曾经在表达式一章中看到过这种行为,大家还有没有印象?现在是了解其内部机制的绝佳时机了。

所有者是拥有某变量的作用域。变量可以在作用域内定义(例如,在脚本中使用关键字 let),也可以作为参数传递给作用域。由于 Move 中唯一的作用域是函数的作用域,所以除了这两种方法,没有其它方法可以将变量放入作用域。

每个变量只有一个所有者,这意味着当把变量作为参数传递给函数时,该函数将成为新所有者,并且第一个函数不再拥有该变量。或者可以说,第二个函数接管了变量的所有权

script {
    use {{sender}}::M;

    fun main() {
        // Module::T is a struct
        let a : Module::T = Module::create(10);

        // here variable `a` leaves scope of `main` function
        // and is being put into new scope of `M::value` function
        M::value(a);

        // variable a no longer exists in this scope
        // this code won't compile
        M::value(a);
    }
}

让我们看一下将变量传递给 value() 函数时,Move 内部发生的情况:

module M {
    // create_fun skipped
    struct T { value: u8 }

    public fun create(value: u8): T {
        T { value }
    }

    // variable t of type M::T passed
    // `value()` function takes ownership
    public fun value(t: T): u8 {
        // we can use t as variable
        t.value
    }
    // function scope ends, t dropped, only u8 result returned
    // t no longer exists
}

我们可以看到,当函数 value() 结束时,t 将不复存在,返回的只是一个 u8 类型的值。如何让t仍然可用呢?当然,一种快速的解决方案是返回一个元组,该元组包含原始变量和其它结果,但是 Move 还有一个更好的解决方案。

move 和 copy

首先,我们了解一下 Move VM 的工作原理,以及将值传递给函数时会发生什么。Move VM 里有两个字节码指令:MoveLoc 和 CopyLoc,反映到 Move 语言层面,它们分别对应关键字movecopy

将变量传递到另一个函数时,MoveLoc 指令被使用,它会被 move。我们可以像下面这样显式使用 move 关键字:

script {
    use {{sender}}::M;

    fun main() {
        let a : Module::T = Module::create(10);

        M::value(move a); // variable a is moved

        // local a is dropped
    }
}

这段代码是没有问题的,但是我们平常并不需要显示使用 move,缺省 a 会被 move。那么 copy 又是怎么回事呢?

关键字 copy

如果想保留变量的值,同时仅将值的副本传递给某函数,则可以使用关键字 copy

script {
    use {{sender}}::M;

    fun main() {
        let a : Module::T = Module::create(10);

        // we use keyword copy to clone structure
        // can be used as `let a_copy = copy a`
        M::value(copy a);
        M::value(a); // won't fail, a is still here
    }
}

上例中,我们第一次调用函数 value() 时,将变量 a 的副本传递给函数,并保留 a 在本地作用域中,以便第二次调用函数时再次使用它。

使用 copy 后,我们实际上复制了变量值从而增加了程序占用内存的大小。但是如果复制数据数据量比较大,它的内存消耗可能会很高。这里要注意了,在区块链中,交易执行时占用的内存资源是消耗交易费的,每个字节都会影响交易执行费用。因此不加限制的使用 copy 会浪费很多交易费。

现在,是时候学习引用了,它可以帮助我们避免不必要的copy 从而节省一些费用。

许多编程语言都支持引用。引用是指向变量(通常是内存中的某个片段)的链接,你可以将其传递到程序的其他部分,而无需移动变量值。引用(标记为&)使我们可以使用值而无需拥有所有权。我们修改一下上面的示例,看看如何使用引用。mod ...