Rust 入门丨01 类型系统概述

在 Rust 编程世界中,绝大部分的特性和能力都离不开 Rust 强大的类型系统,所以在这个系列的第 1 篇我们先来对 Rust 的类型系统做一个全局概述,希望可以帮助你建立起对 Rust 的基本印象。在后续的实践过程中,我推荐你可以经常回来思考下为什么 Rust 要构建这样的类型系统,在每一个分支点是如何做出决策的,这些决策又体现在代码的哪些地方。相信这样可以帮助你更好地入门 Rust。

废话不多说,进入正文。

什么是类型系统?

在进入 Rust 类型系统讨论之前,我们先尝试占在更高的角度,即整个编程语言界的角度去思考,什么是类型系统?

编程语言的类型系统是指一套规则,用于定义和管理程序中数据的类型。类型系统的主要目的是帮助捕获程序中的错误,提高代码的可靠性和可读性。

类型系统可以根据多种特性进行分类,主要包括以下几个方面:

  1. 静态类型和动态类型
    • 静态类型:在编译时检查变量类型。例如,Java、C++ 和 Haskell 都是静态类型语言。在这些语言中,变量的类型必须在编译时确定,这样可以在编译阶段捕获许多类型错误。
    • 动态类型:在运行时检查变量类型。例如,Python、Ruby 和 JavaScript 是动态类型语言。在这些语言中,变量的类型是在程序运行时确定的,这提供了更大的灵活性,但也可能导致运行时错误。
  2. 强类型和弱类型
    • 强类型:严格限制不同类型之间的操作。例如,Python 和 Java 是强类型语言。强类型系统通常不允许隐式类型转换,这意味着在进行不同类型之间的操作时,必须显式地进行类型转换。
    • 弱类型:允许更多隐式类型转换。例如,JavaScript 和 Perl 是弱类型语言。在这些语言中,编译器或解释器会在需要时自动进行类型转换,这可能导致难以预料的行为。
  3. 显式类型和隐式类型
    • 显式类型:程序员必须明确声明每个变量的类型。例如,Java 和 C++ 要求在声明变量时指定其类型。
    • 隐式类型:编译器或解释器会根据上下文自动推断变量的类型。例如,Python 和 JavaScript 使用隐式类型,程序员不需要显式声明变量类型。
  4. 子类型和多态
    • 子类型:一种类型系统允许一种类型作为另一种类型的子集。例如,在面向对象编程中,子类是父类的子类型。
    • 多态:允许一个接口被多种不同类型实现。多态性有多种形式,包括参数多态(如泛型)和子类型多态(如继承)。
  5. 类型推断
    • 类型推断是指编译器自动确定表达式的类型,而无需明确的类型注释。例如,Haskell 和 Scala 使用类型推断来减少程序员的负担,同时保持静态类型的安全性。
  6. 代数数据类型和类型构造
    • 代数数据类型(ADT)是通过组合其他类型来构造新类型的机制,常见于函数式编程语言,如 Haskell 和 OCaml。ADT 包括产品类型(如元组)和和类型(如枚举)。
  7. 结构类型和名义类型
    • 结构类型:基于对象的结构来确定类型的兼容性。例如,TypeScript 和 Go 使用结构类型系统。
    • 名义类型:基于名称来确定类型的兼容性。例如,Java 和 C++ 使用名义类型系统。

这里我梳理了一张图,供你参考:

编程语言类型系统

注:本图参考了陈天老师在 Rust 训练营课程上提供的教案并进行了增改。

Rust 类型系统

Rust 为了在提供高性能的同时保证内存安全和线程安全,花了大量力气构建了一个强大的类型系统。

