每日一R「07」类型系统(一)

244 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第07天,点击查看活动详情

在前面的课程中,我们学习了变量所有权、借用以及生命周期等 Rust 中非常重要且比较难懂的内容。今天我们将开始跟着老师一起学习 Rust 中的类型系统。

01-类型系统基本概念

从机器码角度是不存在类型的,与指令交互的是寄存器或内存中的数据流。

类型系统是高级语言中的概念,它是对值的区分,包含了值在内存中的长度、对齐方式以及值允许的操作方式等。因此,类型系统可以看作是一种工具,在编译期的静态检查和运行期的动态检查中,来保证处理的数据是开发者期望的类型。

类型系统是对类型进行定义、检查和处理的系统。类型系统可以划分为:

  • 按定义后类型是否可以隐式转换,类型系统可分为强类型系统和弱类型系统,前者不允许隐式转换,后者允许隐式转换。
  • 按照类型检查的时机,可以分为静态类型系统和动态类型系统,前者检查发生在编译期,后者检查发生在运行期。

类型系统中有一个非常重要的概念,多态,即在使用相同类型的接口时,不同类型的对象,会采用不同的实现。不同的类型系统实现多态的方式也不尽相同:

  • 对于动态类型系统,多态通过鸭子类型(duck type)实现。
  • 对于静态类型系统,多态通过参数多态、特设多态和子类型多态实现。
    • 其中,参数多态指,函数操作的是满足某个约束(例如实现了某个 trait)的参数,而非具体的类型;
    • 特设多态是指同一种行为(例如加法)可以有多个不同实现的多态,例如面向对象语言中的重载;
    • 子类型多态指运行时子类型可以被当成父类型使用,例如面向对象语言中的里氏替换原则

01.1-Rust 中多态是如何实现的

Rust 是一个静态强类型语言,它对参数多态的支持通过泛型来实现,对特设多态的支持通过 trait 来实现,对子类型多态的支持通过 trait object 来实现。

类型安全,从内存角度看,是指代码只能按照被允许的方法、访问它被授权访问的内存。Rust 下,类型安全有更严格地限制,即代码只能按照被允许的方法和被允许的权限,访问它被授权访问的内存。

为实现如此严格的要求,Rust 中规定一切代码块(出了 let 等定义性语句外)都是表达式。表达式是有类型的,所以类型无处不在。表达式是能够计算出值的,那下面这段表达式的值是什么?

if something_true {
    do_corresponding_work();
}

if something_ture {
    do_corresponding_work();
    30
}

Rust 中表达式的值是代码块中最后一个表达式的值。上述代码中,第一个 if 的返回值是 ()。() 是 Rust 中的一个特别的元素,也称为 unit,它的类型为 (),值也为 (),且在内存中并不占空间。第二个 if 的返回值是 30。

02-泛型(参数多态)

02.1-泛型数据结构

常见的泛型数据结构:Option、Result<T, E>、Cow

pub enum Option<T> {
    None,
    Some(T),
}
pub enum Result<T, E> {
    Ok(T),
    Err(E),
}
pub enum Cow<'a, T> 
where
    T: 'a + ToOwned + ?Sized, 
 {
    Borrowed(&'a T),
    Owned(<T as ToOwned>::Owned),
}

Option 中的 T 未作任何约束,即任何类型都可以。 Cow 中 B 是有约束的,出了必须具备申明周期 ‘a 外,还需要满足:实现了 ToOwned trait,可以是 ?Size 可变大小的类型。

02.2-泛型函数

struct Point<T, U> {
    x: T,
    y: U,
}

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

与泛型数据结构一样,泛型参数需要提前声明,即 impl<T, U>。此时的 Point<T, U> 已不再是泛型声明,而是一个具体的数据结构。

02.3-单态化

Rust 在编译期间会将泛型转换为具体地类型,这个过程称为单态化。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程。正是单态化使得 Rust 运行时的效率不会丧失,但同时带来的是编译时间变长、可执行文件体积变大。

以标准库中 Option 为例,如果按照如下方式使用:

let integer = Some(5);
let float = Some(5.0);

经过编译编译,会产生如下对应的代码,这个过程就是单态化过程:

enum Option_i32 {
    Some(i32),
    None,
}

enum Option_f64 {
    Some(f64),
    None,
}

fn main() {
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}

本节课程链接:《12|类型系统:Rust的类型系统有什么特点?


历史文章推荐