首页
/ Rustlings Rust 字符串指南:识别、创建与使用 &str 和 String 两大字符串类型

Rustlings Rust 字符串指南:识别、创建与使用 &str 和 String 两大字符串类型

2026-09-04 16:06:33作者:鲍丁臣Ursa

本文围绕 rustlings 练习集 09_strings 模块展开,系统讲解 Rust 的两种字符串类型——字符串切片(&str)与拥有所有权的字符串(String):如何识别一个值的类型、如何用 String::from.to_string()format! 等方式创建字符串、&String&str 的自动类型强制转换(deref coercion),以及 trimreplaceto_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()
}

这体现了 &strString 的标准转换方式。常见的等价写法有:

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.rssrc/cli.rssrc/exercise.rs

建议的完成顺序:

  1. 先读 exercises/09_strings/README.md 建立 &str / String 的心智模型;
  2. 依次修改 strings1.rsstrings4.rs,用 rustlings 检查编译与测试;
  3. 卡住时对照 solutions/09_strings/ 下同名文件,重点比较"返回值类型"这一决定编译成败的关键;
  4. 完成后对照本文的判定表,自查能否不查资料地判断任意表达式的字符串类型。

小结

rustlings 的 09_strings 模块虽然篇幅不大,却把 Rust 字符串最容易被踩坑的三个点压缩进了四道题:

  • 创建String::from.to_string().to_owned()format! 都产生拥有所有权的 String
  • 借用:参数统一用 &str&String 会经由 Deref 被编译器自动强制转换为 &str
  • 操作返回值trim 返回切片 &strreplaceto_lowercaseformat! 返回 String,且字符串索引是字节索引而非字符索引。

只要以函数签名要求的类型为准去选择转换方式,Rust 的字符串代码就能做到零编译错误。

登录后查看全文
热门项目推荐
相关项目推荐