Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

数据类型

1. 标量类型(Scalar Types)

标量类型代表一个单一的值。

A. 整数类型 (Integers)

Rust 提供了非常精细的整数控制,分为有符号(i)和无符号(u)。

长度有符号 (Signed)无符号 (Unsigned)范围 (n 为位数)
8-biti8u8i8: -(2^(8-1)) ~ 2^(8-1)-1,
 u8: 0 ~ 2^8-1
16-biti16u16i16: -(2^(16-1)) ~2^(16-1)-1,
u16: 0 ~2^16-1
32-biti32 (默认)u32i32: -(2^(32-1)) ~2^(32-1)-1,
u32: 0 ~ 2^32-1
64-biti64u64i64: -(2^(64-1)) ~2^(64-1)-1,
u64: 0 ~ 2^64-1
128-biti128u128i128:-(2^(128-1)) ~2^(128-1)-1,
u128: 0 ~ 2^128-1
arch (平台相关)isizeusize取决于计算机架构 (64位或32位)
  • usize / isize 的用途:常用于集合索引、切片范围、长度(如 len())、以及与内存地址大小相关的场景。

  • 整数字面值:可以使用 _ 分隔增强可读性,如 1_000_000。支持 0x(十六进制)、0o(八进制)、0b(二进制)。

  • 类型后缀:可在字面量后加后缀明确类型,如 10u820i64

  • 溢出行为

    • debug 构建:整数溢出会触发 panic!
    • release 构建:默认按补码进行回绕(wrapping)
    • 常见策略方法:wrapping_addchecked_addoverflowing_addsaturating_add
fn main() {
    let a: u8 = 250;
    assert_eq!(a.wrapping_add(10), 4);
    assert_eq!(a.checked_add(10), None);
}

B. 浮点类型 (Floating-Point)

Rust 遵循 IEEE-754 标准:

  • f32:单精度。
  • f64:双精度(默认,因为在现代 CPU 上速度几乎与 f32 一样快,但精度更高)。

浮点数相关注意点:

  • 精度误差:尽量避免直接用 == 比较业务浮点值,常用误差范围比较。
  • NaNNaN != NaN,这会影响比较与排序逻辑。
fn main() {
    let x = 0.1f64 + 0.2;
    assert!((x - 0.3).abs() < 1e-10);
}

C. 布尔与字符

  • booltruefalse。通常占用 1 个字节。
  • char:占用 4 个字节,代表一个 Unicode 标量值,可以表示中文、日文、表情符号 (Emoji) 等。char 不是 UTF-8 的“一个字节”,也不等同于字符串的长度单位。

2. 复合类型 (Compound Types)

将多个值组合成一个类型。

A. 元组 (Tuple)

  • 特点:长度固定,各元素类型可以不同
  • 定义let tup: (i32, f64, u8) = (500, 6.4, 1);
  • 访问:使用点号,如 tup.0
  • 解构:可用模式匹配直接拆开。
fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);
    let (x, y, z) = tup;
}
  • 单元类型 ():不包含任何值的元组,常作为表达式的默认返回值或占位类型。

B. 数组 (Array)

  • 特点:长度固定,各元素类型必须相同
  • 定义let a = [1, 2, 3, 4, 5];
  • 类型与长度声明let a: [i32; 5] = [1, 2, 3, 4, 5];
  • 重复初始化let a = [0u8; 1024];
  • 存储:数组是固定大小的值类型,作为局部变量时通常位于栈上;若被 Box 等包裹,则数据会位于堆上。

数组访问与边界:

  • a[i]:越界会 panic!
  • a.get(i):返回 Option<&T>,更安全
fn main() {
    let a = [1, 2, 3];
    assert_eq!(a.get(10), None);
}

3. 序列与字符串 (Sequences & Strings)

这一部分常见困惑点集中在 UTF-8 与内存分配方式。

A. 字符串 (Strings)

Rust 核心语言层面有 str(动态大小类型,通常以引用切片 &str 出现),标准库提供可增长的 String

  • String:拥有所有权,数据在堆上,可增长。内部包含指针、长度、容量等信息。
  • &str:字符串切片,是对一段 UTF-8 字节序列的借用视图。引用本身在栈上,实际数据可能来自只读区(字符串字面量)、栈或堆。

常见转换:

fn main() {
    let s1: String = "hello".to_string();
    let s2: &str = &s1;           // &String 自动解引用成 &str
    let s3: String = s2.to_owned(); // 或 s2.to_string()
}

