强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Rust 系统编程语言完全教程 / 第05章:类型系统

第05章:类型系统

5.1 基本类型概览

Rust 是静态类型语言,所有类型在编译时确定。

标量类型(Scalar)

类型分类类型大小示例
整数i8, i16, i32, i64, i128, isize1~16 字节42, -1
无符号整数u8, u16, u32, u64, u128, usize1~16 字节42u8, 0xff
浮点数f32, f644/8 字节3.14, 2.0e-3
布尔bool1 字节true, false
字符char4 字节(Unicode)'A', '中', '🎯'

复合类型(Compound)

类型说明示例
元组(Tuple)固定长度,不同类型(1, "hello", 3.14)
数组(Array)固定长度,相同类型[1, 2, 3]

5.2 元组(Tuple)

创建与访问

fn main() {
    // 创建元组
    let tup: (i32, f64, &str) = (500, 6.4, "hello");

    // 使用模式解构(destructuring)
    let (x, y, z) = tup;
    println!("x={}, y={}, z={}", x, y, z);

    // 使用索引访问(从 0 开始,使用点号+索引)
    println!("第一个元素: {}", tup.0);
    println!("第二个元素: {}", tup.1);
    println!("第三个元素: {}", tup.2);
}

单元类型(Unit Type)

fn main() {
    // 单元类型 () 是空元组,表示"无值"
    let unit: () = ();

    // 不返回值的函数隐式返回 ()
    fn do_nothing() {
        // 隐式返回 ()
    }

    let result = do_nothing();
    println!("单元类型: {:?}", result); // ()
}

元组作为函数返回值

/// 同时返回商和余数
fn divide(dividend: i32, divisor: i32) -> (i32, i32) {
    let quotient = dividend / divisor;
    let remainder = dividend % divisor;
    (quotient, remainder)
}

fn main() {
    let (q, r) = divide(17, 5);
    println!("17 ÷ 5 = {}{}", q, r);

    // 可以忽略不需要的返回值
    let (_, remainder) = divide(17, 5);
    println!("余数: {}", remainder);
}

嵌套元组

fn main() {
    let nested = ((1, 2), (3, 4), (5, 6));
    println!("nested.0.1 = {}", nested.0.1); // 2
    println!("nested.2.0 = {}", nested.2.0); // 5
}

注意: 元组最多支持 12 个元素实现 Display trait,超过 12 个元素只能用 Debug 输出({:?})。


5.3 数组(Array)

基本用法

fn main() {
    // 显式类型标注
    let a: [i32; 5] = [1, 2, 3, 4, 5];

    // 类型推断
    let b = [1, 2, 3, 4, 5];

    // 初始化相同值的数组
    let zeros = [0; 10]; // [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

    println!("a = {:?}", a);   // [1, 2, 3, 4, 5]
    println!("b = {:?}", b);   // [1, 2, 3, 4, 5]
    println!("zeros = {:?}", zeros);

    // 访问元素(从 0 开始)
    println!("a[0] = {}", a[0]); // 1
    println!("a[4] = {}", a[4]); // 5

    // 越界访问会 panic(运行时检查)
    // println!("{}", a[10]); // panic: index out of bounds
}

可变数组

fn main() {
    let mut arr = [1, 2, 3, 4, 5];
    println!("修改前: {:?}", arr);

    arr[2] = 30;
    println!("修改后: {:?}", arr); // [1, 2, 30, 4, 5]
}

数组切片

fn main() {
    let arr = [1, 2, 3, 4, 5];

    // 切片:引用数组的一部分
    let slice = &arr[1..3]; // [2, 3](索引 1 和 2,不包含 3)
    println!("切片: {:?}", slice);

    let full = &arr[..];    // 全部元素
    println!("全部: {:?}", full);

    let from_two = &arr[2..]; // 从索引 2 到末尾
    println!("从索引2: {:?}", from_two);
}

数组常用方法

fn main() {
    let arr = [3, 1, 4, 1, 5, 9, 2, 6];

    println!("长度: {}", arr.len());           // 8
    println!("是否为空: {}", arr.is_empty());  // false
    println!("包含5: {}", arr.contains(&5));   // true

    // 切片方法
    let slice = &arr[..];
    let mut sorted = arr.to_vec();
    sorted.sort();
    println!("排序后: {:?}", sorted);          // [1, 1, 2, 3, 4, 5, 6, 9]

    // 迭代
    for (i, val) in arr.iter().enumerate() {
        print!("arr[{}] = {}  ", i, val);
    }
    println!();

    // 查找
    let position = arr.iter().position(|&x| x == 5);
    println!("5的位置: {:?}", position);       // Some(4)
}

