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

隐式类型转换

隐式类型转换 是改变值类型的隐式操作。它们在特定位置自动发生,并且对实际进行转换的类型有严格限制。

任何允许隐式转换的转换也可以通过 类型转换运算符 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
    }
    }

如果这些转换点中的表达式是一个转换传播表达式,那么该表达式中相关的子表达式也是转换点。传播从这些新的转换点递归进行。传播表达式及其相关的子表达式包括:

  • 数组字面量,其中数组类型为 [U; n] 。数组字面量中的每个子表达式都是转换为 U 类型的转换点。
  • 具有重复语法格式的数组字面量,其中数组类型为 [U; n] 。重复的子表达式是转换为 U 类型的转换点。
  • 元组,元组本身是转换为类型 (U_0, U_1, ..., U_n) 的转换点。每个子表达式都是对应类型的转换点,例如,第 0 个子表达式是转换为 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 实现了 Deref<Target = U> ,则 &T&mut T&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 被隐式转换为 &char。
    }
  • 如果 T 实现了 DerefMut<Target = U> ,则 &mut T&mut U
  • TyCtor( T ) 到 TyCtor( U ),其中 TyCtor( T ) 是以下之一

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

    并且可以通过 非定长转换T 获得 U

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

非定长转换

以下转换被称为 非定长转换 ,因为它们与将类型转换为非定长类型有关,并且在上述其他转换不允许的少数情况下是允许的。它们仍然可以发生在隐式转换可以发生的任何其他地方。

两个 特型 UnsizeCoerceUnsized 被用于协助此过程并将其公开给库使用。以下转换是内置的,如果 T 可以通过其中之一隐式转换为 U ,则将提供 TUnsize<U> 的实现:

  • [T; n][T]
  • Tdyn U ,当 T 实现 U + Sized ,且 Udyn 兼容 的。
  • dyn Tdyn U ,当 UT父特型 之一时。
    • 这允许丢弃自动特型,即允许 dyn T + Autodyn U
    • 如果主特型将自动特型作为父特型,则允许添加自动特型,即给定 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 不是任何其他字段类型的一部分。

此外,当 T 实现了 Unsize<U>CoerceUnsized<Foo<U>> 时,类型 Foo<T> 可以实现 CoerceUnsized<Foo<U>> 。这允许它提供到 Foo<U> 的非定长转换。

注意

虽然非定长转换的定义及其实现已经稳定,但特型本身尚未稳定,因此不能在稳定版 Rust 中直接使用。

最小上界转换

在某些语境中,编译器必须将多个类型一起进行隐式转换,以尝试找到最通用的类型。这被称为 “最小上界” (LUB)转换。LUB 转换仅在以下情况下使用:

  • 寻找一系列 if 分支的共同类型。
  • 寻找一系列 match 臂的共同类型。
  • 寻找数组元素的共同类型。
  • 寻找具有多个返回语句的闭包的返回类型。
  • 检查具有多个返回语句的函数的返回类型。

在每种情况下,都有一组类型 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);
// 对于 if 分支
let bar = if true {
    a
} else if false {
    b
} else {
    c
};

// 对于 match 臂
let baw = match 42 {
    0 => a,
    1 => b,
    _ => c,
};

// 对于数组元素
let bax = [a, b, c];

// 对于具有多个返回语句的闭包
let clo = || {
    if true {
        a
    } else if false {
        b
    } else {
        c
    }
};
let baz = clo();

// 对于具有多个返回语句的函数的类型检查
fn foo() -> i32 {
    let (a, b, c) = (0, 1, 2);
    match 42 {
        0 => a,
        1 => b,
        _ => c,
    }
}
}

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

警告

这种描述显然是非正式的。更精确的描述预计将作为更精确地规范 Rust 类型检查器总体工作的一部分来推进。