类型强制转换
类型强制转换(Type Coercion)是更改值类型的隐式操作。它们在特定位置自动发生,并且对实际强制转换的类型有高度限制。
强制转换允许的任何转换也可以通过类型转换运算符 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 } }
-
赋值表达式中的赋值值操作数
例如,在以下情况下,
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,如果块具有已知类型。
强制转换类型
允许在以下类型之间进行强制转换:
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 TBox<T>
并且
U可以通过[未大小强制转换][unsized coercion](#unsized-coercions)从T获得。
- 函数项类型到
fn指针
- 非捕获闭包到
fn指针
!到任何T
未大小强制转换
以下强制转换称为未大小强制转换,因为它们涉及将类型转换为未大小类型,并且在少数情况下允许其他强制转换不允许的情况,如上所述。它们仍然可以在强制转换可以发生的任何其他地方发生。
两个 trait Unsize 和 CoerceUnsized 用于协助此过程并将其暴露给库使用。以下是内置的,如果 T 可以通过其中之一强制转换为 U,则将为 T 提供 Unsize<U> 的实现:
[T; n]到[T]。
T到dyn U,当T实现U + Sized,并且U是 dyn 兼容的。
dyn T到dyn U,当U是T的超级 trait之一时。- 这允许丢弃自动 trait,即
dyn T + Auto到dyn U是允许的。 - 如果主 trait 具有自动 trait 作为超级 trait,则允许添加自动 trait,即给定
trait T: U + Send {},dyn T到dyn T + Send或到dyn U + Send强制转换是允许的。
- 这允许丢弃自动 trait,即
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_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 类型检查器的总体努力的一部分进行。