注意: Rust 数组是栈上分配的,大小在编译时确定。如果需要动态大小的集合,使用 Vec<T>


5.4 切片(Slice)

切片是对连续内存区域的引用(&[T]&str),不拥有数据。

字符串切片(&str)

fn main() {
    let s = String::from("hello world");

    // 获取字符串切片
    let hello = &s[0..5];   // "hello"
    let world = &s[6..11];  // "world"

    println!("{} {}", hello, world);

    // 语法糖
    let hello = &s[..5];    // 等价于 &s[0..5]
    let world = &s[6..];    // 等价于 &s[6..11]
    let full = &s[..];      // 整个字符串

    println!("{} {} {}", hello, world, full);
}

注意: 字符串切片的索引必须落在有效的 UTF-8 字符边界上。对中文等多字节字符,错误的索引会导致 panic。

数值切片

fn first_element(slice: &[i32]) -> Option<i32> {
    if slice.is_empty() {
        None
    } else {
        Some(slice[0])
    }
}

fn sum(slice: &[i32]) -> i32 {
    slice.iter().sum()
}

fn main() {
    let arr = [1, 2, 3, 4, 5];

    // 数组自动转为切片引用
    println!("首元素: {:?}", first_element(&arr));
    println!("总和: {}", sum(&arr));

    // Vec 也可以转为切片
    let v = vec![10, 20, 30];
    println!("Vec总和: {}", sum(&v));

    // 部分切片
    println!("部分总和: {}", sum(&arr[1..4])); // 2+3+4=9
}

可变切片

fn fill_with_value(slice: &mut [i32], value: i32) {
    for item in slice.iter_mut() {
        *item = value;
    }
}

fn main() {
    let mut arr = [0; 5];
    println!("填充前: {:?}", arr);

    fill_with_value(&mut arr, 42);
    println!("填充后: {:?}", arr); // [42, 42, 42, 42, 42]

    // 也可以对部分切片可变引用
    let slice = &mut arr[1..4];
    slice[0] = 100;
    println!("部分修改后: {:?}", arr); // [42, 100, 42, 42, 42]
}

5.5 字符串(String 与 &str)

Rust 有两种主要的字符串类型,这是初学者最容易困惑的地方之一。

String vs &str

特性String&str
所有权拥有数据借用数据(引用)
存储位置堆上可在栈、堆或静态区
可变性可变(可 push、pop)不可变
大小动态可增长固定长度
创建方式String::from()字面量 "hello"
类型大小3 个 usize(指针+长度+容量)2 个 usize(指针+长度)
fn main() {
    // &str:字符串切片,字面量类型
    let s1: &str = "hello";

    // String:可增长的字符串
    let mut s2: String = String::from("hello");

    // 修改 String
    s2.push_str(", world!");
    println!("{}", s2); // hello, world!

    // String → &str(自动解引用 Deref)
    let s3: &str = &s2;
    println!("{}", s3);

    // &str → String
    let s4: String = s1.to_string();
    let s5: String = String::from(s1);
    let s6: String = "hello".to_owned();

    println!("s4={}, s5={}, s6={}", s4, s5, s6);
}

字符串常用操作

fn main() {
    let mut s = String::from("Hello");

    // 追加
    s.push(' ');           // 追加单个字符
    s.push_str("World");   // 追加字符串切片
    println!("{}", s);     // Hello World

    // 替换
    let s2 = s.replace("World", "Rust");
    println!("{}", s2);    // Hello Rust

    // 包含检查
    println!("包含Hello: {}", s.contains("Hello")); // true

    // 分割
    let words: Vec<&str> = s.split_whitespace().collect();
    println!("单词: {:?}", words);

    // 大小写
    println!("大写: {}", s.to_uppercase());
    println!("小写: {}", s.to_lowercase());

    // 去除空白
    let padded = "  hello  ";
    println!("左trim: '{}'", padded.trim_start());
    println!("右trim: '{}'", padded.trim_end());
    println!("trim:   '{}'", padded.trim());

    // 格式化
    let name = "Rust";
    let version = 2024;
    let msg = format!("{} Edition {}", name, version);
    println!("{}", msg);

    // 拼接
    let mut result = String::new();
    result.push_str("Hello");
    result.push_str(", ");
    result.push_str("World");
    println!("{}", result);
}

字符串与 UTF-8

