Rust字符串
字符串
字符串:String 和 &str 的"双胞之谜"
如果你学过其他语言,字符串不就是字符串吗?但在 Rust 里,字符串有两种:String和 &str
新手第一次遇到这俩,基本是这样的:
fn main() {
let s1 = "hello";
let s2 = String::from("world");
let s3 = s1 + s2; // ❌ 编译错误!
}
今天咱们就来彻底搞懂 Rust 的字符串,让你从此不再被编译器"教育"。
String vs &str:本质区别
| 特性 | String | &str |
|---|---|---|
| 类型 | 结构体(拥有所有权) | 切片(引用) |
| 内存 | 堆上分配 | 通常指向已有数据 |
| 可变性 | 可变 | 不可变 |
| 大小 | 动态增长 | 固定长度 |
| 字面量 | 需要转换 | 直接就是 |
生活化类比:
String = 你自己买的笔记本,可以随便写、随便撕页
&str = 图书馆的书,只能看,不能改
字符串字面量:天生就是 &str
let s = "hello"; // 类型是 &str
字符串字面量(用双引号括起来的)天生就是 &str,它的类型是 &'static str——生命周期是 'static(跟程序一样长),因为内容存在二进制文件里。
创建 String:从 &str 转换
// 方法 1:from
let s1 = String::from("hello");
// 方法 2:to_string
let s2 = "hello".to_string();
// 方法 3:into(需要类型推断)
let s3: String = "hello".into();
推荐:String::from() 最清晰。
UTF-8:Rust 字符串的"灵魂"
Rust 的字符串默认是 UTF-8 编码,这意味着:
可以存任何语言的字符
一个字符可能占 1-4 个字节
不能通过索引访问(因为不知道第 N 个字符从哪开始)
let s = String::from("你好");
// ❌ 错误!不能索引
// let c = s[0];
// ✅ 正确:用 chars()
for c in s.chars() {
println!("{}", c);
}
字符串操作:常用方法
let mut s = String::from("hello");
// 追加
s.push_str(" world"); // 追加字符串
s.push('!'); // 追加字符
// 拼接
let s1 = String::from("hello");
let s2 = String::from(" world");
let s3 = format!("{}{}", s1, s2); // 推荐!
let s4 = &s1 + &s2; // 也能用,但 s1 被移动
// 长度
let len = s.len(); // 字节数,不是字符数!
// 是否为空
if s.is_empty() {
println!("空字符串");
}
// 截取(用切片)
let slice = &s[0..5]; // &str 类型
// 查找
if s.contains("world") {
println!("包含 world");
}
// 替换
let replaced = s.replace("world", "Rust");
// 分割
for word in s.split_whitespace() {
println!("{}", word);
}
格式化字符串:format! 宏
let name = "Alice";
let age = 25;
// 拼接字符串
let greeting = format!("你好,{}!你{}岁了。", name, age);
// 格式化数字
let pi = 3.1415926;
println!("π ≈ {:.2}", pi); // 3.14
// 对齐
println!("{:>10}", "hello"); // 右对齐:" hello"
println!("{:<10}", "hello"); // 左对齐:"hello "
println!("{:^10}", "hello"); // 居中对齐:" hello "
字符串和数字转换
// 数字转字符串
let num = 42;
let s = num.to_string();
let s = format!("{}", num);
// 字符串转数字
let s = "42";
let n: i32 = s.parse().expect("不是数字!");
let n: Result<i32, _> = s.parse(); // 不 panic,返回 Result
基础示例:字符串操作大全
fn main() {
// 创建
let mut s = String::from("Hello");
// 追加
s.push_str(", ");
s.push('W');
s.push_str("orld!");
// 长度
println!("长度:{} 字节", s.len());
// 截取
let hello = &s[0..5];
println!("截取:{}", hello);
// 查找
if s.contains("World") {
println!("包含 World");
}
// 替换
let replaced = s.replace("World", "Rust");
println!("替换后:{}", replaced);
// 分割
for word in s.split(|c| c == ',' || c == '!') {
if !word.is_empty() {
println!("单词:{}", word.trim());
}
}
}
错误示例 1:&str + String 类型不匹配
fn main() {
let s1 = "hello"; // &str
let s2 = String::from(" world"); // String
let s3 = s1 + s2; // ❌ 错误!
}
编译器:"+ 运算符要求右边是 &str,你给了个 String。加个 & 借用一下啊!"
但还有问题:即使加了 &,左边是 &str 也不行。+ 的左边必须是 String。
修复:
let s3 = s1.to_string() + &s2; // 把 s1 转成 String
// 或者
let s3 = format!("{}{}", s1, s2); // 推荐!更清晰
错误示例 2:字符串索引
fn main() {
let s = String::from("你好");
let c = s[0]; // ❌ 错误!
}
编译器:"str 不能用整数索引!UTF-8 编码下,你不知道第 0 个字符从哪开始啊!"
修复:
let s = String::from("你好");
// 方法 1:用 chars()
let first = s.chars().next().unwrap();
// 方法 2:用切片(知道字节位置)
let slice = &s[0..3]; // "你" 占 3 个字节
错误示例 3:借用冲突
fn main() {
let mut s = String::from("hello");
let r = &s;
s.push_str(" world"); // ❌ 错误!
println!("{}", r);
}
编译器:"r 正在不可变借用 s,你又想可变借用(push_str 需要 &mut self)?不行!"
修复:
let mut s = String::from("hello");
s.push_str(" world");
let r = &s;
println!("{}", r);
错误示例 4:len() 不是字符数
fn main() {
let s = String::from("你好");
println!("长度:{}", s.len()); // 输出:6,不是 2!
}
len() 返回的是字节数,不是字符数。"你"和"好"各占 3 个字节(UTF-8),所以是 6。
修复:
let char_count = s.chars().count(); // 2
常见坑点
坑点 1:混淆 String 和 &str
fn print_name(name: String) {
println!("{}", name);
}
fn main() {
let name = "Alice";
print_name(name); // ❌ 错误!&str != String
}
修复:用 &str 更灵活。
fn print_name(name: &str) {
println!("{}", name);
}
坑点 2:+ 运算符的陷阱
let s1 = String::from("hello");
let s2 = String::from(" world");
let s3 = s1 + &s2; // s1 被移动,不能再用了
println!("{}", s1); // ❌ 错误!
修复:用 format!。
let s3 = format!("{}{}", s1, s2);
坑点 3:切片越界
let s = String::from("hello");
let slice = &s[0..100]; // panic!
修复:确保索引有效。
let slice = &s[0..s.len()];
坑点 4:中文字符切片
let s = String::from("你好");
let slice = &s[0..1]; // panic!不是有效的 UTF-8 边界
修复:用 chars() 或确保在字符边界。
let slice = &s[0..3]; // "你" 占 3 字节
文档信息
版权声明:可自由转载(请注明转载出处)-非商用-非衍生
发表时间:2026年4月17日 09:15