Skip to content

常用数据结构

Rust 标准库提供了多种有用的数据结构,称为集合(collections)。与内置的数组和元组不同,集合存储在堆上,大小可以动态变化。

Rust 有两种主要的字符串类型:

类型说明存储位置
String可变、可增长的字符串
&str字符串切片,不可变通常是堆或静态存储区
fn main() {
// 方法 1:从字符串字面量创建
let s1 = String::from("hello");
// 方法 2:使用 to_string()
let s2 = "hello".to_string();
// 方法 3:空字符串
let s3 = String::new();
// 方法 4:使用 into()
let s4: String = "hello".into();
}
fn main() {
let mut s = String::from("hello");
// 追加字符串
s.push_str(", world");
// 追加字符
s.push('!');
println!("{}", s); // hello, world!
// 字符串长度(字节数)
println!("长度: {}", s.len());
// 是否为空
println!("是否为空: {}", s.is_empty());
// 清空
s.clear();
}
fn main() {
// 使用 + 运算符
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意:s1 被移动,不能再使用
// 使用 format! 宏(推荐,不会移动所有权)
let s1 = String::from("Hello");
let s2 = String::from("world");
let s3 = format!("{}, {}!", s1, s2);
// s1 和 s2 仍然可用
println!("{}", s3);
}

Rust 字符串是 UTF-8 编码的,不能直接索引:

fn main() {
let s = String::from("你好世界");
// 错误!不能索引
// let c = s[0];
// 按字符遍历
for c in s.chars() {
println!("{}", c);
}
// 按字节遍历
for b in s.bytes() {
println!("{}", b);
}
// 获取子串(按字节索引,必须在字符边界)
let hello = &s[0..6]; // "你好"(每个中文字符 3 字节)
println!("{}", hello);
}
fn main() {
// &str -> String
let s1: String = "hello".to_string();
let s2: String = String::from("hello");
// String -> &str
let s3: &str = &s1;
let s4: &str = s1.as_str();
// 函数参数推荐使用 &str
fn print_str(s: &str) {
println!("{}", s);
}
print_str("hello"); // &str
print_str(&s1); // String 自动转换为 &str
}

Vec<T> 是可变长度的数组,元素类型相同。

fn main() {
// 方法 1:使用 new
let v1: Vec<i32> = Vec::new();
// 方法 2:使用 vec! 宏
let v2 = vec![1, 2, 3];
// 方法 3:指定容量
let v3: Vec<i32> = Vec::with_capacity(10);
}
fn main() {
let mut v = vec![1, 2, 3];
// 添加元素
v.push(4);
v.push(5);
// 移除并返回最后一个元素
let last = v.pop(); // Some(5)
// 获取长度和容量
println!("长度: {}, 容量: {}", v.len(), v.capacity());
// 访问元素
let third = &v[2]; // 直接索引,越界会 panic
let third = v.get(2); // 返回 Option,越界返回 None
// 修改元素
v[0] = 10;
// 插入和删除
v.insert(1, 15); // 在索引 1 处插入
v.remove(1); // 删除索引 1 处的元素
}
fn main() {
let v = vec![100, 32, 57];
// 不可变遍历
for i in &v {
println!("{}", i);
}
// 可变遍历
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}
// 带索引遍历
for (index, value) in v.iter().enumerate() {
println!("{}: {}", index, value);
}
}
fn main() {
let v = vec![1, 2, 3, 4, 5];
// 获取切片
let slice = &v[1..3]; // [2, 3]
// 函数接收切片更灵活
fn sum(numbers: &[i32]) -> i32 {
numbers.iter().sum()
}
println!("sum: {}", sum(&v));
println!("sum: {}", sum(&v[1..4]));
}

HashMap 存储键值对,通过键快速查找值。

use std::collections::HashMap;
fn main() {
// 方法 1:使用 new
let mut scores: HashMap<String, i32> = HashMap::new();
// 方法 2:从迭代器创建
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> = teams.into_iter().zip(initial_scores.into_iter()).collect();
}
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
// 插入
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
// 获取
let team_name = String::from("Blue");
let score = scores.get(&team_name); // 返回 Option<&V>
match score {
Some(s) => println!("Blue 队得分: {}", s),
None => println!("没有找到"),
}
// 遍历
for (key, value) in &scores {
println!("{}: {}", key, value);
}
}
use std::collections::HashMap;
fn main() {
let mut scores = HashMap::new();
// 覆盖旧值
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25); // 覆盖为 25
// 只在键不存在时插入
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Yellow")).or_insert(100); // 不会插入
// 基于旧值更新
let text = "hello world wonderful world";
let mut word_count = HashMap::new();
for word in text.split_whitespace() {
let count = word_count.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", word_count);
}

