Skip to content

高级类型

type 类型别名

Rust 提供了声明类型别名(type alias)的能力,使用 type 关键字为现有类型赋予另一个名字.

定义语法

语法: type NewName = ExistingType;

rust
type Kilometers = i32;

这意味着 Kilometersi32 的类型同义词(synonym).你可以使用 Kilometers 来声明变量、函数参数或返回类型,就像使用 i32 一样.Kilometers 类型的值将被完全当作 i32 类型值来对待:

rust
type Kilometers = i32;

let x: i32 = 5;
let y: Kilometers = 5;

println!("x + y = {}", x + y);

因为 Kilometersi32 的别名,它们是同一类型,可以将 i32Kilometers 相加,也可以将 Kilometers 传递给接收 i32 参数的函数.但通过这种手段无法获得 newtype 模式所提供的类型检查的好处.换句话说,混用 Kilometersi32 的值时,编译器不会报错.

减少重复声明

类型别名的主要用途是减少重复声明,特别是当你有复杂的类型时.

rust
// 定义一个复杂的类型别名
type Thunk = Box<dyn Fn() + Send + 'static>;

// 使用类型别名来声明一个变量
let f: Thunk = Box::new(|| println!("hi"));

Result<T> 案例

Rust 标准库中定义了 Result<T, E> 枚举类型,用于表示可能成功(Ok)或失败(Err)的操作结果.对于许多函数来说,错误类型 E 通常是相同的,因此你可以使用类型别名来简化代码:

rust
// 定义一个错误类型
type MyResult<T> = Result<T, MyError>;

// 使用类型别名来简化函数返回类型
fn do_something() -> MyResult<()> {
  // ...
}

例如,std::io::Result<T>Result<T, std::io::Error> 的类型别名.不使用类型别名需要这样写:

rust
use std::fmt;
use std::io::Error;

pub trait Write {
  fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
  fn flush(&mut self) -> Result<(), Error>;
  fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
  fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
}

在标准库中,std::io 模块定义了类型别名 type Result<T> = std::result::Result<T, std::io::Error>; 来简化代码:

rust
use std::io::Result;

pub trait Write {
  fn write(&mut self, buf: &[u8]) -> Result<usize>;
  fn flush(&mut self) -> Result<()>;
  fn write_all(&mut self, buf: &[u8]) -> Result<()>;

  fn write_fmt(&mut self, fmt: fmt::Arguments) -> io::Result<()>;
}

注意:因为 Rust 默认引入 std::result::Result,如果你写了 use std::io::Result;,在该文件中 Result<T> 就会变成只带一个参数的别名,导致无法直接使用原生的 Result<T, E>.

! Never 类型

发散函数

发散函数(Diverging Functions)是指那些永远不会返回的函数.它们通常表现为:

  • 陷入无限循环(loop
  • 触发 panic!()
  • 调用 std::process::exit()

! 类型

Rust 使用特殊的 ! 类型(Never 类型)来表示发散函数的返回结果.

  • 核心作用:明确标记该代码路径永远不会产生结果
  • 万能适配:! 类型可以被强制转换为任意其他类型

代码示例

示例 1:显式定义发散函数

rust
fn forever() -> ! {
  loop {
    println!("无限循环,永不返回");
  }
}

示例 2:在 match 表达式中使用

由于 ! 能适配任何类型,你可以在任何需要返回值的地方使用 panic!():

rust
fn divide(a: i32, b: i32) -> i32 {
  match b {
    0 => panic!("除数不能为零"), // panic! 返回 !,自动适配 i32
    _ => a / b,
  }
}

常见的 ! 类型表达式

  • 程序中断:panic!()
  • 控制流:loop {}continuebreak
  • 程序退出:std::process::exit()
  • 占位占坑:todo!()unimplemented!()

这些表达式都具有 ! 类型,可以在任何需要类型的地方使用.

动态大小类型和 Sized trait

在 Rust 中,编译器必须知道每个变量占多少字节.但有些数据的大小只能在运行时才能确定,这就是动态大小类型(DST,Dynamically Sized Type)的问题.

痛点:有些数据的大小在编译时无法确定,必须等程序运行时才知道.例如 str——一段文字到底是 5 个字还是 500 个字?既然不知道大小,编译器就无法为其预分配内存.

动态大小类型 (DST)

  • 定义:在编译时无法确定大小,只能在运行时确定大小的类型(如 str[T]dyn Trait
  • 局限:不能直接创建 DST 类型的变量,也不能将其作为函数参数直接传递
  • 原则:Rust 要求同类型的所有实例必须占用相同大小的空间
rust
let s1: str = "Hello there!";      // 错误:12 字节
let s2: str = "How's it going?";   // 错误:15 字节

这两个 str 大小不同,所以不能直接创建 str 类型的变量.

指针后的 DST

DST 必须置于指针之后(如 &TBox<T>Rc<T>).指向 DST 的指针通常包含两部分信息:

  1. 数据的内存地址
  2. 元信息(如字符串长度、slice 长度或 trait 对象的虚表)

这被称为胖指针(Fat Pointer).

rust
let s1: &str = "Hello there!";      // &str 大小固定(16 字节)
let s2: &str = "How's it going?";   // &str 大小固定(16 字节)

此时 s1s2 都是 &str 类型,大小固定,编译器可以分配内存.

Sized Trait

为了处理 DST,Rust 提供了 Sized trait 来决定一个类型的大小是否在编译时可知.该 trait 会自动为所有在编译时大小已知的类型实现.

  • 作用:标记一个类型的大小在编译时是否可知
  • 自动实现:Rust 为所有编译时大小已知的类型自动实现 Sized
  • 隐式约束:所有泛型函数默认都带有 T: Sized 约束
rust
fn func<T>(t: T) {}
// 等同于 fn func<T: Sized>(t: T) {}

?Sized 语法

  • 含义:表示"可能不是 Sized"(T 可以是固定大小或动态大小)
  • 用途:放宽泛型约束,使其能接收 DST 类型
  • 要求:参数类型通常必须改为指针形式(如 &T
rust
fn func<T: ?Sized>(t: &T) {}
// 此时 T 可以是 str 或 dyn Trait

基于 MIT 协议发布