Primitive Type never
never_type
#35121)Expand description
!
类型,也称为 “never”。
!
表示永远不会解析为任何值的计算类型。
例如,exit
函数 fn exit(code: i32) -> !
退出该进程而不返回,因此返回 !
。
break
、continue
和 return
表达式也具有 !
类型。例如,我们可以写:
#![feature(never_type)]
let x: ! = {
return 123
};
Run尽管 let
在这里毫无意义,但它说明了 !
的含义。
由于从未给 x
赋值 (因为 return
从整个函数返回),因此可以给 x
指定 !
类型。
我们也可以将 return 123
替换为 panic!
或永无休止的 loop
,并且此代码仍然有效。
以下代码更实际地使用 !
:
let num: u32 = match get_a_number() {
Some(num) => num,
None => break,
};
Run两个匹配分支都必须产生 u32
类型的值,但是由于 break
根本不会产生值,我们知道它永远不会产生不是 u32
的值。
这说明了 !
类型的另一种行为 - 类型为 !
的表达式将强制转换为任何其他类型。
!
和泛型
绝对的错误
您将看到显式使用的 !
的主要位置是泛型代码。考虑 FromStr
trait:
trait FromStr: Sized {
type Err;
fn from_str(s: &str) -> Result<Self, Self::Err>;
}
Run当为 String
实现此 trait 时,我们需要为 Err
选择一个类型。并且由于将字符串转换为字符串永远不会导致错误,因此适当的类型是 !
。
(目前实际使用的类型是一个没有变体的枚举,尽管这只是因为 !
以后才会被添加到 Rust 中,并且将来可能会发生变化。) 对于 Err
类型的 !
,如果我们由于某种原因不得不调用 String::from_str
,那么结果将是 Result<String, !>
,我们可以像这样解包:
#![feature(exhaustive_patterns)]
use std::str::FromStr;
let Ok(s) = String::from_str("hello");
Run由于 Err
变体包含 !
,因此永远不会发生。如果存在 exhaustive_patterns
特性,则意味着我们只需采用 Ok
变体就可以在 Result<T, !>
上进行穷尽的匹配。
这说明了 !
的另一种行为 - 它可以用于 “delete” 泛型 (如 Result
) 中的某些枚举变体。
无限循环
尽管 Result<T, !>
对于消除错误非常有用,但 !
也可以用于消除成功。如果我们将 Result<T, !>
视为 “if this function returns, it has not errored,”,那么我们也会非常直观地想到 Result<!, E>
: 如果函数返回,则 有 错误。
例如,考虑一个简单的 Web 服务器的情况,它可以简化为:
loop {
let (client, request) = get_request().expect("disconnected");
let response = request.process();
response.send(client);
}
Run目前,这并不理想,因为只要无法建立新的连接,我们就简单地使用 panic。 相反,我们想跟踪此错误,如下所示:
loop {
match get_request() {
Err(err) => break err,
Ok((client, request)) => {
let response = request.process();
response.send(client);
},
}
}
Run现在,当服务器断开连接时,我们以错误退出循环而不是 panic。虽然简单地返回错误可能很直观,但我们可能希望将其包装在 Result<!, E>
中:
fn server_loop() -> Result<!, ConnectionError> {
loop {
let (client, request) = get_request()?;
let response = request.process();
response.send(client);
}
}
Run现在,我们可以使用 ?
代替 match
,并且返回类型更有意义:如果循环停止,则意味着发生了错误。我们甚至不必将循环包装在 Ok
中,因为 !
会自动强制转换为 Result<!, ConnectionError>
。
!
和 traits
编写自己的 traits 时,只要有明显的 impl
而不是 panic!
,!
就应该有一个 impl
。
原因是返回 impl Trait
且 !
没有 impl
的函数不能作为它的唯一可能的代码路径发散。
换句话说,它们不能从每个代码路径返回 !
。
例如,此代码不会编译:
但是这段代码可以做到:
use std::ops::Add;
fn foo() -> impl Add<u32> {
if true {
unimplemented!()
} else {
0
}
}
Run原因是,在第一个示例中,!
可以强制转换为许多可能的类型,因为许多类型实现了 Add<u32>
。
但是,在第二个示例中,else
分支返回 0
,编译器从返回类型推断出它为 u32
类型。
由于 u32
是具体类型,因此 !
可以并且将被强制使用。
有关此 !
的更多信息,请参见问题 #36375。
但是,事实证明,大多数 traits 都可以将 impl
用作 !
。以 Debug
为例:
#![feature(never_type)]
impl Debug for ! {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
*self
}
}
Run我们再次使用 !
的功能来强制转换为任何其他类型,在本例中为 fmt::Result
。
由于此方法将 &!
作为参数,因此我们知道它永远不能被调用 (因为没有 !
类型的值可以调用它)。
编写 *self
实质上就是告诉编译器 “我们知道这段代码永远无法运行,所以只需将整个函数体视为具有类型 fmt::Result
”。
当为 !
实现 traits 时,可以使用这种模式。
通常,任何仅具有采用 self
参数的方法的 trait 都应具有这样的含义。
另一方面,不适合实现的一个 trait 是 Default
:
trait Default {
fn default() -> Self;
}
Run由于 !
没有值,因此也没有默认值。
的确,我们可以为此编写一个 impl
,它只是 panics,但是对于任何类型都一样 (我们可以通过仅将 default()
panic 制作为 (eg.) File
来使用 impl Default
)。