17

从关于如何改变结构字段的Rust 书中:

let mut point = Point { x: 0, y: 0 };
point.x = 5;

然后:

可变性是绑定的属性,而不是结构本身的属性。

这对我来说似乎违反直觉,因为point.x = 5看起来我并没有重新绑定变量point。有没有办法解释这一点,所以它更直观?

我可以解决这个问题的唯一方法是“想象”我正在重新绑定到具有不同值point的原始副本(甚至不确定这是否准确)。Pointx

4

4 回答 4

10

这对我来说似乎违反直觉,因为 point.x = 5 看起来我并没有重新绑定变量点。有没有办法解释这一点,所以它更直观?

所有这一切都是说,某事物是否可变取决于let变量的 - 语句(绑定),而不是类型或任何特定字段的属性。

在示例中,point它的字段是可变的,因为point是在语句中引入的let mut(与简单let语句相反),而不是因为Point一般类型的某些属性。

作为对比,为了说明为什么这很有趣:在其他语言中,例如 OCaml,您可以在类型定义中将某些字段标记为可变:

type point =
   { x: int;
     mutable y: int;
   };

这意味着你可以改变y每个point值的字段,但你永远不能改变x

于 2015-07-31T17:26:59.760 回答
7

这里的“绑定”不是动词,而是名词。你可以说在 Rust 中绑定是变量的同义词。因此,您可以像这样阅读该段落

可变性是变量的属性,而不是结构本身的属性。

现在,我想,应该很清楚了——你将变量标记为可变的,这样你就可以修改它的内容了。

于 2015-07-31T17:25:38.007 回答
7

我也有同样的困惑。对我来说,它来自两个不同的误解。首先,我来自一种语言,其中变量(也称为绑定)是对值的隐式引用。在那种语言中,重要的是区分改变引用和改变被引用的值。其次,我认为书中的“结构本身”是指实例化的值,但“结构”是指规范/声明,而不是该类型的特定值。

Rust 中的变量是不同的。从参考

变量是堆栈帧的组成部分...

局部变量(或堆栈局部分配)直接保存一个值,在堆栈内存中分配。该值是堆栈帧的一部分。

所以变量是堆栈帧的一个组件——一块内存——直接保存值。没有引用来区分值本身,也没有引用 mutate。变量和值是同一块内存。

结果是重新绑定变量以将其更改为引用不同的内存块与 Rust 的内存模型不兼容。(nblet x = 1; let x = 2;创建两个变量。)

所以这本书指出,可变性是在“每块内存”级别声明的,而不是作为结构定义的一部分。

我可以解决这个问题的唯一方法是“想象”我将点重新绑定到具有不同 x 值的原始点的副本(甚至不确定这是否准确)

相反,假设您正在将一大块内存中的一个 0 更改为 5;并且该值驻留在由 指定的内存中point。将“绑定是可变的”解释为意味着您可以改变由绑定指定的内存块,包括仅改变其中的一部分,例如通过设置结构字段。考虑以您描述为在 Rust 中不可表达的方式重新绑定 Rust 变量。

于 2015-08-01T19:04:24.567 回答
6

@mn 的回答让我走上了正确的道路。这都是关于堆栈地址的!这是一个演示,它在我的脑海中凝固了实际发生的事情。

struct Point {
    x: i64,
    y: i64,
}

fn main() {
    {
        println!("== clobber binding");
        let a = 1;
        println!("val={} | addr={:p}", a, &a);
        // This is completely new variable, with a different stack address
        let a = 2;
        println!("val={} | addr={:p}", a, &a);
    }
    {
        println!("== reassign");
        let mut b = 1;
        println!("val={} | addr={:p}", b, &b);
        // uses same stack address
        b = 2;
        println!("val={} | addr={:p}", b, &b);
    }
    {
        println!("== Struct: clobber binding");
        let p1 = Point{ x: 1, y: 2 };
        println!(
          "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}",
          p1.x, p1.y,            &p1,            &p1.x,      &p1.y);

        let p1 = Point{ x: 3, y: 4 };
        println!(
          "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}",
          p1.x, p1.y,            &p1,            &p1.x,      &p1.y);
    }
    {
        println!("== Struct: reassign");
        let mut p1 = Point{ x: 1, y: 2 };
        println!(
          "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}",
          p1.x, p1.y,            &p1,            &p1.x,      &p1.y);

        // each of these use the same addresses; no new addresses
        println!("   (entire struct)");
        p1 = Point{ x: 3, y: 4 };
        println!(
          "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}",
          p1.x, p1.y,            &p1,            &p1.x,      &p1.y);

        println!("   (individual members)");
        p1.x = 5; p1.y = 6;
        println!(
          "xval,yval=({}, {}) | pointaddr={:p}, xaddr={:p}, yaddr={:p}",
          p1.x, p1.y,            &p1,            &p1.x,      &p1.y);
    }
}

输出(每次运行的地址显然略有不同):

== clobber binding
val=1 | addr=0x7fff6112863c
val=2 | addr=0x7fff6112858c
== reassign
val=1 | addr=0x7fff6112847c
val=2 | addr=0x7fff6112847c
== Struct: clobber binding
xval,yval=(1, 2) | pointaddr=0x7fff611282b8, xaddr=0x7fff611282b8, yaddr=0x7fff611282c0
xval,yval=(3, 4) | pointaddr=0x7fff61128178, xaddr=0x7fff61128178, yaddr=0x7fff61128180
== Struct: reassign
xval,yval=(1, 2) | pointaddr=0x7fff61127fd8, xaddr=0x7fff61127fd8, yaddr=0x7fff61127fe0
   (entire struct)
xval,yval=(3, 4) | pointaddr=0x7fff61127fd8, xaddr=0x7fff61127fd8, yaddr=0x7fff61127fe0
   (individual members)
xval,yval=(5, 6) | pointaddr=0x7fff61127fd8, xaddr=0x7fff61127fd8, yaddr=0x7fff61127fe0

关键点是:

  • 用于let“破坏”现有绑定(新堆栈地址)。即使声明了变量也会发生这种情况mut,所以要小心。
  • 用于mut重用现有堆栈地址,但在重新分配时不要使用let

这个测试揭示了一些有趣的事情:

  • 如果您重新分配整个可变结构,则相当于单独分配每个成员。
  • 保存结构的变量的地址与第一个成员的地址相同。如果您来自 C/C++ 背景,我想这是有道理的。
于 2015-08-24T16:43:09.610 回答