跳过正文

类型协变:type coercion

··4440 字
Rust
目录
rust-lang - 这篇文章属于一个选集。
§ 12: 本文

1 Subtyping and Variance
#

参考:

  1. https://doc.rust-lang.org/reference/subtyping.html
  2. https://doc.rust-lang.org/nomicon/subtyping.html

Subtyping 是隐式操作(类似的隐式操作还有 type coercion), 用于 type checking 和 infrerence, 它们都指的是 lifetime 之间的关系(Rust 的自定义类型之间没有 subtype 语义),包含两种类型:

  1. 生命周期更长的 lifetime 是更短的子类型:
    1. ‘b: ‘a 表示 ‘b 的生命周期比 ‘a 长,在需要 ‘a 的地方都可以传入 ‘b;
    2. ‘static 的生命周期比其它的都长,在需要 ‘a 的地方,可以传入 ‘static;
  2. HRTB 的 lifetime 是其它任意 lifetime 的子类型:

注:supertrait 用于约束 Self 必须实现多个 trait + ’lifetime, 并不表示 trait 之间的 subtype 关系。

// HRTB 支持闭包
fn use_closure<F>(f: F) where F: for <'a> Fn(&'a str) -> &'a str,
{
    let s = "example";
    let result = f(s); // f 的 HRBT lifetime 是任意其它 lifetime 的子类型
    println!("{}", result);
}

// HRTB 支持函数指针
fn use_fn_ptr(f: for<'a> fn(&'a str) -> &'a str) {
    let s = "example";
    let result = f(s); // f 的 HRBT lifetime 是任意其他 lifetime 的子类型
    println!("{}", result);
}

