什么是孤儿规则
孤儿规则(Orphan rule)是Rust语言中一个重要的概念,它确保了trait实现的连贯性(Coherence)。孤儿规则的定义如下:
给定一个impl<P1..=Pn> Trait<T1..=Tn> for T0
,仅当以下至少一项为真时,这个impl
才有效:
Trait
是一个本地trait,即这个trait是在当前的crate里定义的。- 满足以下所有条件:
T0..=Tn
中至少有一个类型是本地类型。这里我们把第一个本地类型定义为Ti
。- No uncovered type parameters
P1..=Pn
may appear inT0..Ti
(excludingTi
)。即P1..=Pn
中的未覆盖的类型参数不能出现在T0..Ti
(不包括Ti)中。
简单来说,孤儿规则要求当你为某类型实现某trait时,必须要求类型或者trait至少有一个是在当前crate中定义的。你不能为第三方的类型实现第三方的trait。这个规则的存在是为了避免各种trait实现的冲突。
孤儿规则确保了trait实现的安全性和可预测性,防止了潜在的冲突和不可控的情况。例如,如果允许为任何类型实现任何trait,那么可能会有多个不同的库尝试为相同的类型实现相同的trait,从而导致冲突和混乱。孤儿规则通过限制trait实现的范围,确保了Rust代码的稳定性和可靠性。
解释孤儿规则的概念
- 如果trait是本地trait,那就直接满足孤儿规则了。本地trait的意思是这个trait是在当前的crate里定义的
- 如果trait是不是本地trait。那就要同时满足两个条件:
- 类型T0..=Tn要至少有一个类型是本地类型。当然,一个impl可能会有多个本地类型,我们第一个本地类型定义为Ti。
- 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和类型:
- Crate A定义了一个trait。
- Crate B定义了一个类型。
- 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);
}
}
评论区
写评论还没有评论