类型强转
类型强转 指的是值的隐式操作,可以更改值的类型。
强转在特定情况自动发生,对于符合强转要求的类型,有高度的限制。
所有允许隐式强转的转换可以通过 类型转换运算符 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_2
,T_2
转换为T_3
时,T_1
可以转换为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>
而且
U
可以通过 不定大小强转 从T
中获得。 -
函数条目类型到
fn
指针 -
非捕获闭包到
fn
指针 -
!
到任何T
不定大小强转
下列类型强转被称为 不定大小强转
,因为它们涉及将有大小限制的类型转换为无大小限制的类型,并且在一些其他强转不允许的情况下被允许,如上所述。
这类强转可以在可强转的的任意位置。
Unsize
和 CoerceUnsized
两个辅助 trait ,可用实现此类强转并将其公开以供库使用。
下列强转是内置的,如果 T
可以通过其中之一转换为 U
,则将为 T
提供一个实现了 Unsize<U>
的实现:
-
[T; n]
转换为[T]
。 -
当
T
实现U + Sized
时,T
转换为dyn U
,而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 中直接使用它们。
最小上界强转
在某些情况下,编译器必须将多个类型强转在一起处理,会尝试找到最通用的类型。 这被称为 "最小上界强转" (Least Upper Bound coercion,简称LUB 强转) 。LUB 强转仅在以下情况下使用:
- 为一系列 if 分支查找公开类型。
- 为一系列 match 分支查找公开类型。
- 为数组元素查找公开类型。
- 为带有多个返回语句的闭包查找返回类型。
- 检查带有多个返回语句的函数的返回类型。
在每种情况下,存在一组要相互强转为某个目标类型 T_t
的类型 T0..Tn
,该目标类型最初是未知的。
计算 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); // 用于 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
时检查 a
, b
, c
的 LUB 强转结果是否为 i32
。
注意事项
这个描述是非正式的,更准确的描述将作为规范 Rust 类型检查器的泛型尝试的一部分。