let subtype: &(for<'a> fn(&'a i32) -> &'a i32) = &((|x| x) as fn(&_) -> &_);
let supertype: &(fn(&'static i32) -> &'static i32) = subtype;

let subtype: &(dyn for<'a> Fn(&'a i32) -> &'a i32) = &|x| x;
let supertype: &(dyn Fn(&'static i32) -> &'static i32) = subtype;

let subtype: &(for<'a, 'b> fn(&'a i32, &'b i32))= &((|x, y| {}) as fn(&_, &_));
let supertype: &for<'c> fn(&'c i32, &'c i32) = subtype;

Variance 指在传参和赋值时,类型之间如果存在 subtype 关系,则 Rust 隐式自动转换,例如 T 是 U 的子类型 ,则 T 的值会被自动转换为 U 的值:

  • covariant 协变: 如果 T 是 U 的 subtype, 则 F<T> 是 F<U> 的子类型;
  • contravariant 逆变: 如果 T 是 U 的 subtype, 则 F<U> 是 F<T> 的子类型;
  • invariant 不可变: 没有子类型的关系;

Rust 支持的 Variance 列表:

Type Variance in ‘a Variance in T
&‘a T covariant, ‘a 可以有子类型 covariant,T 可以有子类型
&‘a mut T covariant, ‘a 可以有子类型 invariant, T 不可变
*const T covariant,T 可以有子类型
*mut T invariant
[T] and [T; N] covariant, T 可以有子类型
fn() -> T covariant, T 可以有子类型
fn(T) -> () contravariant, 可以传入 T 的父类型
std::cell::UnsafeCell<T> invariant,T 不可变
std::marker::PhantomData<T> covariant,T 可以有子类型
dyn Trait<T> + ‘a covariant, ‘a 可以有子类型 invariant, T 不可以变
  • T 代表一个类型,实际可能包含 lifetime, 比如 fn() -> T 中的 T 可能是 &‘a String,

这时可以使用返回 &‘static String 的函数指针, 因为 ‘static 是 ‘a 的子类型;

  • fn(T) -> () 中的 T 是逆变,对于 fn(&‘static String) 则可以传入 fn(&‘a String)。
  • Vec<T> 等容器类型,以及 Box<T> 类型,都是 covariant,但 Cell<T> 是 invariant;
  • UnsafeCell<T>/Cell<T> 等内部可变性类型和 &mut T 类型一致,T 不可变。

示例:

  • A &'long T coerces to a &'short T :另外,T 也可以协变到 U;
  • A &'long mut T coerces to a &'short mut T :虽然 T 不可变,但 lifetime 可变;
  • A &'medium &'long U coerces to a &'short &'short U
  • A &'medium mut &'long mut U coerces to a &'short mut &'long mut U…, but not to a &‘short mut &‘short mut U
  • Cell<T> and RefCell<T> are also invariant in T.
  • T: dyn Trait<U> 或 T: Trait<U> , U becomes invariant because it’s a type parameter of the trait. If your U resolves to &'x V, the lifetime 'x will be invariant too.
// 由于输入参数是 &mut T 类型,所以在赋值时 val 对应 T 的类型是不可变的,必须和 input 的 T 类型一
// 致。
fn assign<T>(input: &mut T, val: T) {
    *input = val;
}

fn main() {
    let mut hello: &'static str = "hello";
    {
        let world = String::from("world");
        // 错误:传入的参数类型是 &mut &'static str,所以 T 的类型必须是 &'static str, 但是 &world
        // 的类型是 &'world str,所以编译错误。
        assign(&mut hello, &world);
    }
    println!("{hello}");
}

// error[E0597]: `world` does not live long enough
//   --> src/main.rs:9:28
//    |
// 6  |     let mut hello: &'static str = "hello";
//    |                    ------------ type annotation requires that `world` is borrowed for `'static`
// ...
// 9  |         assign(&mut hello, &world);
//    |                            ^^^^^^ borrowed value does not live long enough
// 10 |     }
//    |     - `world` dropped here while still borrowed


// Box<T> 的 T是可以协变的,&'static 是 &'b 的 subtype,所以可以赋值:
let hello: Box<&'static str> = Box::new("hello");
let mut world: Box<&'b str>;
// 对象所有权转移时可以更改可变性
world = hello;

fn get_str() -> &'a str;
fn get_static() -> &'static str;

thread_local! {
    pub static StaticVecs: RefCell<Vec<&'static str>> = RefCell::new(Vec::new());
}

fn store(input: &'static str) {
    StaticVecs.with_borrow_mut(|v| v.push(input));
}

fn demo<'a>(input: &'a str, f: fn(&'a str)) {
    f(input);
}

fn main() {
    // "hello" 是 'static 的, 所以 store() 可以使用它
    demo("hello", store);

    {
        let smuggle = String::from("smuggle");

        // 错误: `fn(&'static str)` 不是 `fn(&'a str)` 的子类型.(函数指针是反向协变)
        demo(&smuggle, store);
    }
}

自定义 struct/enum/union 的 variance 取决于各 field 的 variance:

  • 如果 parameter A 在各 field 的使用都是 covariant, 则整体对 A 是 covariant;
  • 如果 parameter A 在各 field 的使用都是 contravariant, 则整体对 A 是 contravariant;
  • 否则,整体对 A 是 invariant;

如下面的 struct 的 ‘a 和 T 是 covariant 可变的, 而 ‘b, ‘c 和 U 是 invariant 不可变的:

use std::cell::UnsafeCell;

struct Variance<'a, 'b, 'c, T, U: 'a> {
    x: &'a U,               // This makes `Variance` covariant in 'a, and would make it covariant
                            // in U, but U is used later, 由于 U 在多个 field 中使用, 所以 U 是不
                            // 可变的

    y: *const T,            // Covariant in T

    z: UnsafeCell<&'b f64>, // Invariant in 'b, 因为 std::cell::UnsafeCell<T> 中的 T 是不可变的.

    w: *mut U,              // Invariant in U, makes the whole struct invariant, 部分字段不可变时,
                            // 整个 struct 不可变.

    f: fn(&'c ()) -> &'c () // Both co- and contravariant, makes 'c invariant in the struct.
}

在 struct/enum/uion field 之外, variance 是单独计算的, 长的 lifetime 类型值可以赋值给短的 liftime 类型值:

fn generic_tuple<'short, 'long: 'short>(
    // 'long is used inside of a tuple in both a co- and invariant position.
    x: (&'long u32, UnsafeCell<&'long u32>),
) {
    // 在析构时赋值时,各变量 variance 单独计算
    let _: (&'short u32, UnsafeCell<&'long u32>) = x;
}

fn takes_fn_ptr<'short, 'middle: 'short>(
    f: fn(&'middle ()) -> &'middle (),
) {

    // 各变量 variance 单独计算: 函数输入参数是逆变,返回结果是协变。
    let _: fn(&'static ()) -> &'short () = f;
}

2 type-coercions
#

type-coercions 是 Rust 的隐式行为,用于改变一个 value 的 type,Rust 只在一些特定的位置(场景)才会使用类型协变。

在函数传参匹配 trait bound 时不会进行协变,例如虽然 &mut i32 可以协变到 &i32, 但传参时不会协变。带来的影响是:如果 Trait 作为函数参数限界,&i32 和 &mut i32 两种类型都需要实现该 Trait。

trait Trait {}

fn foo<X: Trait>(t: X) {}

impl<'a> Trait for &'a i32 {}

fn main() {
    let t: &mut i32 = &mut 0;
    foo(t);
}

// error[E0277]: the trait bound `&mut i32: Trait` is not satisfied
//  --> src/main.rs:9:9
//   |
// 3 | fn foo<X: Trait>(t: X) {}
//   |           ----- required by this bound in `foo`
// ...
// 9 |     foo(t);
//   |         ^ the trait `Trait` is not implemented for `&mut i32`
//   |
//   = help: the following implementations were found:
//             <&'a i32 as Trait>
//   = note: `Trait` is implemented for `&i32`, but not for `&mut i32`

Rust 隐式类型协变的场景(也可以使用 as 运算符进行显式转换):

  1. let 赋值语句, 如 let _: &i8 = &mut 42;

  2. static 和 const 常量声明;

  3. 函数调用时的传参;

           fn bar(_: &i8) { }
           fn main() {
               bar(&mut 42);
           }
    
  4. 实例化 struct/unio/enum variant field 时;

           struct Foo<'a> { x: &'a i8 }
           fn main() {
               Foo { x: &mut 42 };
           }
    
  5. 函数返回值:

           use std::fmt::Display;
           fn foo(x: &u32) -> &dyn Display {
               x
           }
    

另外还有一些协变传播表达式(coercion-propagating expression), 如:

  1. Array 字面量;
  2. Tuples
  3. 括起来的子表达式 ((e))
  4. Blocks

3 Coercion types
#

Coercion is allowed between the following types:

  1. T to U if T is a subtype of U (reflexive case)

  2. T_1 to T_3 where T_1 coerces to T_2 and T_2 coerces to T_3 (transitive case)

  3. &mut T to &T

  4. *mut T to *const T

  5. &T to *const T

  6. &mut T to *mut T

  7. &T or &mut T to &U if T implements Deref<Target = U>

  8. &mut T to &mut U if T implements DerefMut<Target = U>

  9. TyCtor(T) to TyCtor(U), where TyCtor(T) is one of

    • &T
    • &mut T
    • *const T
    • *mut T
    • Box<T>

    and where U can be obtained from T by unsized coercion.

  10. Function item types to fn pointers

  11. Non capturing closures to fn pointers

  12. ! to any T :如 match 的某个条件返回 return

use std::ops::Deref;

struct CharContainer {
    value: char,
}

impl Deref for CharContainer {
    type Target = char;

    fn deref<'a>(&'a self) -> &'a char {
        &self.value
    }
}

