当前位置:嗨网首页>书籍在线阅读

04-不安全的特征和实现

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

10.1.2 不安全的特征和实现

除了函数之外,特征也可以标记为不安全的。需要不安全的特征的原因并不是很明显。首先使用不安全的特征的动机之一是标记无法发送到线程或在线程之间共享的类型。这是通过不安全的Send和Sync特征实现的,它们也是自动化特征,这意味着它们适用于标准库中的实现的大多数类型。但是,对于某些被明确地排除在外的类型,例如Rc类型。Rc类型没有原子引用计数机制,如果要实现Sync特征并稍后在多个线程中使用,那么我们可能会在类型上得到错误的引用计数,这可能导致相关的内存过早释放和指针挂起。如果为自定义类型进行了适当的同步,那么不安全的Send和Sync使开发者只负责实现它。Send和Sync被标记为不安全的,因为对于在多个线程中进行改变的情况,若对类型的行为没有明确语义的类型,实现它们是不正确的。

将特征标记为不安全的另一个动机是封装一系列类型可能具有未定义行为的操作。如前所述,特征本质上用于指定类型实现必须包含的契约。现在,假定你的类型中包含来自外部函数接口(Foreign Function Interface,FFI)边界的实体,即包含对C字符串引用的字段,并且有很多这种类型。在这种情况下,我们可以通过不安全的特征来抽象这些类型的行为,然后可以使用一个通用接口来获取实现该不安全特征的类型。以Rust标准库中的Searcher特征为例,它是Pattern特征的关联类型。Searcher特征是一种不安全的特征,它抽象了从给定字节序列中搜索元素的概念。Searcher的一个实现者是CharSearcher结构体。将其标记为不安全的可以消除Pattern特征在有效的UTF-8字节边界上检查有效性的负担,并在字符串匹配方面获得一些性能提升。

讨论过使用不安全特征的动机之后,让我们来看看如何定义和使用不安全的特征。将特征标记为不安全的并不会让你的方法也不安全。我们可以拥有安全方法的不安全特征,反之亦然。我们也可以拥有一个安全的特征,其中可能有不安全的方法,但这并不意味着特征是不安全的。不安全的特征和不安全的函数表示方式类似,只需在它们前面加上关键字unsafe即可:

// unsafe_trait_and_impl.rs
struct MyType;
unsafe trait UnsafeTrait {
    unsafe fn unsafe_func(&self);
    fn safe_func(&self) {
        println!("Things are fine here!");
    }
}
trait SafeTrait {
    unsafe fn look_before_you_call(&self);
}
unsafe impl UnsafeTrait for MyType {
    unsafe fn unsafe_func(&self) {
        println!("Highly unsafe");
    }
}
impl SafeTrait for MyType {
    unsafe fn look_before_you_call(&self) {
        println!("Something unsafe!");
    }
}
fn main() {
    let my_type = MyType;
    my_type.safe_func();
    unsafe {
        my_type.look_before_you_call();
    }
}

在上述代码中,我们展示了不安全特征和方法的各种变体。首先,我们有两个特征声明:不安全的特征UnsafeTrait和安全的特征SafeTrait。我们还有一个名为MyType的单元结构体,它实现了上述特征。如你所见,不安全的特征需要使用关键字unsafe作为前缀来实现MyType,从而让实现者知道它们必须维护特征期望的“契约”。在MyType上的SafeTrait的第2个实现中,我们有一个需要在不安全代码块中调用的不安全方法,正如我们在main函数中看到的那样。

在接下来的内容中,我们将探讨一些语言,以及Rust如何与之交互。Rust提供的用于在语言之间互相安全地通信的所有相关API和抽象都被通俗地称为外部函数接口(Foreign Function Interface,FFI)。作为标准库的一部分,Rust为我们提供了内置的FFI抽象。基于这些的包装器程序库我们能够实现无缝的跨语言交互。