是所有权的“移动”还是价值的“复制”?

时间:2019-09-08 07:50:57

标签: rust move-semantics

我有一个与Rust中的移动语义有关的问题。 据我了解,Rust中的“ mut”关键字是要使一些变量 可变的,即可变变量可以再次绑定到另一个值;但是,此可变性仅用于绑定。因此,如果我真的想更改变量的值,则应使用“&mut”关键字,如下所示:

let mut two = 2;
let t = &mut two;
*t += 1;// update the value of two, not only bind t to another values
print!("{}", t); // here, t is 3

但是,在使用结构的情况下,似乎并非如此。

这是示例代码(https://doc.rust-lang.org/book/ch05-01-defining-structs.html):

let mut user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

user1.email = String::from("anotheremail@example.com");

为什么我可以重写user1的“电子邮件”字段? 似乎不像是对user1的重新绑定。

2 个答案:

答案 0 :(得分:1)

我认为您的问题源于mut作为关键字的一小部分困惑,类推可能会有所帮助。

想象一下,我拥有一辆汽车。实际上,让我们首先定义一下汽车是什么:

pub struct Car {
    fuel: usize,
    pub color: String,
    pub wheel_count: u8
}

可变定义

让我们定义我的车

let mut my_car:Car = Car { fuel: 100, color: "Green".to_string(), wheel_count: 4 };

这是我的车。我已将其定义为可变实体(let mut),因此,如果需要,可以加油并将其变成蓝色

my_car.fuel += 20;
my_car.color = "Blue".to_string();

这取决于变量本身的定义。如果我只有let我的车,那我就做不到。为变量分配let mut表示,无论谁具有以下条件,均可在所有字段中对其进行修改:

  • 拥有的对象
  • 向它借来的钱

参考文献/借阅

然后我决定去加油站加油。我把车借给服务员,以便他们为我做:

pub fn lend_car_to_attendant(target_car: &mut Car) {
  target_car.fuel += 20;
}

如果整辆车是他的临时工,他可能会开车去油漆厂并改变汽车的颜色。还值得注意的是,虽然他有车,但我不能用它做任何事。我把它借给了他,直到他退回borrow为止,就是他。

现在,当然,在有车时间之外,任何人都可以窥视我的车并欣赏它的颜色(或它的车轮数量)。拥有不可变借项的任何人都可以公开检查my_car的所有公共属性。

然后我决定尝试另一个加油站,事实证明这是不诚实的:

pub fn lend_car_to_naughty_attendant(target_car: &mut Car) {
    target_car.fuel += 20;
    lend_car_to_paint_shop(target_car);
}
pub fn lend_car_to_paint_shop(target_car: &mut Car) {
    target_car.color = "Bubblegum Pink".to_string();
}

我的车回来了泡泡糖粉红色!

我们可以通过有人监视汽车来避免这种情况。如果我们向&my_car借钱,然后再次尝试去那个糟糕的加油站,程序将根本无法编译(example here

答案 1 :(得分:0)

我认为您对变量的工作方式感到困惑。变量就像可以存储数据的杯子,对于像锈这样的强类型语言,变量只能存储一种数据。

因此,当您声明一个值时,基本上是在指示计算机在内存中分配一些空间:

let x: u32;

在上面的示例中,基本上您是在说“计算机,给我一个足够大的杯子来容纳u32数据(32 bits)”。

然后计算机为您提供杯子,在本例中,x是所有者。 x是您握住杯子的方式,换句话说,是存储空间的所有者。

现在让我们用适当的数据填充杯子:

x = 42;

一旦强大的计算机为x保留了该杯子,它就属于x,直到x放弃所有权:

  • 将其传递给另一个变量(rust称为move),
  • 或通过调用drop
  • 或x不在范围内。
{
  let x: u32 = 42;
  let y = x;
}

{
  let x: u32 = 42;
  drop(x);
}

{
  let x: u32 = 42;
}
 // x is dropped here

释放内存空间后,计算机可以将其分配给其他变量。

您可以如下检查内存空间的地址:

println!("address of var: {:p}", &x);

在初始化x时,计算机为您提供该存储空间。这是正在工作的资源获取初始化(RAII):

fn main() {
    let x: u32;
    println!("address of x: {:p}", &x);
    x = 12;
    println!("{}", x);
}

您收到编译器错误:

12 |     println!("address of x: {:p}", &x);
   |                                      ^^ use of possibly uninitialized `x`

但是,如果您在初始化后检查地址,它将编译而不会出现任何错误:

fn main() {
    let x: u32;
    x = 12;
    println!("address of x: {:p}", &x); // address of x: 0x7ffc8183402c
}

声明变量时,您与编译器有合同。 mut是该合同的条款之一。

通过不使用mut关键字,您只是说一旦填充(初始化变量),就永远不会更改杯中的内容(在内存空间x点)。一旦您加满杯子,它就会保持这种状态。

但是如果您使用mut关键字,合同会说您可以在该内存空间中放入任何值,只要它是正确的类型即可。

在两种情况下,该存储空间的所有者均为x。可变性与所有权无关。

现在,关于您的示例:

let mut two = 2;
let t = &mut two;
*t += 1;

在第二行中,您将对变量two进行可变引用。换句话说,t借用x指向的内容。在下一行中,将3填充到杯子中。由于您正在使用可变引用,因此您必须在下一行*t += 1;中取消引用。

let mut two = 2;
println!("address of &two: {:p}", &two);
let t = &mut two;
*t += 1;
println!("address of t: {:p}", t);

此打印:

address of &two: 0x7ffc5869c9c4
address of t  : 0x7ffc5869c9c4

two是所有者,t只是借来的:

let mut two = 2;
{
  let t = &mut two;
  *t += 1;
  println!("{:?}", t);
}

two += 10;
println!("{:?}", two);

再次的可变性与所有权无关,Rust限制了借用可变值以消除混乱,因为当多个变量可以改变杯子中的内容时,很容易失去对谁做什么的控制。

对于用户类型,如果将user1值初始化为可变值,则可以更改其属性中存储的内容。就像持有其他杯子的杯子,或指向堆中存储的其他杯子的指针一样。