了解结构域变异

Kel*_*vin 16 struct mutators rust

Rust书中了解如何改变struct字段:

let mut point = Point { x: 0, y: 0 };
point.x = 5;
Run Code Online (Sandbox Code Playgroud)

然后:

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

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

我能绕过这个问题的唯一方法就是"想象"我正在用不同的值重新绑定point到原始副本(甚至不确定是否准确).Pointx

fjh*_*fjh 10

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

所有这一切都在说,某个东西是否可变是由let变量的- 语句(绑定)决定的,而不是该类型或任何特定字段的属性.

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

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

type point =
   { x: int;
     mutable y: int;
   };
Run Code Online (Sandbox Code Playgroud)

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


Vla*_*eev 7

这里的"绑定"不是动词,而是名词.你可以说Rust绑定是变量的同义词.因此,你可以阅读那段经文

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

现在,我想,应该很清楚 - 您将变量标记为可变,因此您可以修改其内容.

  • 好吧,我认为这有点帮助.因此,就好像变量前面的"mut"只是将一个可变的*属性*应用于变量.并且该属性意味着某些事物,例如重新绑定和结构域修改.那是准确的吗? (2认同)

m-n*_*m-n 6

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

Rust中的变量是不同的.来自参考:

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

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

因此,变量是堆栈帧的一个组件 - 一块内存 - 直接保存该值.没有提及区分值本身,也没有提到mutate.变量和值是相同的内存块.

结果是,在更改变量的意义上重新绑定变量以引用不同的内存块与Rust的内存模型不兼容.(nb let x = 1; let x = 2;创建两个变量.)

因此,本书指出可变性是在"每块内存"级别而不是结构定义的一部分中声明的.

我能绕过这个问题的唯一方法是"想象"我正在重新指向具有不同x值的原始Point的副本(甚至不确定是否准确)

相反,想象一下你正在将一块内存中的一个0改为5; 并且该值位于由指定的内存中point.解释"绑定是可变的"意味着您可以改变绑定指定的内存块,包括仅改变其中的一部分,例如通过设置struct字段.考虑以Rust中描述为不可表达的方式重新绑定Rust变量.


Kel*_*vin 5

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

== 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
Run Code Online (Sandbox Code Playgroud)

关键点是:

  • 使用let以“撞”现有的结合(新堆栈地址)。即使已声明变量mut,也会发生这种情况,因此请小心。
  • 使用mut重用现有的堆栈地址,而不要使用let重新分配的时候。

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

  • 如果您重新分配整个可变结构,则相当于分别分配每个成员。
  • 拥有该结构的变量的地址与第一个成员的地址相同。我想如果您来自C / C ++背景,这很有意义。