Rust的字符串系统是初学者最常见的困惑来源。Rust不像其他语言那样只有一种字符串类型——它区分了String和&str,这是所有权思想的直接体现。理解字符串就是理解Rust核心思想的最佳实践。
学习目标:区分String与&str、掌握切片概念、理解UTF-8编码、学会字符串操作、处理中文字符串
| 特性 | String | &str(字符串切片) |
|---|---|---|
| 位置 | 堆上 | 栈上(引用)或二进制中 |
| 所有权 | 拥有数据 | 借用数据 |
| 可变性 | 可变 | 不可变引用 |
| 大小 | 动态增长 | 固定大小(胖指针) |
| 创建 | String::from(), to_string() | 字面量, 切片操作 |
fn main() {
// 创建String的多种方式
let s1 = String::new(); // 空字符串
let s2 = String::from("hello"); // 从字面量
let s3 = "world".to_string(); // to_string()
let s4 = format!("{} {}", s2, s3); // format!宏
println!("s1='{}' s2='{}' s3='{}' s4='{}'", s1, s2, s3, s4);
// String拥有所有权
let mut s = String::from("foo");
s.push_str("bar"); // 追加字符串切片
s.push('!'); // 追加单个字符
println!("push后: {}", s);
// + 运算符拼接(左侧String被消耗)
let hello = String::from("Hello, ");
let world = String::from("world!");
let hw = hello + &world; // hello被移动,world被借用
// println!("{}", hello); // ❌ hello已被移动
println!("拼接: {}", hw);
println!("world仍可用: {}", world);
}
✅ 验证通过
fn main() {
// 字符串字面量就是&str
let s: &str = "Hello, Rust!"; // 存储在二进制文件中
println!("{}", s);
// 从String创建切片
let string = String::from("hello world");
let slice: &str = &string; // 整个String的切片
println!("slice = {}", slice);
// &str作为函数参数最灵活
print_str("字面量"); // ✅ &str
print_str(&string); // ✅ &String → &str (Deref)
print_str(&string[0..5]); // ✅ 子切片
}
fn print_str(s: &str) {
println!("收到: {}", s);
}
✅ 验证通过
切片是对连续数据序列的引用,不拥有数据。字符串切片&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]; // 从0开始可省略
let world = &s[6..]; // 到末尾可省略
let all = &s[..]; // 全部
println!("{} {} {}", hello, world, all);
// 数组切片
let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..3]; // [2, 3]
println!("数组切片: {:?}", slice);
}
✅ 验证通过
fn first_word(s: &str) -> &str {
let bytes = s.as_bytes();
for (i, &byte) in bytes.iter().enumerate() {
if byte == b' ' {
return &s[0..i];
}
}
s // 没有空格,返回整个字符串
}
fn main() {
let s = String::from("hello world");
let word = first_word(&s);
println!("第一个词: {}", word);
// 也可以直接传&str
let word2 = first_word("Rust编程语言");
println!("第一个词: {}", word2);
}
✅ 验证通过
Rust字符串是UTF-8编码的。切片索引必须对齐UTF-8字符边界,否则会panic!
fn main() {
let s = "你好,Rust!";
// 字节长度 vs 字符数
println!("字节长度: {}", s.len()); // 17 (每个中文字3字节 + 5个ASCII字符)
println!("字符数: {}", s.chars().count()); // 7
// ⚠️ 不能用字节索引访问中文字符!
// let c = &s[0..1]; // ❌ panic! "你好"第一个字占3字节
// ✅ 正确方式:按字符迭代
for c in s.chars() {
print!("[{}] ", c);
}
println!();
// 字节迭代
println!("字节: {:?}", s.as_bytes());
// 安全的字符索引
let chars: Vec<char> = s.chars().collect();
println!("第3个字符: {}", chars[2]); // ,
// 获取UTF-8安全的子串
let sub: String = s.chars().take(2).collect(); // 前2个字符
println!("前2个字符: {}", sub);
let sub2: String = s.chars().skip(3).take(4).collect();
println!("第4-7个字符: {}", sub2);
}
✅ 验证通过
fn main() {
// 替换
let s = "I like Rust";
println!("替换: {}", s.replace("like", "love"));
println!("替换所有: {}", "aaa".replace("a", "b"));
// 修剪
let s = " hello world ";
println!("trim: '{}'", s.trim());
println!("trim_start: '{}'", s.trim_start());
println!("trim_end: '{}'", s.trim_end());
// 分割
let s = "apple,banana,cherry";
let fruits: Vec<&str> = s.split(',').collect();
println!("分割: {:?}", fruits);
// 按空白分割
let words: Vec<&str> = "hello world rust".split_whitespace().collect();
println!("按空白分割: {:?}", words);
// 按行分割
let lines: Vec<&str> = "line1\nline2\nline3".lines().collect();
println!("行数: {}", lines.len());
// 包含检查
let s = "Hello, Rust!";
println!("包含'Rust': {}", s.contains("Rust"));
println!("以'Hello'开头: {}", s.starts_with("Hello"));
println!("以'!'结尾: {}", s.ends_with("!"));
// 重复
println!("重复: {}", "Ha".repeat(3));
// 大小写
println!("大写: {}", "hello".to_uppercase());
println!("小写: {}", "HELLO".to_lowercase());
// 反转(按字符)
let reversed: String = "hello".chars().rev().collect();
println!("反转: {}", reversed);
}
✅ 验证通过
struct TextProcessor {
content: String,
}
impl TextProcessor {
fn new() -> Self {
TextProcessor { content: String::new() }
}
fn from(text: &str) -> Self {
TextProcessor { content: text.to_string() }
}
fn append(&mut self, text: &str) {
if !self.content.is_empty() {
self.content.push('\n');
}
self.content.push_str(text);
}
fn word_count(&self) -> usize {
self.content.split_whitespace().count()
}
fn char_count(&self) -> usize {
self.content.chars().count()
}
fn line_count(&self) -> usize {
if self.content.is_empty() { return 0; }
self.content.lines().count()
}
fn find(&self, pattern: &str) -> Vec<usize> {
let mut results = Vec::new();
let mut start = 0;
while let Some(pos) = self.content[start..].find(pattern) {
results.push(start + pos);
start += pos + pattern.len();
}
results
}
fn replace_all(&mut self, from: &str, to: &str) -> usize {
let count = self.find(from).len();
self.content = self.content.replace(from, to);
count
}
fn preview(&self, max_len: usize) -> &str {
if self.content.len() <= max_len {
&self.content
} else {
// 找到一个安全的UTF-8边界
let mut end = max_len;
while !self.content.is_char_boundary(end) && end > 0 {
end -= 1;
}
&self.content[..end]
}
}
}
fn main() {
let mut tp = TextProcessor::from("Rust是一门安全的系统编程语言");
tp.append("它没有垃圾回收器");
tp.append("却保证了内存安全");
println!("=== 文本统计 ===");
println!("字符数: {}", tp.char_count());
println!("单词数: {}", tp.word_count());
println!("行数: {}", tp.line_count());
println!("\n=== 搜索 ===");
let positions = tp.find("安全");
println!("'安全'出现位置: {:?}", positions);
println!("\n=== 替换 ===");
let count = tp.replace_all("安全", "可靠");
println!("替换了{}处", count);
println!("\n预览: {}...", tp.preview(20));
}
✅ 验证通过
编写函数is_palindrome(s: &str) -> bool,忽略大小写和非字母字符。如"A man, a plan, a canal: Panama"是回文。
编写函数,统计字符串中每个字符的出现次数,返回HashMap<char, usize>。
编写函数parse_csv(line: &str) -> Vec<&str>,按逗号分割,处理引号内的逗号。
编写函数将字符串转为标题格式:每个单词首字母大写,其余小写。支持中英混合。
🔒 下一课解锁:结构体与方法 —— Rust的自定义类型系统
use std::fmt::Write;
fn main() {
// ❌ 低效:多次分配
let mut s = String::new();
for i in 0..100 {
s = s + &i.to_string() + ","; // 每次都分配新内存
}
// ✅ 高效:预分配 + push_str
let mut s2 = String::with_capacity(300);
for i in 0..100 {
s2.push_str(&i.to_string());
s2.push(',');
}
println!("高效构建: {}字节", s2.len());
// ✅ 最高效:write!宏
let mut s3 = String::with_capacity(300);
for i in 0..100 {
write!(s3, "{},", i).unwrap();
}
println!("write!构建: {}字节", s3.len());
// ✅ format!一次性构建
let parts = ["hello", "world", "rust"];
let joined = parts.join(", ");
println!("join: {}", joined);
// 字符串池化
let s1 = "hello"; // &'static str, 编译期已知
let s2 = "hello"; // 指向同一内存
println!("字面量相同地址? {}", std::ptr::eq(s1, s2));
}
✅ 验证通过
fn main() {
let s = "Rust🦀";
println!("字符串: {}", s);
println!("字节长度: {}", s.len()); // 8 (4+4)
println!("字符数: {}", s.chars().count()); // 5
println!("字节数组: {:?}", s.as_bytes());
// 每个字符的字节宽度
for ch in s.chars() {
println!("'{}' = {}字节 (U+{:04X})", ch, ch.len_utf8(), ch as u32);
}
// 安全的子串提取
fn safe_substr(s: &str, max_chars: usize) -> &str {
match s.char_indices().nth(max_chars) {
Some((idx, _)) => &s[..idx],
None => s,
}
}
println!("前3字符: {}", safe_substr("Rust🦀语言", 3));
}
✅ 验证通过