我已经在Stack Overflow上阅读了几篇答案,并收集了以下用例:
- 当函数
panic!
时 - 当函数中有无限循环时
但是我仍然不清楚为什么我们需要像这样定义函数:
fn func() -> ! {
panic!("Error!");
}
如果它能够像这样工作(没有感叹号)那么它将以同样的方式工作:
fn func() {
panic!("Error!");
}
同时,为什么我们需要在具有无限循环的函数中使用!
?看起来这个签名没有带来任何实际的用途信息。
!
可以强制转换为任何其他类型,因此与任何其他类型兼容(由于不会执行该代码路径,我们可以假设它是我们需要的任何类型)。当我们有多个可能的代码路径时,例如if-else
或match
,这很重要。fn assert_positive(v: i32) -> u32 {
match v.try_into() {
Ok(v) => v,
Err(_) => func(),
}
}
当 func
被声明为返回 !
时,该函数能够成功编译。 如果我们省略返回类型,则 func
将被声明为返回 ()
,并且编译会失败:
error[E0308]: `match` arms have incompatible types
--> src/main.rs:8:19
|
6 | / match v.try_into() {
7 | | Ok(v) => v,
| | - this is found to be of type `u32`
8 | | Err(_) => func(),
| | ^^^^^^ expected `u32`, found `()`
9 | | }
| |_____- `match` arms have incompatible types
您还可以将其与Result::unwrap
的定义进行比较:
pub fn unwrap(self) -> T {
match self {
Ok(t) => t,
Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", &e),
}
}
unwrap_failed
返回 !
,因此它将与Ok
情况下返回的任何类型统一。use rand; // 0.8.4
fn main() {
let foo;
if rand::random::<bool>() {
foo = "Hello, world!";
} else {
diverge();
}
println!("{foo}");
}
fn diverge() {
panic!("Crash!");
}
我们声明了一个变量foo
,但是我们只在if
表达式的一个分支中对其进行了初始化。这将导致以下错误:
error[E0381]: borrow of possibly-uninitialized variable: `foo`
--> src/main.rs:10:15
|
10 | println!("{foo}");
| ^^^^^ use of possibly-uninitialized `foo`
然而,如果我们像这样更改diverge
函数的定义:
fn diverge() -> ! {
panic!("Crash!");
}
那么代码可以成功编译。编译器知道如果执行else
分支,它将永远无法到达println!
,因为diverge()
会发散。因此,else
分支不初始化foo
并不是一个错误。
!
让我们可以达到类似动态类型的效果。但同时,这样是不行的:fn func() -> ! { 123 }
,尽管你说过 __! 可以强制转换为任何其他类型,因此与任何其他类型兼容__。 - Oleksandr Noviki32
转换为!
类型。这显然行不通。在这里,“兼容性”是单向的 -!
类型(根据定义不存在)的“值”可以用于任何其他类型所期望的地方,反之则不行。 - Cerberus