Rust 系统编程语言完全教程 / 第05章:类型系统
第05章:类型系统
5.1 基本类型概览
Rust 是静态类型语言,所有类型在编译时确定。
标量类型(Scalar)
| 类型分类 | 类型 | 大小 | 示例 |
|---|---|---|---|
| 整数 | i8, i16, i32, i64, i128, isize | 1~16 字节 | 42, -1 |
| 无符号整数 | u8, u16, u32, u64, u128, usize | 1~16 字节 | 42u8, 0xff |
| 浮点数 | f32, f64 | 4/8 字节 | 3.14, 2.0e-3 |
| 布尔 | bool | 1 字节 | true, false |
| 字符 | char | 4 字节(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 个元素实现
Displaytrait,超过 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-8 | Rust 字符串是 UTF-8 编码,不支持直接索引 |
| Option | 替代 null,表示可能有值或无值 |
| Result | 表示成功或失败的操作结果 |
扩展阅读
- Rust 字符串详解 — String 与 &str
- Rust 类型系统 — 完整类型参考
- UTF-8 编码 — 理解 Rust 字符串的基础
- Unicode in Rust — Unicode 处理工具