fn foo(arg: &char) {}

fn main() {
    let x = &mut CharContainer { value: 'y' };
    foo(x); //&mut CharContainer 被协变到 &char.
}

// 将 &mut T 转换为 *mut T
let ptr: *mut i32 = &mut 0;
let ref_transmuted = unsafe {
    std::mem::transmute::<*mut i32, &mut i32>(ptr)
};
// Use a reborrow instead
let ref_casted = unsafe { &mut *ptr };

// 将 &mut T 转换为 &mut U:
let ptr = &mut 0;
let val_transmuted = unsafe {
    std::mem::transmute::<&mut i32, &mut u32>(ptr)
};
let val_casts = unsafe { &mut *(ptr as *mut i32 as *mut u32) };

4 Unsized Coercions
#

unsized coercions 指的是将 sized 类型转换为 unsized 类型,它们是编译器定义的特殊 隐式转换行为

unsized coercions 的实现与 Unsize and CoerceUnsized trait 相关, =Unsize trait 只能由编译器内置实现=,而 CoerceUnsized 是可以为自定义类型实现的 trait。

以下是编译器内置的 unsized 协变,即如果编译器为 T 实现了 Unsize<U>,则 T 可以 unsized 协变到 U:

  1. [T; n] to [T]
  2. T to dyn U :当 T 实现了 U + Sized, 且 U 是 object safe. <- T 必须是 Sized
  3. Foo<…, T, …> 到 Foo<…, U, …>, 当:
    • Foo 是 struct 类型;
    • T 实现了 Unsize<U>
    • Foo 的最后一个 field 使用了 T 类型;
    • 如果 Foo field 有 Bar<T> 类型,则 Bar<T> 需要实现 Unsized<Bar<U>>;
    • T 没有在 Foo 的其它 field 中使用;

