主题
Rust 字符串完全理解|从 String 到 Unicode(可复习版)
本文基于一段完整可运行代码,系统梳理 Rust 中字符串的所有核心知识点。 目标:一次写清楚,之后只看这一篇就能想明白。
一、示例代码(全文)
rust
use unicode_segmentation::UnicodeSegmentation;
fn main() {
let s1 = String::from("Hello Rust s1");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
let s2 = "Hello Rust s2".to_string();
println!("s2 = {}", s2);
let s3 = "Hello Rust s2".to_owned();
println!("s3 = {}", s3);
let formatted = format!("s3 = {}", s3);
println!("formatted ={}", formatted);
let s4 = format!("s1 = {}, s2 = {}", s1, s2);
println!("s4 = {}", s4);
let s5 = &s1[..];
println!("s5 = {}", s5);
let mut f1 = String::from("Hello Rust f1");
f1.push_str(" 2026");
println!("f1 = {}", f1);
f1.replace_range(6..10, "World");
println!("f1 = {}", f1);
let a1 = String::from("Hello, ");
let a2 = String::from("World!");
let a3 = a1 + &a2;
println!("a3 = {}", a3);
let t1 = String::from("t1");
let t2 = String::from("t2");
let t3 = String::from("t3");
let t4 = format!("{}-{}-{}", t1, t2, t3);
println!("t4 = {}", t4);
let q1 = ["q1", "q2", "q3"].concat();
println!("q1 = {}", q1);
let q2 = format!("{}{}", "q1", "q2");
println!("q2 = {}", q2);
let q3 = concat!("q1", "q2");
println!("q3 = {}", q3);
let q5 = String::from("q5");
let q6 = q5 + " q6";
println!("q6 = {}", q6);
for c in "12345678".chars() {
println!("{}", c);
}
for b in "12345678".bytes() {
println!("{}", b);
}
for d in "🥺🥺🥺🥺🥺".chars() {
println!("{}", d);
}
for f in "🤢🤮😢😭😱😖😞😩🫰🧑🦳".graphemes(true) {
println!("{}", f);
}
}
fn calculate_length(s: &String) -> usize {
s.len()
}二、String 与 &str:先搞清楚“谁拥有内存”
rust
let s1 = String::from("Hello Rust s1");
let len = calculate_length(&s1);String:- 拥有堆内存
- 可增长、可修改
&String/&str:- 借用
- 不拥有内存
rust
fn calculate_length(s: &String) -> usize {
s.len()
}⚠️ 注意:
len()返回的是 字节长度(UTF-8)- 因为是借用,
s1后面还能继续使用
三、字符串字面量 → String 的两种常用方式
rust
let s2 = "Hello Rust s2".to_string();
let s3 = "Hello Rust s2".to_owned();| 方法 | 说明 |
|---|---|
to_string() | 最常用、最直观 |
to_owned() | 泛型 / trait 场景更常见 |
👉 本质完全一样:都会生成新的 String
四、format!:最安全的字符串拼接方式
rust
let s4 = format!("s1 = {}, s2 = {}", s1, s2);特点:
- 不修改原字符串
- 不转移所有权
- 内部使用借用
✅ 实际开发中,优先使用 format!
五、字符串切片 &str
rust
let s5 = &s1[..];- 这是一个
&str - 不拷贝数据
- 只是指向
s1的一段内存
⚠️ 借用存在期间,s1 不能被修改
六、可变 String 的修改
1️⃣ push_str
rust
f1.push_str(" 2026");- 追加
&str - 修改原字符串
2️⃣ replace_range(高危但强大)
rust
f1.replace_range(6..10, "World");⚠️ 重点:
- 范围是 字节索引
- 必须落在 UTF-8 边界
- 否则运行时 panic
七、+ 运算符:隐藏的所有权转移
rust
let a3 = a1 + &a2;等价签名:
rust
fn add(self, s: &str) -> String结论:
a1被 movea2只是借用
❌ 不推荐新手使用
八、.concat() vs concat!(结合你的代码)
.concat() —— 运行时方法
rust
let q1 = ["q1", "q2", "q3"].concat();特点:
- 方法
- 运行时执行
- 参数可以是变量
- 返回
String
concat! —— 编译期宏
rust
let q3 = concat!("q1", "q2");特点:
- 宏
- 编译期完成
- 只能拼接字符串字面量
- 返回
&'static str
❌ 下面一定报错:
rust
// concat!("q1", q2)对比总结
| 对比点 | .concat() | concat! |
|---|---|---|
| 类型 | 方法 | 宏 |
| 执行时机 | 运行时 | 编译期 |
| 参数 | 变量 / 字面量 | 仅字面量 |
| 返回 | String | &'static str |
九、字符串遍历的三种视角(核心)
1️⃣ .bytes() —— UTF-8 字节
rust
for b in "12345678".bytes() {}- 最底层
- emoji 会拆成多个字节
2️⃣ .chars() —— Unicode 标量值
rust
for c in "🥺🥺".chars() {}- 一个 emoji 通常是一个
char - 但不等于“用户看到的一个字”
3️⃣ .graphemes() —— 用户感知字符(推荐)
rust
for g in "🧑🦳".graphemes(true) {}- 来自
unicode_segmentation - 一个完整“视觉字符”
- 聊天 / 输入框 / 富文本必备
十、一句话总记忆
Rust 没有字符串索引,只有不同的“理解视角”
.len()→ 字节.chars()→ Unicode.graphemes()→ 人类看到的字符
✅ 这篇文章可以作为 Rust 字符串的长期复习入口