Skip to content

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 被 move
  • a2 只是借用

❌ 不推荐新手使用


八、.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 字符串的长期复习入口