字符串索引与切片规则:

  • Rust 不支持 s[0] 直接索引字符,因为 UTF-8 下“字符边界”与字节下标不总一致。
  • &s[a..b] 必须落在 UTF-8 字符边界,否则运行时会 panic!
fn main() {
    let s = "中文";
    let ok = &s[0..3]; // "中" 占 3 个字节
}

B. 切片 (Slices)

切片引用连续的一段序列,而不是整个集合。

  • 数组/向量切片&[T]
  • 字符串切片&str(本质上也是切片)

示例:

fn main() {
    let a = [10, 20, 30, 40, 50];
    let slice = &a[1..3]; // &[20, 30]
}

切片是一种“胖指针”,通常包含地址与长度信息,因此可以安全地携带边界。


4. 标准库集合 (Standard Collections)

虽然属于标准库,但它们在实际开发中几乎被当作基础类型使用。

A. Vec<T> (Vector)

  • 动态数组,在堆上分配,可扩容。
  • 常见 API:pushpoplencapacityget、切片 &v[a..b] 等。
  • 预分配容量:Vec::with_capacity(n) 可减少扩容次数。
fn main() {
    let mut v = Vec::new();
    v.push(1);
    v.extend([2, 3, 4]);
    println!("{:?}", v);

    let first = v.get(0);   // Option<&i32>
    let part = &v[1..3];    // &[i32]
    println!("{:?}", part);
}

B. HashMap<K, V>

  • 键值对映射结构。
  • 常用 entry 模式在“插入或更新”时更方便。
fn main() {
    use std::collections::HashMap;

    let mut m = HashMap::new();
    m.insert("a", 1);
    println!("{:?}", m);

    *m.entry("a").or_insert(0) += 1;
    println!("{:?}", m);
}

5. 特殊/底层类型

A. 枚举 (Enums)

枚举用于表示“一组有限的可能取值”。Rust 的枚举非常强大:每个变体(variant)不仅能表示不同分支,还能携带不同类型的数据,因此很适合用来建模状态机、协议消息、错误类型等。

  1. 基本定义与使用
enum Direction {
    Up,
    Down,
    Left,
    Right,
}
fn move_step(d: Direction) {
    match d {
        Direction::Up => println!("up"),
        Direction::Down => println!("down"),
        Direction::Left => println!("left"),
        Direction::Right => println!("right"),
    }
}

fn main() {
    let d = Direction::Up;
    move_step(d);
}

2)变体携带数据

枚举变体可以携带数据,且不同变体携带的数据类型可以不同:

enum Message {
    Quit,
    Move { x: i32, y: i32 },  // 结构体风格
    Write(String),            // 元组风格
}
fn main() {
    let m1 = Message::Quit;
    let m2 = Message::Move { x: 3, y: 4 };
    let m3 = Message::Write("hi".to_string());
}

B. 结构体 (Structs)

结构体用于把多个字段组合成一个自定义类型,是“组织数据”的核心方式之一。

  1. 具名字段结构体(最常用)
struct User {
    name: String,
    age: u8,
    active: bool,
}
fn main() {
    let u = User {
        name: "Alice".to_string(),
        age: 18,
        active: true,
    };
    //访问字段用点号:
    println!("{:?}", u.name);
}

2)结构体与所有权的直观规则

字段类型如果是 String、Vec <T> 等“拥有型”,把结构体赋值给新变量时默认会发生移动(move)。 想继续使用旧值通常需要借用(&User)或让字段可复制(如 u32)或显式 clone()。

struct User {
    name: String,
    age: u8,
    active: bool,
}
fn main() {
    let u1 = User { name: "A".to_string(), age: 1, active: true };
    let u2 = u1;          // u1 被 move
    // println!("{}", u1.age); // 不能用
    println!("{}", u2.age);
}

C. 指针类型(Pointer Types)

根据底层的表现形式和抽象程度,可以将 Rust 的指针分为以下四大类:

1. 引用 (References) —— 最常用的指针

引用是 Rust 中最常见的指针形式,它们在底层表现为指向某个内存地址的指针。

  • &T(不可变引用) :指向类型为 T 的值,允许读取数据但不能修改。
  • &mut T(可变引用) :允许读取并修改指向的数据。
  • 内存表现
    • 普通引用 :对于已知大小的类型(如 i32),它是单字长的指针。
    • 切片引用(胖指针) :对于动态大小类型(如 &str&[T]),它由指针和长度组成,占用两个字长。

2. 原生指针 (Raw Pointers) —— 绕过安全的底层指针