基于之前提到的七个方面,我们来梳理下 Rust 的类型系统:

  1. 静态类型:Rust 是静态类型语言,这意味着变量的类型在编译时就被确定。这种设计使得 Rust 在编译阶段就可以捕获许多类型错误,从而提高代码的安全性和性能。

    1
    2
    3
    fn main() {
    let x: i32 = 10; // 明确指定类型
    }
  2. 强类型:Rust 是强类型语言,它严格限制不同类型之间的操作。Rust 不允许隐式类型转换(例如,不能自动将整数转换为浮点数),需要显式地使用 as 进行类型转换。这种严格性有助于避免许多常见的编程错误。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    fn main() {
    let x: i32 = 5;
    let y: f64 = 10.0;

    // 错误:不能将 i32 隐式转换为 f64
    // let sum = x + y;

    // 正确:需要显式转换
    let sum = x as f64 + y;
    println!("Sum = {}", sum);
    }
  3. 显式类型和类型推断:虽然 Rust 是显式类型语言,要求在某些情况下声明变量类型,但它也具有强大的类型推断能力。编译器可以根据上下文推断出大多数变量的类型,减少了程序员的负担。例如:

    1
    2
    let mut v = vec![];
    v.push(5u8); // 结合这里,Rust 编译器可以推断出 v 的类型是 Vec<u8>
  4. 子类型和多态:Rust 支持泛型和 trait,这是一种多态性的实现方式。trait 类似于接口,允许定义类型可以实现的一组方法。泛型允许定义函数、结构体和枚举时使用占位类型,从而实现代码的重用和灵活性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 这里 std::fmt::Display 就是一个 trait,目前,你可以先简单理解为 trait 就是接口
    fn print_value<T: std::fmt::Display>(value: T) {
    println!("{}", value);
    }

    fn main() {
    print_value(42); // 42 默认为 i32,标准库为其是实现了 Display trait
    print_value("Hello, world!"); // &str 也实现了 Display trait
    }
  5. 类型推断:Rust 的类型推断系统非常强大,能够根据代码上下文自动推断变量和表达式的类型。这使得代码更简洁,同时保持了类型安全性。

  6. 代数数据类型和类型构造:Rust 支持代数数据类型,通过枚举(enum)和结构体(struct)来实现。枚举允许定义一个类型,该类型可以是几种不同的变体之一,每个变体可以携带不同的数据。

    1
    2
    3
    4
    enum Option<T> {
    Some(T),
    None,
    }
  7. 结构类型和名义类型:Rust 使用名义类型系统。每个类型都有一个显式的名称,类型的兼容性基于名称而不是结构。这意味着即使两个结构体有相同的字段,它们也被视为不同的类型,除非通过特征或显式转换来实现兼容性。

除此之外,Rust 的类型系统还提供了其他非常强大且有用的特效,如所有权和借用、生命周期以及模式匹配。

  • 所有权和借用(Ownership and Borrowing)

    • Rust 的类型系统与其所有权模型紧密结合。所有权模型通过所有权、借用和生命周期的概念来管理内存,从而在无垃圾回收器的情况下确保内存安全。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    fn main() {
    let s = String::from("Hello");
    let len = calculate_length(&s); // 借用
    println!("The length of '{}' is {}.", s, len);
    }

    fn calculate_length(s: &String) -> usize {
    s.len()
    }
  • 生命周期(Lifetimes)

    • Rust 使用生命周期标注来跟踪引用的有效范围,确保引用在使用时始终有效。这是 Rust 类型系统中一个独特的特性,帮助防止悬空引用和数据竞争。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
    x
    } else {
    y
    }
    }

    fn main() {
    let string1 = String::from("long string is long");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is '{}'", result);
    }
  • 模式匹配

    • Rust 提供强大的模式匹配功能,尤其是在处理枚举和复杂数据结构时,使得代码更具表达力和安全性。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
    }

    fn process_message(msg: Message) {
    match msg {
    Message::Quit => {
    println!("Quit the application");
    }
    Message::Move { x, y } => { // 模式匹配能根据数据类型直接拆解出来,使用起来非常方便
    println!("Move to coordinates: ({}, {})", x, y);
    }
    Message::Write(text) => {
    println!("Text message: {}", text);
    }
    Message::ChangeColor(r, g, b) => {
    println!("Change color to RGB({}, {}, {})", r, g, b);
    }
    }
    }

Rust 的类型系统通过上述特性实现了高效、安全和灵活的编程模型,适合系统编程和高性能应用。它在编译期捕获许多潜在错误,使得运行时更为安全可靠。

当然,如果你之前没有学习过 Rust,那这些概念和代码对你来说大概率是云里雾里,不要着急,我们先建立起一个大概的印象就行了。这里我针对 Rust 类型系统梳理了一张图,你可以在以后的学习中时常回来看看:

Rust 类型系统

注:本图参考了陈天老师在 Rust 训练营课程上提供的教案并进行了增改。

本篇就到这里,下篇我们将介绍 Rust 的数据类型,enjoy coding~


Rust 入门丨01 类型系统概述
https://hedon.top/2024/11/28/rust-01-type-system/
Author
Hedon Wang
Posted on
2024-11-28
Licensed under