Skip to content

字符串 String

  • 是一个 struct,定义在标准库中
  • String 是一个 Vec<u8> 的封装
  • Rust 的核心语言中只有一种字符串类型: 字符串 slice str,它通常以被借用的形式出现,记为 &str
  • 字符串(String)类型由 Rust 标准库提供,而不是编入核心语言,它是一种可增长、可变、可拥有、UTF-8 编码的字符串类型
  • &str 是不可变的字符串引用,String 是可变的字符串类型
  • 两者都是有效的 UTF-8 编码
rust
pub struct String {
    vec: Vec<u8>,
}

新建字符串

rust
let mut s = String::new(); // 新建一个空的 String
let s = String::from("initial contents"); // 使用 String::from() 创建
let s = "initial contents".to_string(); // 使用 to_string() 创建 String

更新字符串

  1. push_str(): 添加字符串 slice,不获取所有权
rust
let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(s2);  // s2 是 slice,不转移所有权
println!("s2 is {s2}"); // s2 仍可使用
  1. push(): 添加单个字符
rust
let mut s = String::from("lo");
s.push('l'); // 使用单引号表示单个字符
println!("{s}"); // lol

使用 + 运算符或 format! 宏拼接字符串

  1. +: 会移动第一个变量的所有权,在这个变量的缓冲区后面追加后面的内容
rust
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
  1. format!: 不会获取所有权
rust
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{s1}-{s2}-{s3}"); // format! 不获取所有权,s1、s2、s3 仍可使用

format!println! 都是基于格式化字符串的宏,内部规则一致: println!系列宏格式规则

性能对比

  • + 效率更高(不需要额外分配),但受所有权限制
  • format! 可读性更好,适合多个字符串拼接,性能损耗可接受

小贴士: + 运算符的底层实现是调用 add() 方法,format! 宏的底层实现是调用 format_args! 宏.两者都涉及字符串的拼接,但 format! 提供了更灵活的格式化选项,而 + 更适合简单的字符串连接.

字符串索引与切片

  • 字符串不支持直接使用 [] 进行索引(Rust 中 String 没有实现 Index trait)
  • 使用 [start..end] 切片时需谨慎:必须在字符边界处切割,否则会 panic
  • 对非 ASCII 字符,一个字符可能占多个字节,盲目切片会导致运行时错误
rust
let s = "hello";
// let c = s[0];  // ❌ 编译错误: 不支持直接索引

let s = "Здравствуйте"; // 西里尔字母
let slice = &s[0..4];  // ✓ 正确: 4 字节恰好是两个西里尔字符
println!("{}", slice); // Зд

// let slice = &s[0..3];  // ❌ 运行时 panic: 在字符中间切割

遍历字符串

  1. chars() 方法: 返回 char 类型值,按 Unicode 字符迭代(推荐用于字符处理)
  2. bytes() 方法: 返回原始字节值,用于字节级操作
rust
for c in "Зд".chars() {
    println!("{c}");
}
// 输出: З
//      д

for b in "Зд".bytes() {
    println!("{b}");
}
// 输出: 208
//      151
//      208
//      180

最佳实践: 需要处理字符时用 chars(),处理字节时用 bytes(),避免直接索引

额外补充

常用字符串方法

rust
let s = String::from("  Hello, Rust!  ");

// 查询(不消耗所有权)
s.len();                     // 字节数(非字符数)
s.chars().count();           // 字符数(UTF-8 安全,推荐)
s.is_empty();                // 是否为空字符串
s.contains("Rust");          // 是否包含子串 → bool
s.starts_with("Hello");      // 是否以指定前缀开头(注意前有空格,此处 false)
s.ends_with("!");            // 是否以指定后缀结尾
s.find("Rust");              // 首次出现的字节位置 → Option<usize>
s.rfind("l");                // 最后一次出现的字节位置 → Option<usize>

// 转换(返回新 String,不消耗原所有权)
s.trim();                    // 去除首尾空白 → &str
s.trim_start();              // 只去首部空白 → &str
s.trim_end();                // 只去尾部空白 → &str
s.to_uppercase();            // 转大写 → String
s.to_lowercase();            // 转小写 → String
s.replace("Rust", "World");  // 替换全部匹配 → String
s.replacen("l", "L", 2);     // 只替换前 n 个匹配 → String

// 分割(返回迭代器,不消耗原所有权)
let parts: Vec<&str> = s.split(',').collect();        // 按逗号分割
let words: Vec<&str> = s.split_whitespace().collect(); // 按空白分割(推荐去空格)
let lines: Vec<&str> = "a\nb\nc".lines().collect();   // 按行分割

// 移除字符(消耗所有权)
let s = String::from("rust");
let s = s.chars().take(2).collect::<String>(); // 截取前 2 个字符 → "ru"

所有权提示

查询方法都是借用,转换方法返回新值(to_*replace* 返回 String,trim* 返回 &str),分割返回迭代器(借用).移除或重组字符需重新绑定变量.

len()chars().count() 的区别

rust
let s = "你好"; // 两个汉字
println!("{}", s.len());              // 6(字节数: 每个汉字 3 字节)
println!("{}", s.chars().count());    // 2(字符数: 2 个 Unicode 字符)
// 对 ASCII 字符串两者相等,含多字节字符时必须用 chars().count()

&strString 互转

rust
// &str → String
let s1 = "hello".to_string();
let s2 = String::from("hello");
let s3 = "hello".to_owned();  // 三种等价,to_string 最常用

// String → &str
let owned = String::from("hello");
let borrowed: &str = &owned;
let borrowed2: &str = owned.as_str();

parse() — 字符串转其他类型

rust
let n: i32 = "42".parse().unwrap();
let f: f64 = "3.14".parse().unwrap();
// 推荐: 使用 parse::<T>() 显式指定类型,避免依赖上下文推断
let n = "42".parse::<i32>().unwrap();

基于 MIT 协议发布