Understanding struct-field mutation

爱⌒轻易说出口 提交于 2019-12-10 12:29:54

问题


From the Rust book about how to mutate struct fields:

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

and later:

Mutability is a property of the binding, not of the structure itself.

This seems counter-intuitive to me because point.x = 5 doesn't look like I'm rebinding the variable point. Is there a way to explain this so it's more intuitive?

The only way I can wrap my head around this is to "imagine" that I'm rebinding point to a copy of the original Point with a different x value (not even sure that's accurate).


回答1:


I had the same confusion. For me it came from two separate misunderstandings. First, I came from a language where variables (aka bindings) were implicitly references to values. In that language it was important to distinguish between mutating the reference, and mutating the value that was referred to. Second, I thought by "the structure itself" the book was referring to the instantiated value, but by "the structure" it means the specification/declaration, not a particular value of that type.

Variables in Rust are different. From the reference:

A variable is a component of a stack frame...

A local variable (or stack-local allocation) holds a value directly, allocated within the stack's memory. The value is a part of the stack frame.

So a variable is a component of a stack frame -- a chunk of memory -- that directly holds the value. There is no reference to distinguish from the value itself, no reference to mutate. The variable and the value are the same hunk of memory.

A consequence is that rebinding a variable in the sense of changing it to refer to a different hunk of memory is not compatible with Rust's memory model. (n.b. let x = 1; let x = 2; creates two variables.)

So the book is pointing out that mutability is declared at the "per hunk of memory" level rather than as part of the definition of a struct.

The only way I can wrap my head around this is to "imagine" that I'm rebinding point to a copy of the original Point with a different x value (not even sure that's accurate)

Instead imagine you are changing one of the 0's in a hunk of memory to a 5; and that that value resides within the memory designated by point. Interpret "the binding is mutable" to mean that you can mutate the hunk of memory designated by the binding, including mutating just part of it, e.g. by setting a struct field. Think of rebinding Rust variables in the way that you describe as not expressible within Rust.




回答2:


This seems counter-intuitive to me because point.x = 5 doesn't look like I'm rebinding the variable point. Is there a way to explain this so it's more intuitive?

All this is saying is that whether or not something is mutable is determined by the let- statement (the binding) of the variable, as opposed to being a property of the type or any specific field.

In the example, point and its fields are mutable because point is introduced in a let mut statement (as opposed to a simple let statement) and not because of some property of the Point type in general.

As a contrast, to show why this is interesting: in other languages, like OCaml, you can mark certain fields mutable in the definition of the type:

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

This means that you can mutate the y field of every point value, but you can never mutate x.




回答3:


Here "binding" is not a verb, it is a noun. You can say that in Rust bindings are synonymous to variables. Therefore, you can read that passage like

Mutability is a property of the variable, not of the structure itself.

Now, I guess, it should be clear - you mark the variable as mutable and so you can modify its contents.




回答4:


@m-n's answer set me on the right track. It's all about stack addresses! Here's a demonstration that solidified in my mind what's actually happening.

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);
    }
}

Output (addresses are obviously slightly different per run):

== 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

The key points are these:

  • Use let to "clobber" an existing binding (new stack address). This happens even if the variable was declared mut, so be careful.
  • Use mut to reuse the existing stack address, but don't use let when reassigning.

This test reveals a couple of interesting things:

  • If you reassign an entire mutable struct, it's equivalent to assigning each member individually.
  • The address of the variable holding the struct is the same as the address of the first member. I guess this makes sense if you're coming from a C/C++ background.


来源:https://stackoverflow.com/questions/31751642/understanding-struct-field-mutation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!