我偶然发现了以下场景:
fn compute_values(value: &mut String) {
// the first computation should always work:
let computed_value = String::from("computed value");
// override the default String with the computed value:
*value = computed_value;
// some more code that returns a Result with values or an error message
}
fn main() {
let mut value = String::from("default value");
compute_values(&mut value);
println!("value: {value}")
}
这段代码按照我的预期使用 my 进行编译rustc 1.74.1 (a28077b28 2023-12-04)
并输出value: computed value
,但问题是,该代码是否泄漏内存。
据我了解,*value = computed_value;
进入并且它不再可用(rust编译器确认了这一点,我之后不能computed_value
。)并且在函数作用域结束时不会取消分配。由于这一行有效地将 a 分配给 a ,我猜测所有结构字段都被复制到原始结构的位置。但是,一旦原始包含的引用被覆盖,就无法取消分配 old指向的原始堆内存。由于我没有使用任何不安全的 Rust,并且 Rust 似乎是内存安全的,所以我只能想象原始内存在分配发生之前会自动取消分配,但我找不到有关该主题的任何信息。*value
println!("{computed_value}");
struct String
struct String
&str
&str
我发现以下文章没有回答我的问题:
std::mem::take
名为和std::mem::swap
的答案std::mem::replace
使我相信在这种情况下使用其中之一是必要的。
我还检查了 Rust 官方文档,但没有找到任何内容。
我的问题是:
- 这段代码是否存在内存泄漏?
- 即使替换可变引用的值,非不安全的 Rust 是否应该是内存安全的?
- 是否有关于取消引用分配的任何(官方)文档?
PS:理论上,Rust 可能会进行优化let mut value = String::from("default value");
,使其String
value
指向字符串常量而不是堆上的位置。这样就不需要取消分配内存。但是,当您从 stdin 读取 a 时,整个事情也会起作用value
,然后整个事情必须放置在堆上。
回答
在阅读了 @cyqsimon 的答案(顺便说一句,这很棒)后,我明白这个问题实际上与引用无关,而是在替换值时删除值。有了这个新的见解,我发现这篇 Rustonomicon 文章完全回答了我的问题,并且还讨论了当变量仅被初始化并因此在某些条件下被删除时会发生什么。
不它不是。分配后,旧
String
结构不再属于任何内容,因此它会自动删除(因此会被正确清理)。Rust 的内存安全保证了很多事情——不泄漏内存不是其中之一。事实上,有一个明确的
Box::leak
函数可以完全按照您的想法进行操作。在这种特定情况下,我认为它可能会因未正确实现的Drop
特征而泄漏内存,尽管通常这不是您需要担心的事情。这种“删除旧值”行为是赋值操作的一部分,而不是取消引用操作的一部分。它被记录在这里。(感谢@user2722968)
从 开始
std::mem::{swap, take, replace}
,这些都是安全抽象,旨在满足安全 Rust 的所有值必须始终有效的规定。如果没有它们,如果你想从可变引用中移出一个值,从语义上讲,就会出现变量绑定未定义的情况,这在安全 Rust 中也是不允许的。如果你看一下这些函数的源代码(它们相当简单),它们所做的只是memcopy。在这种情况下,如果您想
String::from("default value")
在重新分配后保留而不是丢弃该值,则可以使用std::mem::replace
。与这个问题无关,但我不相信 Rust 上实现了小字符串优化(事实上,它可能无法在 Rust 中实现)。我记得杰克·奥康纳在他的精彩视频中简短地谈到了为什么会出现这种情况。