从关于如何改变结构字段的Rust 书中:
let mut point = Point { x: 0, y: 0 };
point.x = 5;
然后:
可变性是绑定的属性,而不是结构本身的属性。
这对我来说似乎违反直觉,因为point.x = 5
看起来我并没有重新绑定变量point
。有没有办法解释这一点,所以它更直观?
我可以解决这个问题的唯一方法是“想象”我正在重新绑定到具有不同值point
的原始副本(甚至不确定这是否准确)。Point
x
这对我来说似乎违反直觉,因为 point.x = 5 看起来我并没有重新绑定变量点。有没有办法解释这一点,所以它更直观?
所有这一切都是说,某事物是否可变取决于let
变量的 - 语句(绑定),而不是类型或任何特定字段的属性。
在示例中,point
它的字段是可变的,因为point
是在语句中引入的let mut
(与简单let
语句相反),而不是因为Point
一般类型的某些属性。
作为对比,为了说明为什么这很有趣:在其他语言中,例如 OCaml,您可以在类型定义中将某些字段标记为可变:
type point =
{ x: int;
mutable y: int;
};
这意味着你可以改变y
每个point
值的字段,但你永远不能改变x
。
这里的“绑定”不是动词,而是名词。你可以说在 Rust 中绑定是变量的同义词。因此,您可以像这样阅读该段落
可变性是变量的属性,而不是结构本身的属性。
现在,我想,应该很清楚了——你将变量标记为可变的,这样你就可以修改它的内容了。
我也有同样的困惑。对我来说,它来自两个不同的误解。首先,我来自一种语言,其中变量(也称为绑定)是对值的隐式引用。在那种语言中,重要的是区分改变引用和改变被引用的值。其次,我认为书中的“结构本身”是指实例化的值,但“结构”是指规范/声明,而不是该类型的特定值。
Rust 中的变量是不同的。从参考:
变量是堆栈帧的组成部分...
局部变量(或堆栈局部分配)直接保存一个值,在堆栈内存中分配。该值是堆栈帧的一部分。
所以变量是堆栈帧的一个组件——一块内存——直接保存值。没有引用来区分值本身,也没有引用 mutate。变量和值是同一块内存。
结果是重新绑定变量以将其更改为引用不同的内存块与 Rust 的内存模型不兼容。(nblet x = 1; let x = 2;
创建两个变量。)
所以这本书指出,可变性是在“每块内存”级别声明的,而不是作为结构定义的一部分。
我可以解决这个问题的唯一方法是“想象”我将点重新绑定到具有不同 x 值的原始点的副本(甚至不确定这是否准确)
相反,假设您正在将一大块内存中的一个 0 更改为 5;并且该值驻留在由 指定的内存中point
。将“绑定是可变的”解释为意味着您可以改变由绑定指定的内存块,包括仅改变其中的一部分,例如通过设置结构字段。考虑以您描述为在 Rust 中不可表达的方式重新绑定 Rust 变量。
@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
。这个测试揭示了一些有趣的事情: