类型强制转换
类型强制转换是一种隐式操作,用于更改值的类型。它们在特定位置自动发生,并且对实际强制转换的类型有严格限制。
任何允许强制转换的转换也可以通过类型转换运算符 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
实现了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 is coerced to &char. }
-
如果
T
实现了DerefMut<Target = U>
,则&mut T
到&mut U
。 -
TyCtor(
T
) 到 TyCtor(U
),其中 TyCtor(T
) 是以下之一&T
&mut T
*const T
*mut T
Box<T>
并且其中
U
可以通过非大小强制转换从T
获得。 -
函数项类型到
fn
指针 -
非捕获闭包到
fn
指针 -
!
到任何T
非大小强制转换
以下强制转换称为“非大小强制转换”,因为它们涉及将大小类型转换为非大小类型,并且在上述其他强制转换不允许的情况下,在少数情况下是允许的。它们仍然可以发生在任何其他可以进行强制转换的地方。
两个特征,Unsize
和 CoerceUnsized
,用于协助此过程并将其公开以供库使用。以下强制转换是内置的,如果可以使用其中之一将 T
强制转换为 U
,则将提供 T
的 Unsize<U>
实现
-
[T; n]
到[T]
。 -
当
T
实现了U + Sized
并且U
是对象安全的时,T
到dyn U
。 -
当满足以下条件时,
Foo<..., T, ...>
到Foo<..., U, ...>
Foo
是一个结构体。T
实现了Unsize<U>
。Foo
的最后一个字段的类型涉及T
。- 如果该字段的类型为
Bar<T>
,则Bar<T>
实现了Unsized<Bar<U>>
。 - T 不是任何其他字段类型的一部分。
此外,当 T
实现 Unsize<U>
或 CoerceUnsized<Foo<U>>
时,类型 Foo<T>
可以实现 CoerceUnsized<Foo<U>>
。这允许它提供到 Foo<U>
的非确定大小强制转换。
注意:虽然非确定大小强制转换的定义及其实现已经稳定,但特征本身还不稳定,因此不能直接在稳定的 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 类型检查器的一部分进行。