Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

类型强制转换

类型强制转换(Type Coercion)是更改值类型的隐式操作。它们在特定位置自动发生,并且对实际强制转换的类型有高度限制。

强制转换允许的任何转换也可以通过类型转换运算符 as 显式执行。

强制转换最初在 RFC 401 中定义,并在 RFC 1558 中扩展。

强制转换站点

强制转换只能发生在程序中的某些强制转换站点;这些通常是所需类型是显式的或可以通过从显式类型传播推导的位置(没有类型推断)。可能的强制转换站点是:

  • 给定显式类型的 let 语句。

    例如,在以下情况下,&mut 42 被强制转换为具有类型 &i8

    #![allow(unused)]
    fn main() {
    let _: &i8 = &mut 42;
    }
  • staticconst 项声明(类似于 let 语句)。
  • 函数调用的参数

    被强制转换的值是实际参数,它被强制转换为形式参数的类型。

    例如,在以下情况下,&mut 42 被强制转换为具有类型 &i8

    fn bar(_: &i8) { }
    
    fn main() {
        bar(&mut 42);
    }

    对于方法调用,接收者(self 参数)类型的强制转换方式不同,请参阅方法调用表达式文档了解详情。

  • 结构体、联合体或枚举变体字段的实例化

    例如,在以下情况下,&mut 42 被强制转换为具有类型 &i8

    struct Foo<'a> { x: &'a i8 }
    
    fn main() {
        Foo { x: &mut 42 };
    }
  • 函数结果 — 如果块的最后一行不是以分号终止的,或者是 return 语句中的任何表达式

    例如,在以下情况下,x 被强制转换为具有类型 &dyn Display

    #![allow(unused)]
    fn main() {
    use std::fmt::Display;
    fn foo(x: &u32) -> &dyn Display {
        x
    }
    }
  • 赋值表达式中的赋值值操作数

    例如,在以下情况下,y 被强制转换为具有类型 &i8

    #![allow(unused)]
    fn main() {
    let mut x = &0i8;
    let y = &mut 42i8;
    x = y;
    }

如果这些强制转换站点之一中的表达式是强制转换传播表达式,则该表达式中的相关子表达式也是强制转换站点。传播从这些新的强制转换站点递归。传播表达式及其相关子表达式是:

  • 数组字面量,其中数组类型为 [U; n]。数组字面量中的每个子表达式都是强制转换为类型 U 的强制转换站点。
  • 带重复语法的数组字面量,其中数组类型为 [U; n]。重复的子表达式是强制转换为类型 U 的强制转换站点。
  • 元组,其中元组是类型为 (U_0, U_1, ..., U_n) 的强制转换站点。每个子表达式是强制转换为相应类型的强制转换站点,例如第零个子表达式是强制转换为类型 U_0 的强制转换站点。
  • 括号子表达式((e)):如果表达式类型为 U,则子表达式是强制转换为 U 的强制转换站点。
  • 块:如果块类型为 U,则块中的最后一个表达式(如果不是以分号终止的)是强制转换为 U 的强制转换站点。这包括作为控制流语句一部分的块,例如 if/else,如果块具有已知类型。

强制转换类型

允许在以下类型之间进行强制转换:

  • TU,如果 TU子类型自反情况
  • T_1T_3,其中 T_1 强制转换为 T_2T_2 强制转换为 T_3传递情况

    请注意,这尚未完全支持。

  • &mut T&T
  • *mut T*const T
  • &T*const T
  • &mut T*mut T
  • &T&mut T&U,如果 T 实现 Deref<Target = U>。例如:

    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 is coerced to &char.
    }
  • &mut T&mut U,如果 T 实现 DerefMut<Target = U>
  • TyCtor(T) 到 TyCtor(U),其中 TyCtor(T) 是以下之一

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

    并且 U 可以通过[未大小强制转换][unsized coercion](#unsized-coercions)从 T 获得。

  • 函数项类型到 fn 指针
  • 非捕获闭包到 fn 指针
  • ! 到任何 T

未大小强制转换

以下强制转换称为未大小强制转换,因为它们涉及将类型转换为未大小类型,并且在少数情况下允许其他强制转换不允许的情况,如上所述。它们仍然可以在强制转换可以发生的任何其他地方发生。

两个 trait UnsizeCoerceUnsized 用于协助此过程并将其暴露给库使用。以下是内置的,如果 T 可以通过其中之一强制转换为 U,则将为 T 提供 Unsize<U> 的实现:

  • [T; n][T]
  • Tdyn U,当 T 实现 U + Sized,并且 Udyn 兼容的
  • dyn Tdyn U,当 UT超级 trait之一时。
    • 这允许丢弃自动 trait,即 dyn T + Autodyn U 是允许的。
    • 如果主 trait 具有自动 trait 作为超级 trait,则允许添加自动 trait,即给定 trait T: U + Send {}dyn Tdyn T + Send 或到 dyn U + Send 强制转换是允许的。
  • Foo<..., T, ...>Foo<..., U, ...>,当:
    • Foo 是结构体。
    • T 实现 Unsize<U>
    • Foo 的最后一个字段具有涉及 T 的类型。
    • 如果该字段类型为 Bar<T>,则 Bar<T> 实现 Unsize<Bar<U>>
    • T 不是任何其他字段类型的一部分。

此外,类型 Foo<T> 可以在 T 实现 Unsize<U>CoerceUnsized<Foo<U>> 时实现 CoerceUnsized<Foo<U>>。这允许它提供到 Foo<U> 的未大小强制转换。

Note

虽然未大小强制转换的定义和实现已经稳定,但这些 trait 本身尚未稳定,因此不能在稳定版 Rust 中直接使用。

最小上界强制转换

在某些上下文中,编译器必须将多个类型强制转换在一起以尝试找到最通用的类型。这称为“最小上界“强制转换。LUB 强制转换仅在以下情况下使用:

  • 为一系列 if 分支找到公共类型。
  • 为一系列 match 分支找到公共类型。
  • 为数组元素找到公共类型。
  • 在 break 操作数和最终块操作数之间为标记块表达式找到公共类型。
  • 在 break 操作数之间为带 break 表达式的 loop 表达式找到公共类型。
  • 为具有多个 return 语句的闭包的返回类型找到类型。
  • 检查具有多个 return 语句的函数的返回类型。

在每种情况下,都有一组类型 T0..Tn 要相互强制转换为某个目标类型 T_t,该类型最初是未知的。

计算 LUB 强制转换是迭代完成的。目标类型 T_t 从类型 T0 开始。对于每个新类型 Ti,我们考虑是否

  • 如果 Ti 可以强制转换为当前目标类型 T_t,则不做任何更改。
  • 否则,检查 T_t 是否可以强制转换为 Ti;如果是,则将 T_t 更改为 Ti。(此检查还取决于到目前为止考虑的所有源表达式是否具有隐式强制转换。)
  • 如果没有,尝试计算 T_tTi 的相互超类型,这将成为新的目标类型。

示例:

#![allow(unused)]
fn main() {
let (a, b, c) = (0, 1, 2);
// 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,
    }
}
}

在这些示例中,ba* 的类型通过 LUB 强制转换找到。编译器在处理函数 foo 时检查 abc 的 LUB 强制转换结果是否为 i32

注意

此描述显然是非正式的。使其更精确预计将作为更精确地指定 Rust 类型检查器的总体努力的一部分进行。