我在标准库中查看
String
,有很多像这样的unsafe
代码:
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn remove(&mut self, idx: usize) -> char {
let ch = match self[idx..].chars().next() {
Some(ch) => ch,
None => panic!("cannot remove a char from the end of a string"),
};
let next = idx + ch.len_utf8();
let len = self.len();
unsafe {
ptr::copy(self.vec.as_ptr().add(next), self.vec.as_mut_ptr().add(idx), len - next);
self.vec.set_len(len - (next - idx));
}
ch
}
为什么标准库中有这么多
unsafe
代码?语言如何仍然安全?
这里有一个误解,认为使用
unsafe
会自动变得 unsound 并且会导致内存错误。它不是。事实上,即使在 unsafe
代码块中,你也不允许引起内存错误;如果这样做,那么代码将表现出未定义的行为,并且整个程序定义不明确。 unsafe
的目的是允许编译器无法确保实际上安全的事情。开发人员有责任通过了解使用 unsafe
语法、函数和其他项目所需的安全要求来确保代码不会调用未定义的行为。
编写和使用
unsafe
函数的设计理念是,如果某些参数或情况可能导致函数表现出未定义的行为,则必须对其进行标记 unsafe
并应记录安全参数和情况是什么。然后,调用者必须在 unsafe
块内遵守此文档。这种设计理念的另一面是,如果一个函数not标记为unsafe
,那么no可能的参数或情况可能会导致未定义的行为。
在这种情况下,在内存中移动字节并不总是安全的,因此您必须使用
unsafe
来调用 ptr::copy
。然而,方法 .remove()
没有标记为 unsafe
,因此,如果 Rust 标准库的开发人员已经完成了他们的工作,那么 unsafe
块中发生的任何事情都必须是安全的,而且我确信他们已经做到了。您可以看到任何可能的输入都经过了边界检查,并且正在 copy
的内容位于已分配的块内。可能导致未定义行为的唯一方法是在调用此函数之前已经存在未定义的行为或损坏的不变量。
如果不使用
unsafe
,则无法构建 Rust 标准库。计算机所基于的底层手动内存管理本质上充满了内存枪,但是您可以通过保证这些“不安全”操作的安全来构建它们。
某些
unsafe
'ty 是必需的,但其他实例只是出于性能原因。安全抽象可能需要许多检查来确保它们是安全的,特别是如果涉及任何类型的动态性,但如果现有的不变量编码正确,那么使用 unsafe
可以避免这些检查,同时仍然安全。在此函数中,仅依靠其他 self.vec
方法(在某些时候内部会有 unsafe
)就可以完全安全地完成它,但它可能包含完全不必要的额外边界检查。标准库期望以尽可能少的开销运行,同时保持安全(当然,除非该函数被标记为unsafe
)。