别再被孤儿规则搞崩溃了!一文揭秘Rust编程秘密

科技   2024-10-31 12:46   加拿大  

什么是孤儿规则

孤儿规则(Orphan rule)是Rust语言中一个重要的概念,它确保了trait实现的连贯性(Coherence)。孤儿规则的定义如下:

给定一个impl<P1..=Pn> Trait<T1..=Tn> for T0,仅当以下至少一项为真时,这个impl才有效:

  1. Trait是一个本地trait,即这个trait是在当前的crate里定义的。
  2. 满足以下所有条件:
  • T0..=Tn中至少有一个类型是本地类型。这里我们把第一个本地类型定义为Ti
  • No uncovered type parameters P1..=Pn may appear in T0..Ti (excluding Ti)。即P1..=Pn中的未覆盖的类型参数不能出现在T0..Ti(不包括Ti)中。

简单来说,孤儿规则要求当你为某类型实现某trait时,必须要求类型或者trait至少有一个是在当前crate中定义的。你不能为第三方的类型实现第三方的trait。这个规则的存在是为了避免各种trait实现的冲突。

孤儿规则确保了trait实现的安全性和可预测性,防止了潜在的冲突和不可控的情况。例如,如果允许为任何类型实现任何trait,那么可能会有多个不同的库尝试为相同的类型实现相同的trait,从而导致冲突和混乱。孤儿规则通过限制trait实现的范围,确保了Rust代码的稳定性和可靠性。

解释孤儿规则的概念

  • 如果trait是本地trait,那就直接满足孤儿规则了。本地trait的意思是这个trait是在当前的crate里定义的
  • 如果trait是不是本地trait。那就要同时满足两个条件:
  1. 类型T0..=Tn要至少有一个类型是本地类型。当然,一个impl可能会有多个本地类型,我们第一个本地类型定义为Ti。
  2. P1..=Pn中的未覆盖的类型参数不能出现在 T0..Ti (不包括 Ti )中。 这一条是比较难理解的,如果理解了这一条,我们就理解了孤儿规则了。

什么是未覆盖的类型呢?

规范里的定义是:A type which does not appear as an argument to another type. 一个类型如果它不是做为其它类型的参数而出现,那它就是未覆盖的类型。 举个例子,类型T就是未覆盖的类型,但是Vec中的T就不是未覆盖的类型了,就是覆盖了的类型,因为它做为Vec的参数出现了,不是单独出现的。

我们再来看看规范中的说明:P1..=Pn中的未覆盖的类型参数不能出现在 T0..Ti(不包括 Ti ) 中。需要注意是 T0..Ti,规范中可没要求是T0..=Tn,这一点要注意。

例子详解

在不同crate中定义trait和类型:

  1. Crate A定义了一个trait。
  2. Crate B定义了一个类型。
  3. Crate C尝试为Crate B中的类型实现Crate A中的trait。

CrateA项目中:

// src/lib.rs
pub trait Greet {
fn greet(&self);
}

CrateB项目中

// src/lib.rs
pub struct Person {
   pub name: String,
}

CrateC项目中: 实现

// src/lib.rs
extern crate trait_crate;
extern crate type_crate;
use trait_crate::Greet;
use type_crate::Person;
// 由于孤儿规则,这将无法编译
impl Greet for Person {
  fn greet(&self) {
    println!("你好,我的名字是{}"self.name);
  }
}

上述代码,A定义了Greet trait。B定义了Person结构体。C尝试为Person结构体实现Greet trait。孤儿规则阻止了C为Person结构体实现Greet trait,因为trait和类型都定义在C之外。

如何解决

1. 为外部类型实现本地trait:

你可以在你的crate中定义一个新的trait,并为其外部类型实现该trait。Crate C: impl_crate

extern crate type_crate;
use type_crate::Person;
// 定义一个新的trait去实现Person,就可以使用Person这个结构体的属性了。
pub trait Farewell {
fn farewell(&self);
}
impl Farewell for Person {
   fn farewell(&self) {
     println!("Goodbye from {}"self.name);
   }
}

为本地类型实现外部trait:你可以为在你的crate中定义的类型实现一个外部trait。和上面的反过来,在自己的包里面定一个新的结构体然后实现trait Greet。

extern crate trait_crate;
use trait_crate::Greet;
pub struct LocalPerson {
  pub name: String,
}
impl Greet for LocalPerson {
  fn greet(&self) {
     println!("Hello, my name is {}"self.name);
  }
}


Rust语言中文社区
Rust官方及社区最新信息搜集、文章推送,教程学习,技巧分享,社区交流。信息来源是整个全球Rust社区。
 最新文章