原生指针与 C 语言的指针非常相似。它们在语法上定义为 *const T*mut T

  • 特点
    • 允许忽略借用规则,可以同时拥有多个指向同一位置的可变和不可变指针。
    • 不保证指向有效的内存,且允许为 null
    • 安全性 :解引用原生指针是不安全的,必须放在 unsafe 块中执行。
  • 用途 :主要用于与 C 语言交互(FFI)或编写底层高性能驱动。
fn main() {
    let mut num = 5;

    // 1. 从引用创建原生指针
    // 虽然创建原生指针是安全的,但后续使用是不安全的
    let r1 = &num as *const i32; // 不可变原生指针
    let r2 = &mut num as *mut i32; // 可变原生指针

    // 2. 解引用原生指针
    // 必须放在 unsafe 块中,否则编译器会报错
    unsafe {
        println!("r1 指向的值: {}", *r1);

    // 修改原生指针指向的数据
        *r2 = 10;
        println!("修改后 r2 指向的值: {}", *r2);
    }

    // 3. 创建一个指向任意内存地址的指针(慎用!)
    let address = 0x012345usize;
    let _r3 = address as *const i32;
}

3. 函数指针 (fn Pointer) —— 代码地址的载体

函数指针指向的是代码段中的函数入口地址,而不是堆栈上的数据。

  • 语法 :类型写作 fn(参数类型) -> 返回类型
  • 区别于闭包
    • 函数指针不捕获环境变量。
    • 它的长度始终是一个字长(存储地址)。
    • 它可以作为参数传递给其他函数,或者存储在数据结构中。
fn add_one(x: i32) -> i32 {
    x + 1
}

fn do_twice(f: fn(i32) -> i32, arg: i32) -> i32 {
    // 像正常函数一样通过指针调用
    f(arg) + f(arg)
}

fn main() {
    // 将函数名 add_one 隐式转换为函数指针类型
    let f: fn(i32) -> i32 = add_one;

    let answer = do_twice(f, 5);

    println!("计算结果为: {}", answer); // 输出: 12

    // 函数指针的大小验证
    println!("函数指针的大小: {} 字节", std::mem::size_of_val(&f));
}

4. 智能指针 (Smart Pointers) —— 携带元数据的指针

智能指针是拥有数据所有权的结构体,它们实现了 DerefDrop 特性。

A. Box<T> (堆空间分配)
  • 功能 :在堆上分配空间存储类型为 T 的值,并在栈上保留指针。
  • 场景 :当数据大小在编译时未知(如递归类型),或数据量巨大不适合在栈上拷贝时使用。
B. Rc<T> (引用计数指针)
  • 功能 :全称 Reference Counting,允许多个变量通过增加计数来共享同一个堆数据的所有权。
  • 场景 :用于单线程环境下,需要一个数据有多个所有者的复杂逻辑(如树或图的节点共享)。
C. Arc<T> (原子引用计数指针)
  • 功能 :Atomic Reference Counting,是 Rc<T> 的线程安全版本。
  • 场景 :多线程并发环境下,安全地共享同一份数据的所有权。
总结对比表
指针类型语法表示内存位置长度(64位)核心特性
引用&T/&mut T栈/堆8 或 16 字节安全借用,编译器检查生命周期
原生指针*const T/*mut T栈/堆8 字节不安全,类似 C 指针
函数指针fn(...) -> ...代码段8 字节指向函数地址
智能指针Box<T>/Rc<T>8 字节管理堆内存,提供自动清理逻辑

D. Option<T>Result<T, E>

Rust 不提供 null,用 Option<T> 表示“可能不存在”。

fn main() {
    let x: Option<i32> = Some(1);
    let y: Option<i32> = None;
}

错误处理通常用 Result<T, E>

fn parse(s: &str) -> Result<i32, std::num::ParseIntError> {
    s.parse()
}
fn main() {
    let result = parse("123");
    println!("{:?}", result);
}

E. Never 类型 (!)

! 表示永远不会返回的类型,常见于 panic!、无限循环等。

#![allow(unused)]
fn main() {
fn forever() -> ! {
    loop {}
}
}

6. 类型转换注意

Rust 不会进行隐式类型转换。不同整数类型之间的转换必须显式完成。

fn main() {
    let a: u8 = 10;
    let b: u32 = a as u32;
}

as 转换在整数之间可能发生截断或符号变化。需要“转换失败就返回错误/None”时可用 TryFrom/TryInto

fn main() {
    use std::convert::TryFrom;

    let x: i32 = 300;
    let y = u8::try_from(x); // Err(...)
}