String 与字符串切片
约 1134 字大约 4 分钟
2025-11-25
重要
严格来说 String 不属于 std::collections 模块里的集合类型,但 String 的本质是一个拥有所有权、可增长、可变、UTF-8 编码的字符串类型。它的底层可以理解为:Vec<u8>
字符串由 Rust 标准库提供,位于 std::string 模块。它是一种可增长、可变、可拥有、UTF-8 编码的字符串类型。
String 与 &str
Rust 中 String 是拥有所有权的字符串,数据在堆上,可增长、可修改。而 &str 是字符串切片,是对一段 UTF-8 字符串数据的借用视图,通常不可变,不拥有数据。
fn main() {
let s1 = String::from("hello");
let s2 = "hello";
println!("{s1}");
println!("{s2}");
}
s1是 String;s2是 &str 字符串切片
创建与更新 String
很多 Vec 上可用的操作在 String 上同样可用,实际上 String 也被认为是一个带有一些额外保证、限制和功能的字节 Vec 的封装。
创建字符串的方式:
String::new()创建一个空的、可增长的字符串String::from()直接从字符串字面量创建Stringto_string()将一个字符串字面量转换成String
fn main() {
let mut s = String::new();
s.push_str("hello");
s.push(',');
s.push(' ');
s.push_str("world");
s.push('!');
println!("{s}");
}fn main() {
let s = String::from("Hello, world!");
}fn main() {
let s = "Hello, world!".to_string();
}
push_str接收的参数是&str,除此之外还可以通过push追加单个字符,参数是char
字符串拼接
字符串拼接主要有三种方式:
push_str+运算符format!
fn main() {
let mut s = String::from("Hello");
s.push_str(", world!");
println!("{s}");
}fn main() {
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意:s1 被移动了,不能再使用
println!("{s3}");
}fn main() {
let s1 = String::from("Hello");
let s2 = String::from("world");
let s3 = format!("{s1}, {s2}!");
println!("{s1}");
println!("{s2}");
println!("{s3}");
}
format!不会拿走任何字符串的所有权,原字符串依然可用
索引字符串
在很多语言中,可以直接通过索引访问字符串中的某个字符,例如 JavaScript 中可以写:
const s = 'hello'
console.log(s[0]) // h但在 Rust 中,String 不支持这种形式的索引:
fn main() {
let s = String::from("hi");
// ❌ Rust 中不能这样索引 String
// let h = s[0];
}原因
这是因为 Rust 的字符串是 UTF-8 编码的,而 String 本质上可以理解为对 Vec<u8> 的封装。也就是说,字符串底层存储的是一组字节,而不是一个简单的字符数组。
例如:
let s = String::from("Hola");这里每个英文字母刚好占 1 个字节,所以看起来似乎可以通过索引直接访问。但是对于中文、俄文、日文等字符,一个字符通常会占用多个字节:
let s = String::from("你好");"你" 并不是 1 个字节,而是多个 UTF-8 字节组成的。如果使用 s[0],那么 Rust 无法确定到底想要的是:
- 第 0 个字节
- 第 0 个 Unicode 字符
- 第 0 个用户眼中的字形
因此 Rust 直接禁止使用整数索引访问字符串,避免返回错误或令人误解的结果
遍历字符串
按字符遍历
如果想按 字符 遍历字符串,可以使用 chars():
fn main() {
let s = String::from("你好");
for c in s.chars() {
println!("{c}");
// 输出:
// 你
// 好
}
}重要
chars() 返回的是 Unicode 标量值,也就是 Rust 中的 char
按字节遍历
如果想查看字符串底层的 UTF-8 字节,可以使用 bytes():
fn main() {
let s = String::from("你好");
for b in s.bytes() {
println!("{b}");
// 输出:
// 228
// 189
// 160
// 229
// 165
// 189
}
}按字节切片查看
如果想直接拿到底层字节切片 &[u8],可以使用 as_bytes():
fn main() {
let s = String::from("hello");
let bytes = s.as_bytes();
println!("{bytes:?}"); // 输出: [104, 101, 108, 108, 111]
}重要
as_bytes() 是拿到整段字节切片,而 bytes() 一个字节一个字节遍历
字符串 slice
Rust 虽然不允许 s[0] 这样用单个索引访问字符串,但允许用范围索引创建字符串切片,也就是 &str。
字符串切片
fn main() {
let hello = String::from("Здравствуйте")
let s = &hello[0..4];
println!("{s}");
}这里不是取 "第 0 到第 4 个字符" 而是 "取第 0 到第 4 个字节"。如果改成
&hello[0..1]就会 panic,因为第 0 个字节并不构成一个有效的 UTF-8 字符
