类型转换
类型转换 是隐式操作,用于更改值的类型。它们在特定位置自动发生,并且在实际转换的类型方面受到高度限制。
任何允许通过强制转换进行的转换也可以通过类型转换运算符 as
显式执行。
强制转换最初在 RFC 401 中定义,并在 RFC 1558 中进行了扩展。
强制转换位点
强制转换只能在程序中的某些强制转换位点发生;这些位点通常是期望的类型是显式的,或者可以从显式类型传播(无需类型推断)推导出的地方。可能的强制转换位点包括
-
给出显式类型的
let
语句。例如,在以下代码中,
&mut 42
被强制转换为&i8
类型#![allow(unused)] fn main() { let _: &i8 = &mut 42; }
static
和const
项声明(类似于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)
的强制转换位点。每个子表达式都是强制转换为相应类型的强制转换位点,例如,第零个子表达式是强制转换为类型U_0
的强制转换位点。
- 带括号的子表达式 (
(e)
):如果表达式的类型为U
,则子表达式是强制转换为U
的强制转换位点。
- 代码块:如果代码块的类型为
U
,则代码块中的最后一个表达式(如果它不是以分号结尾)是强制转换为U
的强制转换位点。这包括作为控制流语句一部分的代码块,例如if
/else
,如果代码块具有已知类型。
强制转换类型
允许在以下类型之间进行强制转换
T
到U
,如果T
是U
的子类型(自反情况)
-
T_1
到T_3
,其中T_1
强制转换为T_2
,且T_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
可以通过 不定大小强制转换 从T
获得。
- 函数项类型到
fn
指针
- 非捕获闭包到
fn
指针
!
到任何T
不定大小强制转换
以下强制转换称为 不定大小强制转换
,因为它们与将固定大小类型转换为不定大小类型有关,并且在其他强制转换不适用的少数情况下是允许的,如上所述。它们仍然可以在任何其他可以发生强制转换的地方发生。
两个 trait,Unsize
和 CoerceUnsized
,用于辅助此过程并为库使用公开它。以下强制转换是内置的,如果 T
可以通过其中一种强制转换为 U
,则将为 T
提供 Unsize<U>
的实现
[T; n]
到[T]
。
T
到dyn U
,当T
实现了U + Sized
,并且U
是 dyn 兼容的。
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>
提供不定大小强制转换。
注意:虽然不定大小强制转换的定义及其实现已经稳定,但 traits 本身尚未稳定,因此不能在稳定的 Rust 中直接使用。
最小上界强制转换
在某些上下文中,编译器必须将多个类型强制转换在一起,以尝试找到最通用的类型。这称为“最小上界”强制转换。LUB 强制转换仅在以下情况下使用
- 查找一系列 if 分支的公共类型。
- 查找一系列 match 分支的公共类型。
- 查找数组元素的公共类型。
- 查找具有多个 return 语句的闭包的返回类型。
- 检查具有多个 return 语句的函数的返回类型。
在每种情况下,都有一组类型 T0..Tn
要相互强制转换为某个目标类型 T_t
,该目标类型最初是未知的。
计算 LUB 强制转换是迭代完成的。目标类型 T_t
最初是类型 T0
。对于每个新类型 Ti
,我们考虑是否
- 如果
Ti
可以强制转换为当前目标类型T_t
,则不进行更改。
- 否则,检查
T_t
是否可以强制转换为Ti
;如果是,则将T_t
更改为Ti
。(此检查还取决于到目前为止考虑的所有源表达式是否具有隐式强制转换。)
- 如果不是,则尝试计算
T_t
和Ti
的互超类型,这将成为新的目标类型。
示例
#![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
时,检查 a
、b
、c
的 LUB 强制转换结果是否为 i32
。
注意
此描述显然是非正式的。使其更精确预计将作为更精确地指定 Rust 类型检查器的一般工作的一部分进行。