Shared vs immutable references

- 3 mins

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中也没有讲到它。

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

rss facebook twitter github gitlab youtube mail spotify lastfm instagram linkedin google google-plus pinterest medium vimeo stackoverflow reddit quora quora