Rust 中的发散函数有什么意义?
What is the point of diverging functions in Rust?
我已经阅读了几个关于 SO 的答案,并收集了这些用例:
- 当函数
panic!
s
- 当函数中有无限循环时
但我仍然不清楚为什么我们需要这样定义函数:
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
声明为return!
时,这个函数compiles successfully. If we drop the return type, func
will be declared as returning ()
, and the compilation breaks:
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
您也可以将此与 definition for 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
is returning !
,因此它与 returned 在 Ok
情况下的任何类型统一。
编译器知道发散表达式(w.r.t.求值顺序)后面的任何东西都是不可到达的。在决定是否初始化局部变量时,它可以使用此信息来避免漏报。
考虑以下示例:
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
.
并不是错误
我已经阅读了几个关于 SO 的答案,并收集了这些用例:
- 当函数
panic!
s - 当函数中有无限循环时
但我仍然不清楚为什么我们需要这样定义函数:
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
声明为return!
时,这个函数compiles successfully. If we drop the return type, func
will be declared as returning ()
, and the compilation breaks:
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
您也可以将此与 definition for 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
is returning !
,因此它与 returned 在 Ok
情况下的任何类型统一。
编译器知道发散表达式(w.r.t.求值顺序)后面的任何东西都是不可到达的。在决定是否初始化局部变量时,它可以使用此信息来避免漏报。
考虑以下示例:
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
.