rust 这是所有权的“转移”还是价值的“复制”?

8yoxcaq7  于 2023-02-16  发布在  其他
关注(0)|答案(3)|浏览(146)

我有一个关于Rust中的move语义的问题,在我的理解中,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");

为什么我可以重写user 1的“email”字段?看起来不像是重新绑定user 1。

dwthyt8l

dwthyt8l1#

我认为您的问题源于围绕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

lzfw57am

lzfw57am2#

我想你对变量的工作原理感到困惑。变量就像可以存储数据的杯子,对于像rust这样的强类型语言来说,它们只能容纳一种类型的数据。
因此,当你声明一个值时,基本上你是在指示计算机在内存中分配一些空间:

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对借用可变值进行了限制,以消除混乱,因为当多个变量可以改变杯中的内容时,很容易失去对谁做什么的控制。
在User类型的情况下,如果你把user1值初始化为可变的,你可以改变你在它的属性中存储的内容,就像杯子容纳其他杯子,或者指针指向堆中存储的其他杯子一样。

sczxawaw

sczxawaw3#

不,你不需要为了改变一个变量而可变地借用它。

let mut variable: i32 = 2;
    variable += 1;

可以很好地工作,当你想让你的函数修改它所接收的结构体时,可变的借位是很有用的,如果一个函数foo把它所接收的结构体的所有权作为参数,而不是接收一个可变的引用,那么当函数foo返回时,这个结构体的生命周期就结束了,你通常不希望这样,这就是为什么要给函数传递一个可变的引用。可变引用是一些函数的返回类型,如HashMap的get函数返回一个可变引用的可选项,它是一个可变引用的可选项。不是自己的类型,因为您希望HashMap继续拥有数据,并且您还希望允许get函数的调用者能够改变HashMap拥有的数据。假设HashMap函数通过复制其数据返回一个可选的拥有类型,在这种情况下,调用者所做的任何改变都不会影响存储在HashMap中的数据,因为调用者已经收到了数据的副本。

相关问题