字符串 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更新字符串
push_str(): 添加字符串 slice,不获取所有权
rust
let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(s2); // s2 是 slice,不转移所有权
println!("s2 is {s2}"); // s2 仍可使用push(): 添加单个字符
rust
let mut s = String::from("lo");
s.push('l'); // 使用单引号表示单个字符
println!("{s}"); // lol使用 + 运算符或 format! 宏拼接字符串
+: 会移动第一个变量的所有权,在这个变量的缓冲区后面追加后面的内容
rust
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用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: 在字符中间切割遍历字符串
chars()方法: 返回char类型值,按 Unicode 字符迭代(推荐用于字符处理)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()&str 与 String 互转
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();