Rust 中的所有权跟踪:Box<T>(堆)和 T(堆栈)之间的区别
Ownership tracking in Rust: Difference between Box<T> (heap) and T (stack)
使用 Rust 编程语言进行实验,我发现编译器能够非常准确地跟踪堆栈上某个结构的 字段的移动(它确切地知道 what 字段已移动)。
但是,当我将结构的一部分放入 Box
中(即将其放入堆中)时,编译器不再能够确定取消引用后 发生的所有事情的字段级移动 的框。它将假定整个结构 "inside the box" 已经移动。让我们先来看一个所有东西都在栈上的例子:
struct OuterContainer {
inner: InnerContainer
}
struct InnerContainer {
val_a: ValContainer,
val_b: ValContainer
}
struct ValContainer {
i: i32
}
fn main() {
// Note that the whole structure lives on the stack.
let structure = OuterContainer {
inner: InnerContainer {
val_a: ValContainer { i: 42 },
val_b: ValContainer { i: 100 }
}
};
// Move just one field (val_a) of the inner container.
let move_me = structure.inner.val_a;
// We can still borrow the other field (val_b).
let borrow_me = &structure.inner.val_b;
}
现在是 相同的示例,但有一个小改动:我们将 InnerContainer
放入一个盒子 (Box<InnerContainer>
)。
struct OuterContainer {
inner: Box<InnerContainer>
}
struct InnerContainer {
val_a: ValContainer,
val_b: ValContainer
}
struct ValContainer {
i: i32
}
fn main() {
// Note that the whole structure lives on the stack.
let structure = OuterContainer {
inner: Box::new(InnerContainer {
val_a: ValContainer { i: 42 },
val_b: ValContainer { i: 100 }
})
};
// Move just one field (val_a) of the inner container.
// Note that now, the inner container lives on the heap.
let move_me = structure.inner.val_a;
// We can no longer borrow the other field (val_b).
let borrow_me = &structure.inner.val_b; // error: "value used after move"
}
我怀疑它与堆栈的性质与堆的性质有关,其中前者是静态的(至少每个堆栈帧),后者是动态的。也许由于某些原因我不能 articulate/identify 足够好。
在抽象中,堆栈上的 struct
是 种 只是一堆具有通用名称的变量。编译器知道这一点,可以将结构分解为一组独立的堆栈变量。这让它可以独立跟踪每个字段的移动。
它不能用 Box
或任何其他类型的自定义分配来做到这一点,因为编译器不控制 Box
es。 Box
只是标准库中的一些代码,不是语言的固有部分。 Box
无法推理自身的不同部分突然变得无效。当需要销毁 Box
时,它的 Drop
实现只知道销毁 一切 .
换句话说:在堆栈上,编译器处于完全控制之下,因此可以做一些奇特的事情,比如分解结构并将它们零碎地移动。一旦自定义分配进入画面,所有的赌注都被取消了,编译器不得不后退并停止试图变得聪明。
使用 Rust 编程语言进行实验,我发现编译器能够非常准确地跟踪堆栈上某个结构的 字段的移动(它确切地知道 what 字段已移动)。
但是,当我将结构的一部分放入 Box
中(即将其放入堆中)时,编译器不再能够确定取消引用后 发生的所有事情的字段级移动 的框。它将假定整个结构 "inside the box" 已经移动。让我们先来看一个所有东西都在栈上的例子:
struct OuterContainer {
inner: InnerContainer
}
struct InnerContainer {
val_a: ValContainer,
val_b: ValContainer
}
struct ValContainer {
i: i32
}
fn main() {
// Note that the whole structure lives on the stack.
let structure = OuterContainer {
inner: InnerContainer {
val_a: ValContainer { i: 42 },
val_b: ValContainer { i: 100 }
}
};
// Move just one field (val_a) of the inner container.
let move_me = structure.inner.val_a;
// We can still borrow the other field (val_b).
let borrow_me = &structure.inner.val_b;
}
现在是 相同的示例,但有一个小改动:我们将 InnerContainer
放入一个盒子 (Box<InnerContainer>
)。
struct OuterContainer {
inner: Box<InnerContainer>
}
struct InnerContainer {
val_a: ValContainer,
val_b: ValContainer
}
struct ValContainer {
i: i32
}
fn main() {
// Note that the whole structure lives on the stack.
let structure = OuterContainer {
inner: Box::new(InnerContainer {
val_a: ValContainer { i: 42 },
val_b: ValContainer { i: 100 }
})
};
// Move just one field (val_a) of the inner container.
// Note that now, the inner container lives on the heap.
let move_me = structure.inner.val_a;
// We can no longer borrow the other field (val_b).
let borrow_me = &structure.inner.val_b; // error: "value used after move"
}
我怀疑它与堆栈的性质与堆的性质有关,其中前者是静态的(至少每个堆栈帧),后者是动态的。也许由于某些原因我不能 articulate/identify 足够好。
在抽象中,堆栈上的 struct
是 种 只是一堆具有通用名称的变量。编译器知道这一点,可以将结构分解为一组独立的堆栈变量。这让它可以独立跟踪每个字段的移动。
它不能用 Box
或任何其他类型的自定义分配来做到这一点,因为编译器不控制 Box
es。 Box
只是标准库中的一些代码,不是语言的固有部分。 Box
无法推理自身的不同部分突然变得无效。当需要销毁 Box
时,它的 Drop
实现只知道销毁 一切 .
换句话说:在堆栈上,编译器处于完全控制之下,因此可以做一些奇特的事情,比如分解结构并将它们零碎地移动。一旦自定义分配进入画面,所有的赌注都被取消了,编译器不得不后退并停止试图变得聪明。