Rustlings Rust 字符串指南:识别、创建与使用 &str 和 String 两大字符串类型
本文围绕 rustlings 练习集 09_strings 模块展开,系统讲解 Rust 的两种字符串类型——字符串切片(&str)与拥有所有权的字符串(String):如何识别一个值的类型、如何用 String::from、.to_string()、format! 等方式创建字符串、&String 到 &str 的自动类型强制转换(deref coercion),以及 trim、replace、to_lowercase 等常用操作的返回值类型。读完本文,你可以独立完成 rustlings 仓库中 exercises/09_strings/README.md 对应的四道练习,并建立"先判断类型、再选择 API"的 Rust 字符串处理习惯。
两种字符串类型:&str 与 String
exercises/09_strings/README.md 开篇即给出本模块的核心结论:
Rust has two string types: a string slice (
&str) and an owned string (String).
&str(字符串切片):不拥有底层字节缓冲,只是对某段 UTF-8 文本的引用。字符串字面量(如"blue")就是&str,它通常存在于只读的静态存储区。String(拥有所有权的字符串):在堆上分配、长度可增长的拥有所有权的字符串,类似于其他语言中的std::string。创建它需要一次内存分配。
官方文档给出的延伸阅读是 Rust Book 的 Strings 章节、str 方法列表与 String 方法列表(见 exercises/09_strings/README.md 的 Further information 部分)。掌握两者的区别是后续所有练习的前提:函数签名决定了你该传哪种类型,而表达式返回类型决定了它产生的是哪一种。
strings1:把 &str 字面量转换为 String
exercises/09_strings/strings1.rs 给出如下代码,要求在不修改函数签名的前提下修复编译错误:
// TODO: Fix the compiler error without changing the function signature.
fn current_favorite_color() -> String {
"blue"
}
fn main() {
let answer = current_favorite_color();
println!("My current favorite color is {answer}");
}
问题在于:函数声明返回 String,但 "blue" 是 &str 字面量,类型不匹配。对照 solutions/09_strings/strings1.rs 的官方解法:
fn current_favorite_color() -> String {
// Equivalent to `String::from("blue")`
"blue".to_string()
}
这体现了 &str 到 String 的标准转换方式。常见的等价写法有:
let s1: String = "blue".to_string(); // 调用 ToString trait
let s2: String = String::from("blue"); // 官方推荐的最直接方式
let s3: String = "blue".to_owned(); // 等价于 to_string
let s4: String = format!("blue"); // 需要格式化拼接时的习惯用法
四种写法效果一致,都产生一个拥有所有权的堆上字符串。
strings2:&String 到 &str 的自动类型强制转换
exercises/09_strings/strings2.rs 要求不修改 is_a_color_word 函数,只修改 main 中调用处以消除编译错误:
fn is_a_color_word(attempt: &str) -> bool {
attempt == "green" || attempt == "blue" || attempt == "red"
}
fn main() {
let word = String::from("green"); // Don't change this line.
if is_a_color_word(word) { // 编译错误:期望 &str,实参是 String
println!("That is a color word I know!");
} else {
println!("That is not a color word I know.");
}
}
solutions/09_strings/strings2.rs 的修复只加了一个 &:
if is_a_color_word(&word) {
// ^ added to have `&String` which is automatically
// coerced to `&str` by the compiler.
这里的机制是解引用类型强制转换(deref coercion):String 实现了 Deref<Target = str>,编译器会把 &String 自动转换为 &str,从而满足函数参数要求。这条规则的实际意义是:当函数只需要"借用一段文本"时,统一以 &str 作为参数类型即可同时接受字面量、&String 等来源;反之若函数签名要求 String,则必须显式转换。
strings3:按返回值类型选择 trim / format! / replace
exercises/09_strings/strings3.rs 给出三个待实现函数,并附带单元测试;题目本身就在考察"操作结果究竟是 &str 还是 String":
fn trim_me(input: &str) -> &str {
// TODO: Remove whitespace from both ends of a string.
}
fn compose_me(input: &str) -> String {
// TODO: Add " world!" to the string! There are multiple ways to do this.
}
fn replace_me(input: &str) -> String {
// TODO: Replace "cars" in the string with "balloons".
}
solutions/09_strings/strings3.rs 的官方实现:
fn trim_me(input: &str) -> &str {
input.trim()
}
fn compose_me(input: &str) -> String {
// The macro `format!` has the same syntax as `println!`, but it returns a
// string instead of printing it to the terminal.
// Equivalent to `input.to_string() + " world!"`
format!("{input} world!")
}
fn replace_me(input: &str) -> String {
input.replace("cars", "balloons")
}
三个 API 的返回值类型规律值得牢记:
| 操作 | 示例 | 返回类型 | 原因 |
|---|---|---|---|
str::trim |
input.trim() |
&str |
返回原字符串的子切片,不产生新数据 |
format! / 字符串拼接 |
format!("{input} world!")、input.to_string() + " world!" |
String |
产生新的堆上内容,必须拥有所有权 |
str::replace |
input.replace("cars", "balloons") |
String |
替换后的长度不确定,产生新字符串 |
文件内的 #[cfg(test)] 测试模块(见 exercises/09_strings/strings3.rs 第 17–46 行)通过 assert_eq! 验证了边界行为:trim_me("Hi!") 表明 trim 对无首尾空白的输入是幂等的;replace_me 的断言则确认了整词替换("I think cars are cool" → "I think balloons are cool")。运行 rustlings 检查该练习时,这些测试就是判定依据。
strings4:在调用点识别 String 与 &str
exercises/09_strings/strings4.rs 是识别能力的综合检验:把每个 placeholder(…) 调用替换为 string_slice(…)(对应 &str)或 string(…)(对应 String):
fn string_slice(arg: &str) {
println!("{arg}");
}
fn string(arg: String) {
println!("{arg}");
}
fn main() {
placeholder("blue");
placeholder("red".to_string());
placeholder(String::from("hi"));
placeholder("rust is fun!".to_owned());
placeholder(format!("Interpolation {}", "Station"));
// WARNING: This is byte indexing, not character indexing.
// Character indexing can be done using `s.chars().nth(INDEX)`.
placeholder(&String::from("abc")[0..1]);
placeholder(" hello there ".trim());
placeholder("Happy Monday!".replace("Mon", "Tues"));
placeholder("mY sHiFt KeY iS sTiCkY".to_lowercase());
}
对照 solutions/09_strings/strings4.rs,九个表达式的类型判定结果如下:
| 表达式 | 类型 | 判定依据 |
|---|---|---|
"blue" |
&str |
字面量天然是切片 |
"red".to_string() |
String |
to_string() 返回 String |
String::from("hi") |
String |
构造函数本身 |
"rust is fun!".to_owned() |
String |
to_owned() 产生拥有所有权的副本 |
format!("Interpolation {}", "Station") |
String |
宏返回新构造的 String |
&String::from("abc")[0..1] |
&str |
对 String 的字节范围索引产生切片;注意注释强调这是字节索引而非字符索引,字符级索引应使用 s.chars().nth(INDEX) |
" hello there ".trim() |
&str |
trim 返回原字符串的子切片 |
"Happy Monday!".replace("Mon", "Tues") |
String |
replace 返回新字符串 |
"mY sHiFt KeY iS sTiCkY".to_lowercase() |
String |
to_lowercase 需要分配新内存(小写形式可能改变字节布局),返回 String |
这张判定表恰好覆盖了 strings1–strings3 中出现过的全部 API,是检验本模块理解程度的清单式总结。
如何运行与验证
本仓库即 rustlings 官方仓库,练习按目录编号组织,exercises/ 存放待修改的练习题,solutions/ 存放对应的参考答案,两者文件一一对应(如 solutions/09_strings/strings4.rs)。安装 rustlings 命令行工具后,可在本仓库目录下直接运行 rustlings 进入练习流程,它会自动编译并运行各练习(含 strings3 中的单元测试)以判定通过与否;工具自身的实现可参考 src/main.rs、src/cli.rs 与 src/exercise.rs。
建议的完成顺序:
- 先读 exercises/09_strings/README.md 建立
&str/String的心智模型; - 依次修改
strings1.rs至strings4.rs,用rustlings检查编译与测试; - 卡住时对照
solutions/09_strings/下同名文件,重点比较"返回值类型"这一决定编译成败的关键; - 完成后对照本文的判定表,自查能否不查资料地判断任意表达式的字符串类型。
小结
rustlings 的 09_strings 模块虽然篇幅不大,却把 Rust 字符串最容易被踩坑的三个点压缩进了四道题:
- 创建:
String::from、.to_string()、.to_owned()、format!都产生拥有所有权的String; - 借用:参数统一用
&str,&String会经由Deref被编译器自动强制转换为&str; - 操作返回值:
trim返回切片&str,replace、to_lowercase、format!返回String,且字符串索引是字节索引而非字符索引。
只要以函数签名要求的类型为准去选择转换方式,Rust 的字符串代码就能做到零编译错误。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00