引用

引用 #

Move 提供两种类型的引用:

  • 不可变 & - 不能修改基础值(或其任何字段)。
  • mutable &mut - 允许通过对该引用的写入进行修改。

参考运营商

这里的符号 e: T 代表“表达式 e 具有类型 T”。

References #

SyntaxTypeDescription
&e&T where e: T and T is a non-reference typeCreate an immutable reference to e
&mut e&mut T where e: T and T is a non-reference typeCreate a mutable reference to e.
&e.f&T where e.f: TCreate an immutable reference to field f of struct e.
&mut e.f&mut T where e.f: TCreate a mutable reference to field f of structe.
freeze(e)&T where e: &mut TConvert the mutable reference e into an immutable reference.

&e.f&mut e.f 运算符既可用于在结构中创建新引用,也可用于扩展现有引用:

let s = S { f: 10 };
let f_ref1: &u64 = &s.f; // works
let s_ref: &S = &s;
let f_ref2: &u64 = &s_ref.f // also works

只要两个结构在同一个模块中,具有多个字段的引用表达式就可以工作:

struct A { b: B }
struct B { c : u64 }
fun f(a: &A): &u64 {
  &a.b.c
}

最后,请注意不允许引用引用:

let x = 7;
let y: &u64 = &x;
let z: &&u64 = &y; // will not compile

通过参考文献读写

可以读取可变和不可变引用以生成引用值的副本。

只能写入可变引用。写入 *x = v 会丢弃先前存储在 x 中的值并用 v 更新它。

SyntaxTypeDescription
*eT where e is &T or &mut TRead the value pointed to by e
*e1 = e2() where e1: &mut T and e2: TUpdate the value in e1 with e2.

禁用推断

可变引用可以用在需要不可变引用的上下文中:

let x = 7;
let y: &u64 = &mut x;

这是有效的,因为在底层下,编译器在需要的地方插入 freeze 指令。

更多冻结推理的例子:

fun takes_immut_returns_immut(x: &u64): &u64 { x }

// freeze inference on return value
fun takes_mut_returns_immut(x: &mut u64): &u64 { x }

fun expression_examples() {
    let x = 0;
    let y = 0;
    takes_immut_returns_immut(&x); // no inference
    takes_immut_returns_immut(&mut x); // inferred freeze(&mut x)
    takes_mut_returns_immut(&mut x); // no inference

    assert!(&x == &mut y, 42); // inferred freeze(&mut y)
}

fun assignment_examples() {
    let x = 0;
    let y = 0;
    let imm_ref: &u64 = &x;

    imm_ref = &x; // no inference
    imm_ref = &mut y; // inferred freeze(&mut y)
}

子类型

通过这个 freeze 推断,Move 类型检查器可以将 &mut T 视为 &T 的子类型。如上所示,这意味着对于任何使用 &T 值的表达式,也可以使用 &mut T 值。该术语用于错误消息中,以简明地指示在提供 &T 的地方需要 &mut T。例如

address 0x42 {
module example {
    fun read_and_assign(store: &mut u64, new_value: &u64) {
        *store = *new_value
    }

    fun subtype_examples() {
        let x: &u64 = &0;
        let y: &mut u64 = &mut 1;

        x = &mut 1; // valid
        y = &2; // invalid!

        read_and_assign(y, x); // valid
        read_and_assign(x, y); // invalid!
    }
}
}

将产生以下错误消息

error:

    ┌── example.move:12:9 ───
    
 12          y = &2; // invalid!
             ^ Invalid assignment to local 'y'
    ·
 12          y = &2; // invalid!
                 -- The type: '&{integer}'
    ·
  9          let y: &mut u64 = &mut 1;
                    -------- Is not a subtype of: '&mut u64'
    

error:

    ┌── example.move:15:9 ───
    
 15          read_and_assign(x, y); // invalid!
             ^^^^^^^^^^^^^^^^^^^^^ Invalid call of '0x42::example::read_and_assign'. Invalid argument for parameter 'store'
    ·
  8          let x: &u64 = &0;
                    ---- The type: '&u64'
    ·
  3      fun read_and_assign(store: &mut u64, new_value: &u64) {
                                    -------- Is not a subtype of: '&mut u64'
    

当前唯一具有子类型的其他类型是元组。

所有权

可变和不可变引用始终可以被复制和扩展,即使存在相同引用的现有副本或扩展:

fun reference_copies(s: &mut S) {
  let s_copy1 = s; // ok
  let s_extension = &mut s.f; // also ok
  let s_copy2 = s; // still ok
  ...
}

对于熟悉 Rust 的所有权系统的程序员来说,这可能会令人惊讶,因为它会拒绝上面的代码。Move 的类型系统在处理副本方面更为宽松,但在写入前确保可变引用的唯一所有权方面同样严格。