前语

上一篇文章咱们了解了Move言语的结构体与类型体系,这篇文章将会介绍Move中的所有权机制。

所有权owership

Move虚拟机完成了相似Rust的所有权体系,有兴趣能够先了解一下Rust中的所有权体系。

每一个变量都有自己的规模,当超出规模时,该规模内的变量也会被丢弃。

咱们现已在表达式的相关章节中看到了这种现象,记住一个变量只在自己的规模内收效。每一个包括变量的规模都是所有者,变量能够是在这个规模内经过let定义的,也能够是经过参数传递进这个规模的,在Move中只要函数能将变量传递进一个规模。

每一个变量都只要一个具有者,这意味着当一个变量被当作参数传递给一个函数时,这个函数就变成了这个变量新的所有者。

script {
  use {{sender}}::M;
  fun main() {
    // Module::T是一个结构体
    let a: Module::T = Module::create(10);
    // 这时候变量a脱离main函数的规模,进入M::value函数规模内
    M::value(a);
    // 这时候main函数规模内现已没有a这个变量,编译会报错
    M::value(a);
  }
}

模块M的完成如下:

module M {
  struct T {value: u8}
  public fun create(value: u8): T {
    T {value}
  }
  //变量t传递给函数value,value函数具有变量的所有权
  public fun value(t: T): u8 {
    t.value
  }
  // 这时候函数规模完毕,变量t被丢弃,不会再存在
}

move和copy

首先咱们需要了解Move VM是怎么作业的,当咱们传递参数给一个函数又发生了什么,在VM中有两个字节码指令,一个是MoveLoc,一个是CopyLoc,它们分别能够经过move和copy关键字运用。

当一个变量被传递给其他函数时,它被运用MoveLoc移动,比如如下

script {
  use {{sender}}::M;
  fun main() {
    // Module::T是一个结构体
    let a: Module::T = Module::create(10);
    // 这时候变量a脱离main函数的规模,进入M::value函数规模内
    M::value(move a);
    //变量a现已被抛弃
  }
}

move关键字能够省掉,这儿仅仅为了说明

假如想传递一个值给函数且想保存变量的值能够运用关键字copy。

script {
  use {{sender}}::M;
  fun main() {
    // Module::T是一个结构体
    let a: Module::T = Module::create(10);
    M::value(copy a);
    //变量a依然存在
  }
}

以上咱们经过copy关键字避免了变量被抛弃,可是copy会增加内存运用,当copy非常大的数据时价值很大,在区块链中每个字节都会影响执行的价值,为了避免过大的额定开支,能够运用引证。

引证

许多编程言语都完成了引证,引证是变量的链接,经过引证能够将变量传递给程序其他部分而不用传递变量的值。

引证(经过&)能够不需要所有权就能够获取到一个变量

module M {
  struct T {value: u8}
  //传递一个引证而不是传递一个值
  public fun value(t: &T): u8 {
    t.value
  }
}

不可变的引证只能读取变量的值,不能改动变量的值,可变的引证能够读写变量的值。

module M {
  struct T {value: u8}
  //回来一个非引证类型的值
  public fun create(value: u8): {
    T {value}
  }
  //不可变的引证只答应读
  public fun value(t: &T): u8 {
    t.value
  }
  // 可变引证答应读写值
  public fun change(t: &mut T, value: u8) {
    t.value = value;
  }
}

Borrow查看

Move中经过Borrow查看来控制程序中引证的运用,这样有助于避免出错。

module Borrow {
    struct B { value: u64 }
    struct A { b: B }
    // 创立一个含有B的A
    public fun create(value: u64): A {
        A { b: B { value } }
    }
    // 取得B的可变引证
    public fun ref_from_mut_a(a: &mut A): &mut B {
        &mut a.b
    }
    // 改动B
    public fun change_b(b: &mut B, value: u64) {
        b.value = value;
    }
}
script {
    use {{sender}}::Borrow;
    fun main() {
        // 创立一个A
        let a = Borrow::create(0);
        // 经过A获取B的可变引证
        let mut_a = &mut a;
        let mut_b = Borrow::ref_from_mut_a(mut_a);
        // 改动B
        Borrow::change_b(mut_b, 100000);
        // 获取另一个A的可变引证
        let _ = Borrow::ref_from_mut_a(mut_a);
    }
}

上面代码能够成功编译运转,不会报错。这儿终究发生了什么呢?首先,咱们运用 A 的可变引证(&mut A)来获取对其内部 struct B 的可变引证(&mut B)。然后咱们改动 B。然后能够再次经过 &mut A 获取对 B 的可变引证。

可是,假如咱们交流最后两个表达式,即首先尝试创立新的 &mut A,而 &mut B 仍然存在,会出现什么状况呢?

let mut_a = &mut a;
let mut_b = Borrow::ref_from_mut_a(mut_a);
let _ = Borrow::ref_from_mut_a(mut_a);
Borrow::change_b(mut_b, 100000);

此时编译器会报错

    ┌── /scripts/script.move:10:17 ───
    │
 10let _ = Borrow::ref_from_mut_a(mut_a);
    │                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Invalid usage of reference as function argument. Cannot transfer a mutable reference that is being borrowed
    
  8let mut_b = Borrow::ref_from_mut_a(mut_a);
    │                     ----------------------------- It is still being mutably borrowed by this reference
    │

该代码不会编译成功。为什么?因为 &mut A 现已被 &mut B 借用。假如咱们再将其作为参数传递,那么咱们将堕入一种奇怪的状况,A 能够被更改,但 A 一起又被引证。
定论如下

  • 编译器经过所谓的”借用查看”(最初是Rust言语的概念)来避免上面这些过错。编译器经过树立”借用图”,不答应被借用的值被”move”。这便是 Move 在区块链中如此安全的原因之一。
  • 能够从引证创立新的引证,老的引证将被新引证”借用”。可变引证能够创立可变或许不可变引证,而不可变引证只能创立不可变引证。
  • 当一个值被引证时,就无法”move”它了,因为其它值对它有依赖。

取值

能够经过取值运算*来获取引证所指向的值。

值运算实际上是发生了一个副本,要保证这个值具有 Copy ability。

module M {
    struct T has copy {}
    // value t here is of reference type
    public fun deref(t: &T): T {
        *t
    }
}

取值运算不会将原始值 move 到当前效果域,实际上仅仅生成了一个副本

有一个技巧用来复制一个结构体的字段:便是运用*&,引证并取值。咱们来看一个比如

module M {
    struct H has copy {}
    struct T { inner: H }
    // ...
    // we can do it even from immutable reference!
    public fun copy_inner(t: &T): H {
        *&t.inner
    }
}

根本类型

根本类型非常简略,它们不需要作为引证传递,缺省会被复制。当根本类型的值被传给函数时,相当于运用了copy关键字,传递进函数的是它们的副本。当然你能够运用move关键字强制不发生副本,可是因为根本类型的巨细很小,复制它们其实开支很小,甚至比经过引证或许”move”传递它们开支更小。

最后

这篇文章首要介绍了Move中的所有权,更多文章能够关注大众号QStack。