静态类型语言黑话-协变

Rust 中的协变和反变

  • 协变(Covariant):如果 AB 的子类型(A <: B),那么 Container<A> 也是 Container<B> 的子类型。
  • 逆变/反变(Contravariant):如果 AB 的子类型(A <: B),那么 Function<B> 反向成为 Function<A> 的子类型。

Rust 中的协变

在 Rust 里,如果 T 的生命周期 'a'b 的子生命周期('a <: 'b),那么 &'a T 也是 &'b T 的子类型。这意味着 &'a T协变的(covariant)

1
2
3
4
5
6
7
8
struct Container<'a, T>(&'a T);

fn co_variant<'short, 'long>(long: Container<'long, i32>) -> Container<'short, i32>
where
'long: 'short, // 'long 生命周期比 'short 长
{
long // 允许转换,因为 Container<'a, T> 对 'a 是协变的
}

协变的意义

  • 允许生命周期缩短(即更“短暂”的借用可以替代“长久”的借用)。
  • 常见于不可变引用,因为不可变数据不会被修改,所以缩短引用是安全的。

Rust 中 *mut 是 invariant ,不能被子类型化,而 NonNull<T> 是协变的。其实现方式,是通过转换为 *const T 的方式,而 *const T 是协变的。

&'a mut T 对于 'a 来说是协变( covariant )的,但是对于 T 是不变的( invariant )。

1
2
3
4
5
6
7
8
9
10
pub struct NonNull<T> {
pointer: *const T,
}

impl<T> NonNull<T> {
pub unsafe fn new_unchecked(ptr: *mut T) -> Self {
// SAFETY: the caller must guarantee that `ptr` is non-null.
unsafe { NonNull { pointer: ptr as *const T } }
}
}

另外,如果使用 NonNull<T> 作为一个类的成员,为了不受到一些间接因素的影响,类型擦除或者其它间接的什么原因,使用一个 _boo: PhantomData<T> 成员,来告诉编译器,NonNull 中一定是一个 T 类型。

Rust 中的反变

在 Rust 里,函数参数的生命周期是逆变(contravariant) 的。这意味着,如果 'a <: 'b,那么 fn(&'b i32) -> () 可以赋值给 fn(&'a i32) -> ()

1
2
3
4
5
6
fn contravariant_example<'short, 'long>(func: fn(&'short i32)) -> fn(&'long i32)
where
'long: 'short, // 'long 生命周期比 'short 长
{
func // 允许转换,因为函数参数的生命周期是逆变的
}

反变的意义

  • 防止不安全的生命周期扩展(即不能让短生命周期的数据被长生命周期的函数引用)。
  • 常见于函数指针或闭包,因为调用方可以传递更具体的参数类型,而不影响调用。

C++ 中的协变和反变

在 C++,协变和反变主要体现在继承和模板的上下界规则,特别是 返回值(协变)和参数(逆变) 的继承关系。

C++ 中的协变

如果派生类 Derived 继承了 Base,那么返回 Derived* 的函数可以覆盖返回 Base* 的函数(返回值是协变的)。

1
2
3
4
5
6
7
8
9
class Base {
public:
virtual Base* get() { return this; }
};

class Derived : public Base {
public:
Derived* get() override { return this; } // 允许,返回值是协变的
};

协变的意义

  • 允许更具体的返回类型,使得调用者可以获得更具体的对象,而不需要额外的类型转换。

C++ 中的反变

在 C++ 中,函数参数是逆变的。如果 Derived 继承自 Base,则 void func(Derived*) 不能替代 void func(Base*),但反过来可以。

1
2
3
4
5
6
7
8
9
10
class Base {};
class Derived : public Base {};

void acceptBase(Base* b) {}

void acceptDerived(Derived* d) {}

using FuncType = void (*)(Derived*);

FuncType funcPtr = acceptBase; // ❌ 错误,参数类型是逆变的

反变的意义

  • 防止子类对象被错误地当作父类处理,因为子类可能有额外的字段或行为。

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!