fn main() {
    let s = "你好,世界!";

    // len() 返回字节数,不是字符数
    println!("字节数: {}", s.len());        // 18(每个中文字符 3 字节)
    println!("字符数: {}", s.chars().count()); // 6

    // 遍历字符
    for c in s.chars() {
        print!("{} ", c);
    }
    println!();

    // 遍历字节
    for b in s.bytes() {
        print!("{} ", b);
    }
    println!();

    // ⚠️ 直接索引字符串不支持
    // let c = s[0]; // ❌ 编译错误

    // 正确获取第 n 个字符
    let third_char = s.chars().nth(2);
    println!("第三个字符: {:?}", third_char); // Some(',')
}

注意: Rust 字符串不支持直接索引 s[0],因为 UTF-8 中每个字符可能占用不同字节数。使用 .chars().bytes() 迭代。

字符串与其他类型的转换

fn main() {
    // 数字 → 字符串
    let n = 42;
    let s1 = n.to_string();
    let s2 = format!("{}", n);
    let s3 = i32::to_string(&n);
    println!("{} {} {}", s1, s2, s3);

    // 字符串 → 数字
    let s = "42";
    let n: i32 = s.parse().unwrap();
    let n2: i32 = s.parse::<i32>().unwrap();
    let n3 = s.parse::<i32>().expect("解析失败");
    println!("{} {} {}", n, n2, n3);

    // 安全转换
    let s = "not a number";
    match s.parse::<i32>() {
        Ok(n) => println!("解析成功: {}", n),
        Err(e) => println!("解析失败: {}", e),
    }

    // Vec<char> → String
    let chars = vec!['H', 'e', 'l', 'l', 'o'];
    let s: String = chars.into_iter().collect();
    println!("{}", s); // Hello
}

5.6 枚举基础

简单枚举

#[derive(Debug)]
enum Direction {
    North,
    South,
    East,
    West,
}

fn main() {
    let dir = Direction::North;
    println!("方向: {:?}", dir);

    // 使用 match 匹配
    match dir {
        Direction::North => println!("向北"),
        Direction::South => println!("向南"),
        Direction::East => println!("向东"),
        Direction::West => println!("向西"),
    }
}

带数据的枚举

#[derive(Debug)]
enum Shape {
    Circle(f64),                    // 半径
    Rectangle(f64, f64),            // 宽、高
    Triangle(f64, f64, f64),        // 三条边
}

impl Shape {
    fn area(&self) -> f64 {
        match self {
            Shape::Circle(r) => std::f64::consts::PI * r * r,
            Shape::Rectangle(w, h) => w * h,
            Shape::Triangle(a, b, c) => {
                // 海伦公式
                let s = (a + b + c) / 2.0;
                (s * (s - a) * (s - b) * (s - c)).sqrt()
            }
        }
    }

    fn name(&self) -> &str {
        match self {
            Shape::Circle(_) => "圆形",
            Shape::Rectangle(_, _) => "矩形",
            Shape::Triangle(_, _, _) => "三角形",
        }
    }
}

fn main() {
    let shapes: Vec<Shape> = vec![
        Shape::Circle(5.0),
        Shape::Rectangle(4.0, 6.0),
        Shape::Triangle(3.0, 4.0, 5.0),
    ];

    for shape in &shapes {
        println!("{}的面积: {:.2}", shape.name(), shape.area());
    }
}

5.7 常见的预定义枚举

Option

表示可能有值或无值(替代 null):

fn find_first_even(numbers: &[i32]) -> Option<i32> {
    for &n in numbers {
        if n % 2 == 0 {
            return Some(n);
        }
    }
    None
}

fn main() {
    let numbers = vec![1, 3, 5, 8, 9];

    match find_first_even(&numbers) {
        Some(n) => println!("第一个偶数: {}", n),
        None => println!("没有偶数"),
    }

    // Option 的常用方法
    let some_value: Option<i32> = Some(42);
    let none_value: Option<i32> = None;

    // unwrap_or: 提供默认值
    println!("{}", some_value.unwrap_or(0));  // 42
    println!("{}", none_value.unwrap_or(0));  // 0

    // is_some / is_none
    println!("some_value.is_some(): {}", some_value.is_some()); // true
    println!("none_value.is_none(): {}", none_value.is_none()); // true

    // map: 转换内部值
    let doubled = some_value.map(|x| x * 2);
    println!("doubled: {:?}", doubled); // Some(84)

    // and_then (flat_map)
    let result = some_value.and_then(|x| {
        if x > 0 { Some(x * 2) } else { None }
    });
    println!("result: {:?}", result); // Some(84)
}

Result<T, E>

表示成功或失败:

fn parse_number(s: &str) -> Result<i32, String> {
    s.parse::<i32>().map_err(|e| format!("解析失败: {}", e))
}

fn main() {
    // 成功情况
    match parse_number("42") {
        Ok(n) => println!("解析成功: {}", n),
        Err(e) => println!("错误: {}", e),
    }

    // 失败情况
    match parse_number("abc") {
        Ok(n) => println!("解析成功: {}", n),
        Err(e) => println!("错误: {}", e),
    }

    // Result 的常用方法
    let ok: Result<i32, &str> = Ok(42);
    let err: Result<i32, &str> = Err("出错了");

    println!("ok.unwrap_or(0) = {}", ok.unwrap_or(0));       // 42
    println!("err.unwrap_or(0) = {}", err.unwrap_or(0));     // 0

    // map / and_then
    let mapped = ok.map(|x| x * 2);
    println!("mapped = {:?}", mapped); // Ok(84)

    // map_err
    let with_err = err.map_err(|e| format!("自定义: {}", e));
    println!("with_err = {:?}", with_err);
}

5.8 类型别名

// 给复杂类型起别名
type Kilometers = i32;
type Thunk = Box<dyn Fn() + Send + 'static>;
type Result<T> = std::result::Result<T, std::io::Error>;

fn main() {
    // 类型别名只是别名,不是新类型
    let distance: Kilometers = 5;
    let n: i32 = 10;
    let sum = distance + n; // 可以混用
    println!("总和: {}", sum);
}

5.9 业务场景示例

API 响应类型

use std::collections::HashMap;

#[derive(Debug)]
enum ApiResponse {
    Success { data: String, code: u16 },
    Error { message: String, code: u16 },
    Redirect { url: String, code: u16 },
}

impl ApiResponse {
    fn status_code(&self) -> u16 {
        match self {
            ApiResponse::Success { code, .. } => *code,
            ApiResponse::Error { code, .. } => *code,
            ApiResponse::Redirect { code, .. } => *code,
        }
    }

    fn is_success(&self) -> bool {
        matches!(self, ApiResponse::Success { .. })
    }
}

fn fetch_user(id: u32) -> ApiResponse {
    if id == 0 {
        ApiResponse::Error {
            message: "无效的用户ID".to_string(),
            code: 400,
        }
    } else if id > 1000 {
        ApiResponse::Redirect {
            url: format!("/users/{}", id % 1000),
            code: 301,
        }
    } else {
        ApiResponse::Success {
            data: format!("用户 {} 的数据", id),
            code: 200,
        }
    }
}

fn main() {
    for id in [0, 1, 5000] {
        let response = fetch_user(id);
        println!("ID={}: {:?} (成功: {})", id, response, response.is_success());
    }
}

学生成绩统计

#[derive(Debug)]
enum Grade {
    Excellent,  // 优秀 (90-100)
    Good,       // 良好 (80-89)
    Average,    // 中等 (70-79)
    Pass,       // 及格 (60-69)
    Fail,       // 不及格 (<60)
}

impl Grade {
    fn from_score(score: f64) -> Self {
        match score as u32 {
            90..=100 => Grade::Excellent,
            80..=89 => Grade::Good,
            70..=79 => Grade::Average,
            60..=69 => Grade::Pass,
            _ => Grade::Fail,
        }
    }

    fn to_chinese(&self) -> &str {
        match self {
            Grade::Excellent => "优秀",
            Grade::Good => "良好",
            Grade::Average => "中等",
            Grade::Pass => "及格",
            Grade::Fail => "不及格",
        }
    }
}

fn main() {
    let scores = vec![95.0, 82.5, 73.0, 65.0, 42.0];

    for score in &scores {
        let grade = Grade::from_score(*score);
        println!("分数: {:.1} → 等级: {} ({})", score, grade.to_chinese(), grade:?);
    }
}

5.10 本章小结

要点说明
标量类型整数、浮点、布尔、字符
元组固定长度,不同类型,解构或索引访问
数组固定长度,相同类型,栈上分配
切片连续内存的引用,&[T]&str
String堆上分配,可增长,拥有所有权
&str字符串切片,借用,不可变
UTF-8Rust 字符串是 UTF-8 编码,不支持直接索引
Option替代 null,表示可能有值或无值
Result表示成功或失败的操作结果

扩展阅读

  1. Rust 字符串详解 — String 与 &str
  2. Rust 类型系统 — 完整类型参考
  3. UTF-8 编码 — 理解 Rust 字符串的基础
  4. Unicode in Rust — Unicode 处理工具