5分钟速读之Rust权威指南(四十一)高级类型
2023-11-07 02:00:03
<#content>
上一节我们介绍了一些比较高级的类型特性,包括上一节讲到的newtype模式、类型别名、never类型、动态大小类型。
使用newtype模式实现类型安全与抽象
上一节中我们使用newtype模式为i32创建了一个名为Length的类型。
struct Length(i32);
Length实际上相当于我们常常听到的包装类型(Wrapper Type),它通过一层类型封装,进而达到类型安全的效果。因此它也被称为包装类型。
不过,newtype模式真正的作用并不仅仅是类型安全与抽象这么简单。类型安全与抽象我们也可以通过trait来实现。newtype的另一个强大作用是能够自动从字面值产生一个值。比如,我们还可以这么定义Length类型:
struct Length(pub i32);
impl From<i32> for Length {
fn from(val: i32) -> Self {
Length(val)
}
}
编译器甚至会自动产生From
impl From<Length> for i32 {
fn from(val: Length) -> Self {
val.0
}
}
接下来我们看看一些类型的相互转换的例子。
fn use_length(l: Length) {}
fn main() {
let l1 = Length(1);
use_length(l1);
let l2 = Length::from(2);
use_length(l2);
let l3 = 3;
let l4 = Length::from(l3);
use_length(l4);
use_length(4);
}
可以看到,只要在两个互相转换的类型中实现了From互相转换的 trait方法,那么我们可以随意地相互转换这些类型的值。
最后,我们回到newtype模式,它还有一种很重要的妙用,那就是当一些类型看上去像是同一个类型时,newtype模式可以有效地区分开这些类型。比如,我们假设定义了这样一个名为Foot的结构体:
struct Foot {
inches: u32,
}
假设另外一个程序员创建了另一个名为英寸的结构体:
struct Inch {
inches: u32,
}
很明显,两个结构体都表示相同的类型。但是编译器会认为它们是两个完全不同的类型。为了解决这个问题,我们需要使用newtype模式来创建两个新类型:
newtype Foot = u32;
newtype Inch = u32;
现在两个新类型Foot和Inch都代表32位无符号整数,编译器也不会把它们当作同一种类型。
类型别名
Rust中的类型别名与newtype模式非常相似,但是它只提供类型重命名,而不涉及类型转换。
比如,我们可以创建以下类型别名:
type Kilometers = u32;
现在我们就可以使用Kilometers类型作为u32类型的别名。比如,我们可以声明一个名为distance的变量,它的类型是Kilometers:
let distance: Kilometers = 100;
类型别名还有另外一个用途,那就是创建更长的类型名,使得类型名更容易理解。比如,我们有一个枚举类型,它的变体非常多,并且每个变体的名字都非常长。为了让代码更容易阅读,我们可以为每个变体创建一个类型别名。
比如,我们假设有一个枚举类型名为Command,它有两个变体:Add和Subtract。为了让代码更容易阅读,我们可以创建以下类型别名:
type AddCommand = Command::Add;
type SubtractCommand = Command::Subtract;
现在我们就可以使用AddCommand和SubtractCommand来代替Command::Add和Command::Subtract,从而让代码更容易阅读。
never类型
never类型是一种特殊类型,它表示永远不会返回任何值的类型。never类型通常用于那些永远不会返回的函数,比如panic!()函数。
panic!()函数会立即终止程序,因此它永远不会返回任何值。因此,panic!()函数的类型是never。
我们也可以自己定义never类型的函数。比如,我们可以定义一个名为infinite_loop()的函数,这个函数会无限循环,永远不会返回任何值:
fn infinite_loop() -> ! {
loop {}
}
动态大小类型
动态大小类型是指大小可以在运行时确定的类型。Rust中,数组和切片都是动态大小类型。
动态大小类型可以使用[]符号来定义。比如,我们可以定义一个名为numbers的数组,它可以存储10个i32值:
let numbers: [i32; 10] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
我们也可以定义一个名为slice的切片,它可以存储任意数量的i32值:
let slice: &[i32] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
结论
本节我们介绍了一些比较高级的类型特性,包括newtype模式、类型别名、never类型、动态大小类型。这些类型特性可以帮助我们编写更强大和灵活的Rust代码。