
字符串:
String和&str的"双胞之谜"
如果你学过其他语言,字符串不就是字符串吗?但在 Rust 里,字符串有两种:String 和 &str。
新手第一次遇到这俩,基本是这样的:
fn main() {
let s1 = "hello";
let s2 = String::from("world");
let s3 = s1 + s2; // ❌ 编译错误!
}
编译器:"类型不匹配!"
你:"不都是字符串吗?!"
编译器:"一个是 &str,一个是 String,能一样吗?"
你:"......"
今天咱们就来彻底搞懂 Rust 的字符串,让你从此不再被编译器"教育"。
String vs &str:本质区别特性 | String | &str |
|---|---|---|
类型 | 结构体(拥有所有权) | 切片(引用) |
内存 | 堆上分配 | 通常指向已有数据 |
可变性 | 可变 | 不可变 |
大小 | 动态增长 | 固定长度 |
字面量 | 需要转换 | 直接就是 |
生活化类比:
String = 你自己买的笔记本,可以随便写、随便撕页&str = 图书馆的书,只能看,不能改&strlet 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() 最清晰。
Rust 的字符串默认是 UTF-8 编码,这意味着:
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[..]; // &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 = ;
// 拼接字符串
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 = ;
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[..];
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());
}
}
}
&str + String 类型不匹配fn main() {
let s1 = "hello"; // &str
let s2 = String::from(" world"); // String
let s3 = s1 + s2; // ❌ 错误!
}
编译器错误:
error[E0308]: mismatched types
--> src/main.rs:4:17
|
4 | let s3 = s1 + s2;
| ^^ expected `&str`, found struct `String`
|
help: consider borrowing here
|
4 | let s3 = s1 + &s2;
| +
人话翻译:编译器:"+ 运算符要求右边是 &str,你给了个 String。加个 & 借用一下啊!"
但还有问题:即使加了 &,左边是 &str 也不行。+ 的左边必须是 String。
修复:
let s3 = s1.to_string() + &s2; // 把 s1 转成 String
// 或者
let s3 = format!("{}{}", s1, s2); // 推荐!更清晰
fn main() {
let s = String::from("你好");
let c = s[]; // ❌ 错误!
}
编译器错误:
error[E0277]: the type `str` cannot be indexed by `{integer}`
--> src/main.rs:3:14
|
3 | let c = s[0];
| ^^^ `str` cannot be indexed by `{integer}`
人话翻译:编译器:"str 不能用整数索引!UTF-8 编码下,你不知道第 0 个字符从哪开始啊!"
修复:
let s = String::from("你好");
// 方法 1:用 chars()
let first = s.chars().next().unwrap();
// 方法 2:用切片(知道字节位置)
let slice = &s[..]; // "你" 占 3 个字节
fn main() {
let mut s = String::from("hello");
let r = &s;
s.push_str(" world"); // ❌ 错误!
println!("{}", r);
}
编译器错误:
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let r = &s;
| -- immutable borrow occurs here
4 | s.push_str(" world");
| ^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
5 | println!("{}", r);
| - immutable borrow used here
人话翻译:编译器:"r 正在不可变借用 s,你又想可变借用(push_str 需要 &mut self)?不行!"
修复:
let mut s = String::from("hello");
s.push_str(" world");
let r = &s;
println!("{}", r);
len() 不是字符数fn main() {
let s = String::from("你好");
println!("长度:{}", s.len()); // 输出:6,不是 2!
}
人话翻译:len() 返回的是字节数,不是字符数。"你"和"好"各占 3 个字节(UTF-8),所以是 6。
修复:
let char_count = s.chars().count(); // 2
String 和 &strfn print_name(name: String) {
println!("{}", name);
}
fn main() {
let name = "Alice";
print_name(name); // ❌ 错误!&str != String
}
修复:用 &str 更灵活。
fn print_name(name: &str) {
println!("{}", name);
}
+ 运算符的陷阱let s1 = String::from("hello");
let s2 = String::from(" world");
let s3 = s1 + &s2; // s1 被移动,不能再用了
println!("{}", s1); // ❌ 错误!
修复:用 format!。
let s3 = format!("{}{}", s1, s2);
let s = String::from("hello");
let slice = &s[..]; // panic!
修复:确保索引有效。
let slice = &s[..s.len()];
let s = String::from("你好");
let slice = &s[..]; // panic!不是有效的 UTF-8 边界
修复:用 chars() 或确保在字符边界。
let slice = &s[..]; // "你" 占 3 字节
use std::collections::HashMap;
fn count_words(text: &str) -> HashMap<String, usize> {
let mut counts = HashMap::new();
for word in text.split_whitespace() {
let word = word.to_lowercase();
let word = word.trim_matches(|c: char| !c.is_alphanumeric());
*counts.entry(word).or_insert() += ;
}
counts
}
fn main() {
let text = "hello world hello Rust world hello";
let counts = count_words(text);
for (word, count) in &counts {
println!("{}: {}", word, count);
}
}
fn reverse(s: &str) -> String {
s.chars().rev().collect()
}
fn main() {
let s = "你好,Rust!";
let reversed = reverse(s);
println!("原字符串:{}", s);
println!("反转后:{}", reversed);
}
fn parse_csv_line(line: &str) -> Vec<&str> {
line.split(',').collect()
}
fn parse_csv_line_trimmed(line: &str) -> Vec<String> {
line.split(',')
.map(|s| s.trim().to_string())
.collect()
}
fn main() {
let line = "Alice, 25, Engineer";
let fields = parse_csv_line(line);
println!("{:?}", fields);
let fields = parse_csv_line_trimmed(line);
println!("{:?}", fields);
}

String 拥有所有权,&str 是引用——函数参数优先用 &str&str,存在二进制里,生命周期 'staticchars() 遍历len() 是字节数,不是字符数,中文字符要注意format!,+ 运算符会移动所有权下篇预告:函数返回引用时,编译器总抱怨"生命周期"是啥?下一篇聊聊生命周期基础,揭开这个神秘概念的面纱!