Published on

型变三法则:协变 vs 逆变 vs 不变

1. 协变(Covariance)

定义:如果 AB 的子类型,那么 F<A> 也是 F<B> 的子类型。
方向相同A → BF<A> → F<B>)。
典型例子:不可变引用 &TBox<T>Vec<T>

🌰 例子:动物和狗

trait Animal { fn sound(&self); }
struct Dog;
impl Animal for Dog { fn sound(&self) { println!("Woof!"); } }

// 协变:&Dog 可以当作 &Animal 使用
fn make_sound(animal: &dyn Animal) {
    animal.sound();
}

fn main() {
    let dog = Dog;
    make_sound(&dog); // ✅ &Dog 协变为 &Animal
}

为什么安全?
因为 &T 是只读的,不可能通过 &Animal 修改数据,所以协变是安全的。


2. 逆变(Contravariance)

定义:如果 AB 的子类型,那么 F<B>F<A> 的子类型。
方向相反A → BF<B> → F<A>)。
典型例子:函数参数(如 fn(T))。

🌰 例子:事件处理器

trait Event {}
struct ClickEvent;
impl Event for ClickEvent {}

// 逆变:fn(Event) 是 fn(ClickEvent) 的子类型
fn handle_event(event: impl Event) {
    println!("Event handled!");
}

// 接受一个处理 ClickEvent 的函数
fn set_click_handler(handler: fn(ClickEvent)) {
    handler(ClickEvent); // 调用时传入 ClickEvent
}

fn main() {
    // ✅ 将 fn(Event) 传给需要 fn(ClickEvent) 的函数
    set_click_handler(handle_event); // 逆变:父类型 => 子类型
}

为什么安全?
因为 handler 只需要处理 ClickEvent,而 handle_event 能处理所有 Event,所以传入更通用的函数是安全的。


3. 不变(Invariance)

定义F<A>F<B> 没有任何子类型关系,即使 AB 有。
典型例子:可变引用 &mut TCell<T>

🌰 例子:可变引用的危险

trait Animal {}
struct Dog;
struct Cat;
impl Animal for Dog {}
impl Animal for Cat {}

// 尝试让 &mut Dog 协变为 &mut Animal(会失败!)
fn train_animal(animal: &mut dyn Animal) {
    // 如果允许协变,这里可能把 Cat 写进 Dog 的引用!
}

fn main() {
    let mut dog = Dog;
    train_animal(&mut dog); // ❌ 编译错误(Rust 禁止)
}

为什么不安全?
如果允许 &mut Dog 当作 &mut Animal 使用,train_animal 可能会把 Cat 赋值给 dog,导致类型混乱!


对比表

特性方向例子Rust 中的典型场景
协变A → B ⇒ F<A> → F<B>&Dog 当作 &Animal&T, Box<T>, Vec<T>
逆变A → B ⇒ F<B> → F<A>fn(Event) 传给 fn(ClickEvent)函数参数 (fn(T))
不变无关系&mut Dog&mut Animal&mut T, Cell<T>

关键记忆点

  1. 协变:子类型关系正向传递(安全场景:只读数据)。
  2. 逆变:子类型关系反向传递(函数参数,保证输入足够具体)。
  3. 不变禁止传递(可变数据必须严格匹配类型,防止错误写入)。

在 Rust 中,编译器会自动推断这些规则,你只需要在写泛型或生命周期复杂的代码时注意它们的影响!

THE END