当 T 实现了 Unsize<U> 或 CoerceUnsized<Foo<U>> 时,Foo<T> 实现 CoerceUnsized<Foo<U>>

5 Unsized trait
#

Unsized trait 是一个 marker trait, 只能由编译器自动实现, 主要用到的是两种:

  1. 数组 [T; N] 实现了 Unsize<[T]>, 这意味着 &[T;N] 可以赋值给 &[T] 类型;
  2. trait object: 如果 value 实现了 MyTrait, 则可以将 value 赋值给 dyn MyTrait

对于内置类型,如果 T: Unsize<U>, 则 &T 可以自动隐式转换为 &U。

// https://doc.rust-lang.org/std/marker/trait.Unsize.html
pub trait Unsize<T> where T: ?Sized, { }
let u: &dyn MyTrait =  &value;
let u: Box<dyn MyTrait> = Box::new(value);

Unsize trait 和 CoerceUnsized trait 结合使用,可以允许自定义类型,如 Rc,包含动态大小的类型。

6 CoerceUnsized trait
#

对于自定义类型,如果满足 Foo<T>: CoerceUnsized<Foo<U>>,则 Foo<T> 可以被自动隐式转换为 Foo<U>.

// https://doc.rust-lang.org/std/ops/trait.CoerceUnsized.html
pub trait CoerceUnsized<T> where T: ?Sized, { }

