Function std::mem::forget

1.0.0 (const: 1.46.0) · source ·
pub const fn forget<T>(t: T)
Expand description

获取所有权和 “forgets” 值,而不运行其析构函数。

该值管理的任何资源 (例如堆内存或文件句柄) 将永远处于无法访问的状态。但是,它不能保证指向该内存的指针将保持有效。

  • 如果要泄漏内存,请参见 Box::leak
  • 如果要获取内存的裸指针,请参见 Box::into_raw
  • 如果要正确处理某个值,请运行其析构函数,请参见 mem::drop

Safety

forget 没有标记为 unsafe,因为 Rust 的安全保证不包括析构函数将始终运行的保证。 例如,程序可以使用 Rc 创建引用循环,或调用 process::exit 退出而不运行析构函数。 因此,从安全代码允许 mem::forget 不会从根本上改变 Rust 的安全保证。

也就是说,通常不希望泄漏诸如内存或 I/O 对象之类的资源。 在某些特殊的用例中,对于 FFI 或不安全代码提出了需求,但即使这样,通常还是首选 ManuallyDrop

因为允许忘记一个值,所以您编写的任何 unsafe 代码都必须允许这种可能性。您不能返回值,并且期望调用者一定会运行该值的析构函数。

Examples

mem::forget 的规范安全使用是为了避免 Drop trait 实现的值的析构函数。例如,这将泄漏 File,即 回收变量占用的空间,但不要关闭底层系统资源:

use std::mem;
use std::fs::File;

let file = File::open("foo.txt").unwrap();
mem::forget(file);
Run

当底层资源的所有权先前已转移到 Rust 之外的代码时 (例如,通过将原始文件描述符传输到 C 代码),这很有用。

ManuallyDrop 的关系

虽然 mem::forget 也可以用于转移 内存 所有权,但是这样做很容易出错。 应改用 ManuallyDrop。例如,考虑以下代码:

use std::mem;

let mut v = vec![65, 122];
// 使用 `v` 的内容构建 `String`
let s = unsafe { String::from_raw_parts(v.as_mut_ptr(), v.len(), v.capacity()) };
// 泄漏 `v`,因为它的内存现在由 `s` 管理
mem::forget(v);  // 错误 - v 无效,不得将其传递给函数
assert_eq!(s, "Az");
// `s` 被隐式丢弃,并释放其内存。
Run

上面的示例有两个问题:

  • 如果在 String 的构造与 mem::forget() 的调用之间添加了更多代码,则其中的 panic 将导致双重释放,因为 vs 均处理同一内存。
  • 调用 v.as_mut_ptr() 并将数据所有权传输到 s 之后,v 值无效。 即使将值仅移动到 mem::forget (不会检查它),某些类型对其值也有严格的要求,以使它们在悬垂或不再拥有时无效。 以任何方式使用无效值,包括将它们传递给函数或从函数中返回它们,都构成未定义的行为,并且可能会破坏编译器所做的假设。

切换到 ManuallyDrop 可以避免两个问题:

use std::mem::ManuallyDrop;

let v = vec![65, 122];
// 在将 `v` 解开为原始零件之前,请确保它不会丢弃掉!
let mut v = ManuallyDrop::new(v);
// 现在解开 `v`。这些操作不能 panic,因此不会有泄漏。
let (ptr, len, cap) = (v.as_mut_ptr(), v.len(), v.capacity());
// 最后,构建一个 `String`。
let s = unsafe { String::from_raw_parts(ptr, len, cap) };
assert_eq!(s, "Az");
// `s` 被隐式丢弃,并释放其内存。
Run

ManuallyDrop 强大地防止双重释放,因为我们在做任何其他事情之前禁用了 v 的析构函数。 mem::forget() 不允许这样做,因为它消耗了它的参数,迫使我们只有在从 v 中提取我们需要的任何东西后才能调用它。 即使在 ManuallyDrop 的构建与字符串的构建之间引入了 panic (这在所示的代码中不能发生),也将导致泄漏,而不是双重释放。 换句话说,ManuallyDrop 在泄漏的一侧发生错误,而不是在 (两次) 丢弃的一侧发生错误。

同样,ManuallyDrop 避免了在将所有权转让给 s 之后必须使用 “touch” v 的情况-完全避免了与 v 交互以处置它而不运行其析构函数的最后一步。