美文网首页
细说Rust中的可变引用和不可变引用

细说Rust中的可变引用和不可变引用

作者: Loru_vest | 来源:发表于2020-08-19 23:35 被阅读0次

mut a: &T和a: &mut T

这两个东西看起来挺像,只不过是mut的位置相反而已,然而意义却是天壤之别。

struct Foo(usize);

fn main() {

    let mut a = &Foo(2);
    a = &Foo(5);  // ok
    a.0 = 7;      // [E0594]: cannot assign to `a.0` which is behind a `&` reference
    
    let b = &mut Foo(42);
    b = &Foo(36); // [E0384]: cannot assign twice to immutable variable `b`
    b.0 = 100;    // ok
    
}

这段代码很好地诠释了它们的不同。

对于mut a: &Foo(2)而言,可变的是变量a本身,而不是它指向的不可变引用,因此,你可以修改a的值(因为a是可变的),而不能对a引用的值进行修改(因为a是不可变引用)。

对于b: &mut Foo(42)而言,可变的是变量b引用的值,而不是它本身,因此,你不可以修改b的值,但可以对b引用的值进行修改,因为它指向一个可变引用。

mut的位置不同,可变的东西也就不同,把引用比作跳板,前者就相当于你可以换跳板,但是你不能动跳板,后者相当于你就得用这个跳板,但是你可以把它拆了(

&mut T实现了move semantic 吗

fn main() {
  
    let mut name = String::from("TENX-S");
    
    let a = &mut name;
    let b = a;         // `a` has been moved!
   
    add_string(a);     // 爆炸
    add_string(b);
    add_string(b);     // `b` has not been moved!
    
}

fn add_string(s: &mut String) {
    s.push_str("Rust");
}

这个问题曾困扰了我许久,直到看到了StackOverflow上的这篇回答才知道答案,其实只是编译器玩了一个小小的把戏。

回答问题之前,我们先说move是什么,简单一点就是如果一个类型没有实现Copy这个trait,那么把这个类型的变量赋给另一个变量或传递给一个函数时就会发生move,细节上来说,就是把右值的value赋给左值,然后invaildate右值的地址,使之不可访问;不过在TRPL中是用ownership解释的,本质是一样的。

不可变引用实现了Copy,所以不会因为赋值而发生move

然而,可变引用不是Copy的,想想都知道,如果是Copy的,一个scope中就能轻松地写出多个指向同一个类型的可变引用,直接原地爆炸(

但是,我们又知道,没有实现Copy的类型在赋值和传递函数参数时都会发生move,那么例子中的b为什么没有move呢?

因为隐式重借用(implicitly reborrowed)取代了move,即对编译器来说:

add_string(y);

变成了:

add_string(&mut *y);

原始引用被解引用了,一个新的可变引用被创建了,这个新的引用被move进了函数内部,而原始引用在函数结束时被释放了

然而,隐式重借用也是有适用范围的,即类型必须已知才可以,否则就会move,看下面这个例子:

fn bar<T>(_a: T, _b: T) {}

fn main() {
    let mut i = 42;
    let mut j = 43;
    let x = &mut i;
    let y = &mut j;
    bar(x, y);   // Moves x, but reborrows y.
    let _z = x;  // error[E0382]: use of moved value: `x`
    let _t = y;  // Works fine. 
}

变量前用下划线表示不倾向于使用它,否则编译器会发出warning: unused variable ...

x为什么被move了?因为函数参数此时未知,y为什么没被move,因为函数参数已知,发生隐式重借用。

如果你弄明白了上面所说的,请看下面这个例子:

struct Foo;

fn main() {
    let a = &mut Foo;
    a.mut_ref();
}

impl Foo {
    fn mut_ref(&mut self) { }
}

变量a被声明为不可变的,然而,我们却获取了它的可变引用?是这样吗?

答案是否定的,我们并没有获得变量a的可变引用,解释如下:

我们都知道,其实&self是语法糖,所以

impl Foo {
    fn mut_ref(&mut self) { }
}

其实相当于:

impl Foo {
    fn mut_ref(self: &mut Self) { }
}

所以:

a.mut_ref();

其实是:

Foo::mut_ref(&mut *a);

整个过程中,并没有使用a的可变引用!

这题可以当作一个面试题,不过有点偏门了,因为即使是Rust reference中也没有讲到它。

不过如果你看过这篇文章,然后答上来了,会是一个不错的加分项。

相关文章

  • 细说Rust中的可变引用和不可变引用

    mut a: &T和a: &mut T 这两个东西看起来挺像,只不过是mut的位置相反而已,然而意义却是天壤之别。...

  • 《kotlin实战》阅读笔记(1)

    变量 分为 可变的引用(var) 不可变的引用(val) 可变和不可变类似于c++ 中 const 对于指针的限定...

  • Kotlin笔记

    一、Kotlin基础 1.1 变量 在Kotlin中变量分为可变引用var和不可变引用val,val对应的是jav...

  • rust 所有权

    不可变变量 可变变量 所有权三个规格 内存与分配 MOVE COPY 所有权和函数 引用与借用 可变引用的限制 引用悬挂

  • python 变量进阶

    目标 变量的引用 可变和不可变类型 局部变量和全局变量 01. 变量的引用 变量 和 数据 都是保存在 内存 中的...

  • 02.1.Python变量续

    目标 变量的引用 可变和不可变类型 局部变量和全局变量 01. 变量的引用 变量 和 数据 都是保存在 内存 中的...

  • 10.Python变量续

    目标 变量的引用 可变和不可变类型 局部变量和全局变量 01. 变量的引用 变量 和 数据 都是保存在 内存 中的...

  • 09.Python变量续

    目标 变量的引用 可变和不可变类型 局部变量和全局变量 01. 变量的引用 变量 和 数据 都是保存在 内存 中的...

  • 2.变量是声明

    val: 引用不可变 var: 值可变

  • Python中 传递值 与 传递引用 的区别

    对于不可变类型传递值(不会影响原数据) 不可变类型 对于可变类型传递引用(会影响原数据) 不可变类型传递引用 py...

网友评论

      本文标题:细说Rust中的可变引用和不可变引用

      本文链接:https://www.haomeiwen.com/subject/iyejjktx.html