Option<T> 用于表示可能存在或不存在的值:

enum Option<T> {
Some(T),
None,
}
fn main() {
// 有值
let some_number = Some(5);
let some_string = Some("hello");
// 无值(必须指定类型)
let absent_number: Option<i32> = None;
// 从 Vec 获取元素
let v = vec![1, 2, 3];
let third = v.get(2); // Some(&3)
let tenth = v.get(10); // None
}
fn main() {
let x: Option<i32> = Some(5);
// 方法 1:match
match x {
Some(n) => println!("值是: {}", n),
None => println!("没有值"),
}
// 方法 2:if let
if let Some(n) = x {
println!("值是: {}", n);
}
// 方法 3:unwrap(有值返回,None 则 panic)
let n = x.unwrap();
// 方法 4:unwrap_or(提供默认值)
let n = x.unwrap_or(0);
// 方法 5:unwrap_or_else(延迟计算默认值)
let n = x.unwrap_or_else(|| {
println!("计算默认值");
0
});
// 方法 6:map(转换 Some 中的值)
let doubled = x.map(|n| n * 2); // Some(10)
// 方法 7:and_then(链式调用,类似 flatMap)
let result = x.and_then(|n| Some(n * 2));
}

Result<T, E> 用于表示可能成功或失败的操作:

enum Result<T, E> {
Ok(T),
Err(E),
}
use std::fs::File;
use std::io::Read;
fn main() {
// 打开文件可能失败
let file_result = File::open("hello.txt");
let file = match file_result {
Ok(f) => f,
Err(e) => {
println!("打开文件失败: {}", e);
return;
}
};
}
use std::fs::File;
fn main() {
// unwrap:成功返回值,失败 panic
let f = File::open("hello.txt").unwrap();
// expect:与 unwrap 类似,但可以自定义错误信息
let f = File::open("hello.txt").expect("无法打开文件");
// unwrap_or_else:失败时执行闭包
let f = File::open("hello.txt").unwrap_or_else(|error| {
panic!("打开文件时出错: {:?}", error);
});
// is_ok / is_err
let result = File::open("hello.txt");
if result.is_ok() {
println!("文件存在");
}
}
fn main() {
// Option -> Result
let x: Option<i32> = Some(5);
let result: Result<i32, &str> = x.ok_or("没有值");
// Result -> Option
let result: Result<i32, &str> = Ok(5);
let option: Option<i32> = result.ok();
}

使用三种不同的方法将 &str 转换为 String

fn main() {
let s: &str = "hello";
// 方法 1
// 方法 2
// 方法 3
}

创建一个 Vec,实现以下操作:

  • 添加 5 个元素
  • 删除最后一个元素
  • 在索引 2 处插入一个元素
  • 遍历并打印所有元素
fn main() {
let mut v: Vec<i32> = Vec::new();
// 你的代码
}

使用 HashMap 统计以下文本中每个单词出现的次数:

use std::collections::HashMap;
fn main() {
let text = "hello world hello rust world rust rust";
let mut word_count: HashMap<&str, i32> = HashMap::new();
// 你的代码
println!("{:?}", word_count);
// 应该输出类似: {"hello": 2, "world": 2, "rust": 3}
}

编写一个函数,从 Vec 中安全获取指定索引的元素:

fn safe_get(v: &Vec<i32>, index: usize) -> Option<i32> {
// 你的代码
}
fn main() {
let v = vec![1, 2, 3];
println!("{:?}", safe_get(&v, 1)); // Some(2)
println!("{:?}", safe_get(&v, 10)); // None
}

编写一个函数,尝试将字符串解析为数字:

fn parse_number(s: &str) -> Result<i32, std::num::ParseIntError> {
// 你的代码
}
fn main() {
println!("{:?}", parse_number("42")); // Ok(42)
println!("{:?}", parse_number("hello")); // Err(...)
}

了解了常用数据结构后,下一章我们将学习结构体和枚举,它们是自定义类型的基础。