← [ BACK_TO_NOTES ]

Rust 字符串

String 与 &str 的分工:拥有型 UTF-8 缓冲 vs 借用切片;索引、拼接与常见陷阱。

#concept #growing #dev #rust

[!info] related notes

Rust 字符串

这个概念为什么出现

文本处理无处不在,但 Rust 故意把“字符串”拆成多层:

  • 字面量与借用视图:&str
  • 可增长拥有缓冲:String
  • 底层字节:[u8] / Vec<u8>

还要面对 UTF-8:不能像某些语言一样按“字符下标”廉价随机改。

[!abstract] 一句话理解 &str 是 UTF-8 字符串切片(借用);String 是堆上可增长的拥有型 UTF-8 缓冲;范围索引使用字节偏移且必须落在 UTF-8 字符边界,不支持单个 usize 下标取字符。

最小可运行示例

场景:用户中心规范化显示名

注册流把用户输入 " Ada Lovelace " 规范化为小写 trim 后的拥有字符串,并统计字节长度用于存储估算。

// 业务意图:trim + lowercase 得到可存储的 display_name。
// 教学点:&str 输入、String 输出;to_lowercase 产生新 String;len 是字节数。

fn normalize_display_name(raw: &str) -> String {
    let trimmed = raw.trim(); // &str 切片,仍借用 raw
    trimmed.to_lowercase()    // 得到拥有的 String
}

fn main() {
    let input = "  Ada Lovelace  ";
    let name = normalize_display_name(input);
    println!("display_name={name}");
    println!("byte_len={}", name.len()); // UTF-8 字节长度
    println!("chars={}", name.chars().count());
}

建议运行:

cargo run

期望输出:

display_name=ada lovelace
byte_len=12
chars=12

这个示例只展示 trim 与 Unicode 小写映射,不是完整的用户名规范化:to_lowercase 不等于 Unicode normalization 或 case folding,可能改变标量数量,也不应直接据此判定账号身份。是否保留显示名大小写是产品决策。

结合场景再看三个关注点

  1. API 收 &str、返回 String:调用方用字面量或已有 String 都行。
  2. len() 是字节:含中文时字节数 ≠ 字符数。
  3. 不要 s[0] 取字符:String/str 不支持按 usize 直接索引字符。

核心概念与准确模型

类型拥有?可变增长?典型来源
&str否否(视图)字面量、String 借用
String是是String::from、to_string、format!
&[u8]否否原始字节

常用构造:

let s = String::from("hi");
let t = "hi".to_string();
let u = format!("user={id}", id=7);

拼接:

let mut s = String::from("hello");
s.push_str(", world");
s.push('!');
let both = s + "!!"; // + 会吃掉左侧 String 所有权

遍历:chars() 产生 Unicode 标量,bytes() 产生 UTF-8 字节,split_whitespace() 产生非空白片段。一个用户可见的字形簇可能含多个标量,chars().count() 不等于显示字符数。

设计动机

  • UTF-8 默认正确处理全球文本
  • 区分借用/拥有,避免隐式拷贝
  • 强制处理编码边界,减少半吊子截断

边界情况与反直觉行为

  • 中文等字符多字节:&s[0..1] 可能 panic;不确定边界时用 s.get(range) 处理 None
  • 任意字节转字符串需验证 UTF-8:String::from_utf8 / str::from_utf8 返回 Result;lossy 转换会替换坏字节,可能丢失原始信息
  • + 运算符对 String 的所有权特殊
  • OsString/Path/CString 是另一类字符串边界(OS/FFI)

常见误区

[!warning] 常见误区:用下标取“第 n 个字符” 正确做法:chars().nth(n)(O(n))或用 crate 处理字形簇。

[!warning] 常见误区:把 String 与 str 当成无关类型 str 是 DST;String 可 Deref 到 str,故很多方法在两者上都能调。

工程实践

  1. 函数参数优先 &str
  2. 需要拥有再 String
  3. 日志/格式化用 format! 或 write!
  4. 性能敏感路径避免无谓分配

本节总结

  • &str 借用,String 拥有
  • UTF-8 字节索引要落在边界
  • 文本 API 设计先想清所有权

自测题

  1. 为什么 s[0] 对 String 不成立?
  2. fn f(s: String) 与 fn f(s: &str) 的调用体验差在哪?
参考答案
  1. 字符串按 UTF-8 编码,单字节下标不一定是字符边界,且类型未实现 Index<usize> 取 char。
  2. 收 String 会强制调用方交出或 clone;&str 更通用。

延伸阅读与资料来源

资料类型支撑内容
The Book — Storing UTF-8 Encoded Text with Strings官方书String
std::string::String标准库API
std::str标准库&str 方法
创建于 2026/7/15 更新于 2026/10/7
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本