了解结构字段突变
Understanding struct-field mutation
来自 Rust book 关于如何改变结构字段的内容:
let mut point = Point { x: 0, y: 0 };
point.x = 5;
及以后:
Mutability is a property of the binding, not of the structure itself.
这对我来说似乎违反直觉,因为 point.x = 5
看起来不像我在重新绑定变量 point
。有没有办法解释这个更直观?
我唯一能解决这个问题的方法是 "imagine" 我将 point
重新绑定到原始 Point
的副本,但 x
值(甚至不确定那是准确的)。
这里"binding"不是动词,是名词。你可以说在 Rust 中绑定是变量的同义词。因此,你可以像
一样阅读那段话
Mutability is a property of the variable, 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?
这就是说,某物是否可变是由变量的 let
- 语句(绑定)决定的,而不是 属性 类型或任何特定领域。
在示例中,point
及其字段是可变的,因为 point
是在 let mut
语句(与简单的 let
语句相反)中引入的,而不是因为一般 Point
类型的某些 属性。
作为对比,说明为什么这很有趣:在其他语言中,例如 OCaml,您可以在类型定义中将某些字段标记为可变:
type point =
{ x: int;
mutable y: int;
};
这意味着您可以改变每个 point
值的 y
字段,但您永远不能改变 x
.
我也有同样的困惑。对我来说,它来自两个不同的误解。首先,我来自一种语言,其中变量(又名绑定)是对值的隐式引用。在该语言中,区分引用的变异和引用的值的变异是很重要的。其次,我认为书中 "the structure itself" 指的是实例化值,但 "the structure" 表示 specification/declaration,而不是该类型的特定值。
Rust 中的变量不同。来自 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.
因此,变量是堆栈帧的组成部分——一块内存——直接保存值。没有引用来区分值本身,没有引用 mutate。变量和值是同一块内存。
结果是重新绑定一个变量,将其更改为引用不同的内存块,这与 Rust 的内存模型不兼容。 (n.b。let x = 1; let x = 2;
创建两个变量。)
因此本书指出可变性是在 "per hunk of memory" 级别声明的,而不是作为结构定义的一部分。
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)
相反,假设您正在将一大块内存中的一个 0 更改为 5;并且该值位于 point
指定的内存中。将 "the binding is mutable" 解释为您可以改变绑定指定的内存块,包括只改变其中的一部分,例如通过设置结构字段。考虑以您描述的在 Rust 中无法表达的方式重新绑定 Rust 变量。
@m-n 的回答让我走上了正轨。这都是关于堆栈地址的!这是一个演示,让我牢记实际发生的事情。
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
到 "clobber" 现有绑定(新堆栈地址)。即使声明变量 mut
也会发生这种情况,所以要小心。
- 使用
mut
重用已有的栈地址,但重新分配时不要使用let
。
这个测试揭示了一些有趣的事情:
- 如果您重新分配整个可变结构,则相当于单独分配每个成员。
- 保存结构的变量地址与第一个成员的地址相同。如果您来自 C/C++ 背景,我想这是有道理的。
来自 Rust book 关于如何改变结构字段的内容:
let mut point = Point { x: 0, y: 0 };
point.x = 5;
及以后:
Mutability is a property of the binding, not of the structure itself.
这对我来说似乎违反直觉,因为 point.x = 5
看起来不像我在重新绑定变量 point
。有没有办法解释这个更直观?
我唯一能解决这个问题的方法是 "imagine" 我将 point
重新绑定到原始 Point
的副本,但 x
值(甚至不确定那是准确的)。
这里"binding"不是动词,是名词。你可以说在 Rust 中绑定是变量的同义词。因此,你可以像
一样阅读那段话Mutability is a property of the variable, 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?
这就是说,某物是否可变是由变量的 let
- 语句(绑定)决定的,而不是 属性 类型或任何特定领域。
在示例中,point
及其字段是可变的,因为 point
是在 let mut
语句(与简单的 let
语句相反)中引入的,而不是因为一般 Point
类型的某些 属性。
作为对比,说明为什么这很有趣:在其他语言中,例如 OCaml,您可以在类型定义中将某些字段标记为可变:
type point =
{ x: int;
mutable y: int;
};
这意味着您可以改变每个 point
值的 y
字段,但您永远不能改变 x
.
我也有同样的困惑。对我来说,它来自两个不同的误解。首先,我来自一种语言,其中变量(又名绑定)是对值的隐式引用。在该语言中,区分引用的变异和引用的值的变异是很重要的。其次,我认为书中 "the structure itself" 指的是实例化值,但 "the structure" 表示 specification/declaration,而不是该类型的特定值。
Rust 中的变量不同。来自 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.
因此,变量是堆栈帧的组成部分——一块内存——直接保存值。没有引用来区分值本身,没有引用 mutate。变量和值是同一块内存。
结果是重新绑定一个变量,将其更改为引用不同的内存块,这与 Rust 的内存模型不兼容。 (n.b。let x = 1; let x = 2;
创建两个变量。)
因此本书指出可变性是在 "per hunk of memory" 级别声明的,而不是作为结构定义的一部分。
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)
相反,假设您正在将一大块内存中的一个 0 更改为 5;并且该值位于 point
指定的内存中。将 "the binding is mutable" 解释为您可以改变绑定指定的内存块,包括只改变其中的一部分,例如通过设置结构字段。考虑以您描述的在 Rust 中无法表达的方式重新绑定 Rust 变量。
@m-n 的回答让我走上了正轨。这都是关于堆栈地址的!这是一个演示,让我牢记实际发生的事情。
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
到 "clobber" 现有绑定(新堆栈地址)。即使声明变量mut
也会发生这种情况,所以要小心。 - 使用
mut
重用已有的栈地址,但重新分配时不要使用let
。
这个测试揭示了一些有趣的事情:
- 如果您重新分配整个可变结构,则相当于单独分配每个成员。
- 保存结构的变量地址与第一个成员的地址相同。如果您来自 C/C++ 背景,我想这是有道理的。