在代码中说同样的事情,我什么时候会选择以下任何一个例子?
unsafe trait MyCoolTrait {
fn method(&self) -> u8;
}
trait MyCoolTrait {
unsafe fn method(&self) -> u8;
}
opt-in builtin traits (OIBIT) RFC说:
不安全的特征是一种实现不安全的特性,因为它代表了某种可信的断言。请注意,不安全的特性使用起来非常安全。
Send
和Share
(注意:现在称为Sync
)是不安全特征的例子:实现这些特征实际上是一种断言,你的类型对于线程是安全的。
标准库Searcher
中有另一个不安全特征的例子。它说:
该特征被标记为不安全,因为
next()
方法返回的索引需要位于大海捞针中有效的utf8边界。这使得该特征的消费者能够在没有额外运行时检查的情况下切割干草堆。
不幸的是,这些段落都没有真正帮助我理解何时将整个特征标记为不安全而不是某些或所有方法是正确的。
一个函数标记为unsafe
,表示可以通过调用它来违反内存安全性。特征标记为unsafe
,表示可以通过实施来违反记忆安全。这通常是因为特征具有其他不安全代码依赖于维护的不变量,并且这些不变量不能以任何其他方式表达。
在Searcher
的情况下,这些方法本身应该是安全的。也就是说,用户不必担心他们是否正确使用Searcher
;接口合同说所有电话都是安全的。你无能为力会导致这些方法违反记忆安全。
但是,不安全的代码将调用Searcher
的方法,并且这种不安全的代码将依赖于给定的Searcher
实现来返回有效的UTF-8代码点边界上的偏移量。如果违反了这个假设,那么不安全的代码最终可能会导致内存安全违规。
换句话说:使用Searcher
s的不安全代码的正确性取决于每个Searcher
实现也是正确的。或者:错误地实现此特性允许安全代码诱导内存安全违规是无关的unsafe
代码。
那么为什么不标记方法unsafe
?因为它们根本不是不安全的!他们不做任何可能违反记忆安全的事情。 next_match
只是扫描并返回Option<(usize, usize)>
。只有当不安全的代码假定这些usize
s是被搜索字符串的有效索引时,才存在这种危险。
那么为什么不检查结果呢?因为那会慢一些。搜索代码想要快速,这意味着它希望避免冗余检查。但是这些检查不能在Searcher
界面中表达...所以相反,整个特征被标记为unsafe
,以警告任何实现它的人,在必须遵守的代码中没有陈述或强制执行的额外条件。
还有Send
和Sync
:当你不应该违反(必要时)必须处理线程的代码的期望时实现这些。允许您创建线程的代码是安全的,但只要Send
和Sync
只在它们适合的类型上实现。
经验法则是这样的:
unsafe fn method()
块中包装方法调用,请使用unsafe
。unsafe trait MyTrait
,请使用unsafe impl MyTrait
。unsafe
是Rust用户的提示:必须仔细编写不安全的代码。关键点是unsafe
应该用作双重:当作者声明特征/函数不安全时,实现者/用户需要使用unsafe
来实现/使用它。
当函数标记为unsafe
时,表示用户需要仔细使用该函数。函数作者假设用户必须保留该函数。
当特征被标记为unsafe
时,这意味着特征实现者需要仔细实施。特征要求实施者保持一定的假设。但是,不安全特征的用户可能会无意中调用特征中定义的方法。
举个具体例子,unsafe trait Searcher
要求所有Searcher
实现在调用next
时应返回有效的utf8边界。并且所有实现都标记为unsafe impl Searcher
,表示实现代码可能不安全。但作为Searcher
的用户,可以调用searcher.next()
而不将其包裹在unsafe
块中。