CoerceUnsized 是 marker trait,提供指针类型(含智能指针)如 &T/&mut T/*const T/*mut T/Box<T>/Rc<T>/Arc<T> 以及包装器类型,如 Cell<T>/RefCell<T>/Pin<T>,到另一个 unsized 类型 U 的 unsized coerce:

  • T 和 U 都是 ?Sized ;
  • &T 可以 unsized coerce 到 &U 或 *const U;
  • &mut T 可以 unsized coerce 到 &mut U 或 *mut U 或 &U 或 *const U;
  • 裸指针间 unsized 转换;
  • 智能指针间转换,如 Box<T>/Rc<T>/Arc<T> 到 Box<U>/Rc<U>/Arc<U>;
  • 智能指针的包装器类型间转换,如 Cell/Pin<Box<T>> 到 Cell/Pin<Box<U>>;
// &T 可以 unsized coerce 到 &U 或 *const U
impl<'a, 'b, T, U> CoerceUnsized<&'a U> for &'b T where 'b: 'a, T: Unsize<U> + ?Sized, U: ?Sized
impl<'a, T, U> CoerceUnsized<*const U> for &'a T where T: Unsize<U> + ?Sized, U: ?Sized

// &mut T 可以 unsized coerce 到 &mut U 或 *mut U 或 &U 或 *const U
impl<'a, T, U> CoerceUnsized<*mut U> for &'a mut T where T: Unsize<U> + ?Sized, U: ?Sized,
impl<'a, T, U> CoerceUnsized<&'a mut U> for &'a mut T where T: Unsize<U> + ?Sized, U: ?Sized,
impl<'a, 'b, T, U> CoerceUnsized<&'a U> for &'b mut T where 'b: 'a, T: Unsize<U> + ?Sized, U: ?Sized,
impl<'a, T, U> CoerceUnsized<*const U> for &'a mut T where T: Unsize<U> + ?Sized, U: ?Sized,

// 裸指针间转换
impl<T, U> CoerceUnsized<*const U> for *const T where T: Unsize<U> + ?Sized, U: ?Sized,
impl<T, U> CoerceUnsized<*const U> for *mut T where T: Unsize<U> + ?Sized, U: ?Sized,
impl<T, U> CoerceUnsized<*mut U> for *mut T where T: Unsize<U> + ?Sized, U: ?Sized,

// 智能指针间转换,如 Box<T> 到 Box<U>

// Ref 是 RefCell.borrow() 返回的类型, RefMut 是 RefCell.borrow_mut() 返回的类型;
impl<'b, T, U> CoerceUnsized<Ref<'b, U>> for Ref<'b, T> where T: Unsize<U> + ?Sized, U: ?Sized,
impl<'b, T, U> CoerceUnsized<RefMut<'b, U>> for RefMut<'b, T> where T: Unsize<U> + ?Sized, U: ?Sized,
impl<T, U, A> CoerceUnsized<Box<U, A>> for Box<T, A> where T: Unsize<U> + ?Sized, A: Allocator, U: ?Sized,
impl<T, U, A> CoerceUnsized<Rc<U, A>> for Rc<T, A> where T: Unsize<U> + ?Sized, A: Allocator, U: ?Sized,
impl<T, U, A> CoerceUnsized<Arc<U, A>> for Arc<T, A> where T: Unsize<U> + ?Sized, A: Allocator, U: ?Sized,
impl<T, U> CoerceUnsized<NonNull<U>> for NonNull<T> where T: Unsize<U> + ?Sized, U: ?Sized,

// 智能指针的包装器类型,如 Cell<Box<T>> 到 Cell<Box<U>>.
impl<Ptr, U> CoerceUnsized<Pin<U>> for Pin<Ptr> where Ptr: CoerceUnsized<U>,
impl<T, U> CoerceUnsized<Cell<U>> for Cell<T> where T: CoerceUnsized<U>,
impl<T, U> CoerceUnsized<RefCell<U>> for RefCell<T> where T: CoerceUnsized<U>,
impl<T, U> CoerceUnsized<SyncUnsafeCell<U>> for SyncUnsafeCell<T> where T: CoerceUnsized<U>,
impl<T, U> CoerceUnsized<UnsafeCell<U>> for UnsafeCell<T> where T: CoerceUnsized<U>,

impl<T, U, A> CoerceUnsized<Weak<U, A>> for std::rc::Weak<T, A> where T: Unsize<U> + ?Sized, A: Allocator, U: ?Sized
impl<T, U, A> CoerceUnsized<Weak<U, A>> for std::sync::Weak<T, A> where T: Unsize<U> + ?Sized, A: Allocator, U: ?Sized

Option 没有实现 CoerceUnsized,所以不能实现 Option<T> 到 Option<U> 的转换:

trait X {
    fn x(&mut self);
}

struct XX;

impl X for XX {
    fn x(&mut self) {
        todo!()
    }
}

struct C<'a> {
    mut_ref: Option<&'a mut dyn X>,
}

fn main() {
    //  Box <dyn X > 等效于 Box <dyn X + 'static>,所以 b 的实际类型是 Option<Box<dyn X + 'static>>
    let mut b: Option<Box<dyn X>> = Some(Box::new(XX));

    // as_deref_mut() 的签名:
    //
    // pub fn as_deref_mut(&mut self) -> Option<&mut <T as Deref>::Target> where T: DerefMut
    //
    // 所以 b.as_deref_mut() 的结果类型是 Option<&'a mut(dyn X + 'static)>.

    // 重点来了: Option<T> 是不支持协变到 Option<U> 的, 所以 Option<&'a mut(dyn X + 'static)> 不能协
    // 变到 mut_ref 要求的 Option<&'a mut(dyn X + 'a).

    // match 匹配 Some(x) 之所以 OK, 是因为 x 此时的类型已经是 &'a mut(dyn X + 'static) 了,而标准库
    // 实现了 impl<'a, T, U> CoerceUnsized<&'a mut U> for &'a mut T where T: Unsize<U> + ?Sized, U:
    // ?Sized,当 T 是 unsized 的 dyn X + 'static 且 U 是 dyn X + 'a 时, 满足上面的 where 约束, 所以
    // 支持 &'a mut(dyn X + 'static) 到 &'a mut(dyn X + 'a) 的 unsized 协变, 所以下面的 match 表达式
    // 可以执行成功.

    // error[E0597]: `b` does not live long enough
    // let c = C {
    //     mut_ref: b.as_deref_mut(),
    // };

    let c = match b.as_deref_mut() {
        None => C { mut_ref: None },
        Some(x) => C { mut_ref: Some(x) },
    };
}

使用 CoerceUnsized trait 实现的 type coercion 转换的例子:

// 满足 CoerceUnsized<Ptr<U>> for Ptr<T> where T: Unsize<U> + ?Sized 时,
// Ptr<T> 可以 type coercion 到 Ptr<U>, 这里的 Ptr 类型是:
//
// &'b T 或 &'b mut T 或 Ref<'b, T> 或 RefMut<'b, T> 或 *mut T 或 *const T 或 Cell<T> 或 RefCell<T>或 Box<T, A> 或 Rc<T, A> 或 Arc<T, A>,
//
// 但不包含 Result 或 Option

// [T; N] -> [T]
let bo: Box<[i32]> = Box::new([1, 2, 3]);

// 1i32 实现了 dyn std::fmt::Display 和 dyn std::any::Any, 所以 1i32 可以 unsized coercion 到
// trait object trait object 有两种形式1. &dyn Trait
let dsd: &dyn std::fmt::Display = &1i32;
// 2. Box<dyn Trait>
let bo: Box<dyn std::fmt::Display> = Box::new(1i32);
let bo: Box<dyn std::any::Any> = Box::new(1i32);
let bo: Box<&dyn std::fmt::Display> = Box::new(&1i32);

// 以下也是 CoerceUnsized trait 实现的 type coercion 的转换. U 需要是 unsized type.

// &T -> &U
let u: &dyn std::fmt::Display = &123i32;
// &mut T -> &U
let u: &dyn std::fmt::Display = &mut 123i32;
// &mut T -> &mut U
let u: &mut dyn std::fmt::Display = &mut 123i32;
// &T -> *const U
let u: *const dyn std::fmt::Display = &123i32;
// &mut T -> *const U
let u: *const dyn std::fmt::Display = &mut 123i32;
// &mut T -> *mut U
let u: *mut dyn std::fmt::Display = &mut 123i32;

7 Least upper bound coercions
#

In some contexts, the compiler must coerce together multiple types to try and find the most general type. This is called a "Least Upper Bound" coercion.

LUB coercion is used and only used in the following situations:

  • To find the common type for a series of if branches.
  • To find the common type for a series of match arms.
  • To find the common type for array elements.
  • To find the type for the return type of a closure with multiple return statements.
  • To check the type for the return type of a function with multiple return statements.

In each such case, there are a set of types T0..Tn to be mutually coerced to some target type T_t, which is unknown to start. Computing the LUB coercion is done iteratively. The target type T_t begins as the type T0. For each new type Ti, we consider whether

  1. If Ti can be coerced to the current target type T_t, then no change is made.
  2. Otherwise, check whether T_t can be coerced to Ti; if so, the T_t is changed to Ti. (This check is also conditioned on whether all of the source expressions considered thus far have implicit coercions.)
  3. If not, try to compute a mutual supertype of T_t and Ti, which will become the new target type.

Examples:

// For if branches
let bar = if true {
    a
} else if false {
    b
} else {
    c
};

// For match arms
let baw = match 42 {
    0 => a,
    1 => b,
    _ => c,
};

// For array elements
let bax = [a, b, c];

// For closure with multiple return statements
let clo = || {
    if true {
        a
    } else if false {
        b
    } else {
        c
    }
};
let baz = clo();

// For type checking of function with multiple return statements
fn foo() -> i32 {
    let (a, b, c) = (0, 1, 2);
    match 42 {
        0 => a,
        1 => b,
        _ => c,
    }
}

In these examples, types of the ba* are found by LUB coercion. And the compiler checks whether LUB coercion result of a, b, c is i32 in the processing of the function foo.

Caveat: This description is obviously informal. Making it more precise is expected to proceed as part of a general effort to specify the Rust type checker more precisely.

rust-lang - 这篇文章属于一个选集。
§ 12: 本文

相关文章

不安全:unsafe
··824 字
Rust
Rust
借用:refer/borrow
··3127 字
Rust
Rust 引用类型和借用
函数、方法和闭包:function/method/closure
··7032 字
Rust
Rust 函数、方法和闭包
包和模块:package/crate/module
··2066 字
Rust
Rust 项目的包和模块组织结构