返回

5分钟速读之Rust权威指南(四十一)高级类型

开发工具

<#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 for i32,这会导致当我们向一个期望Length类型的函数传递一个i32值时,编译器会自动将其转换成Length类型。也就是说,我们可以通过这种方式来让i32和Length类型做到相互兼容。当然,前提是需要让i32也实现From for i32。

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代码。