Skip to content

Rust - 字符串

文本是一种复杂的数据类型,Rust 采用两部分方法来处理它,提供安全性、速度和对内存的控制。两种主要的字符串类型是:

  • &str(字符串切片):一个不可变、固定大小的字符串数据的视图。它是对始终是有效 UTF-8 字节序列的引用。
  • String:一个可变、可增长、拥有所有权的字符串。它是一个堆分配的缓冲区,可以被修改。

理解这两者之间的区别是编写地道 Rust 代码的基础。

字符串字面量,例如 "Hello, world!",类型是 &str。字符串字面量的文本直接硬编码到你的程序的二进制文件中。它们是不可变的,并具有 'static 生命周期,这意味着它们保证在整个程序运行期间都有效。

fn main() {
// 类型 `&str` 是指向该字符串数据的切片。
let greeting: &str = "Hello, Rust developers!";
println!("{}", greeting);
// 我们也可以让编译器推断类型。
let location = "Planet Earth";
println!("We are live from {}.", location);
}

String:拥有所有权的字符串类型

Section titled “String:拥有所有权的字符串类型”

String 类型是 Rust 标准库的一部分。它专为需要拥有字符串数据、修改字符串或在编译时不知道字符串内容(例如,用户输入)的情况而设计。

你可以通过几种常见方式创建 String:

fn main() {
// 1. 创建一个新的空 String
let mut new_string = String::new();
new_string.push_str("This is a new");
new_string.push(' '); // 推入一个字符
println!("new_string: {}", new_string);
// 2. 使用 String::from() 从字符串字面量创建
let from_literal = String::from("string from a literal.");
println!("from_literal: {}", from_literal);
// 3. 使用 .to_string() 将 &str 转换为 String
let str_slice = "Another way to do it.";
let to_string_version = str_slice.to_string();
println!("to_string_version: {}", to_string_version);
}
方法签名描述
new()pub const fn new() -> String创建一个新的空 String。
push_str()pub fn push_str(&mut self, string: &str)将一个字符串切片(&str)追加到此 String 的末尾。
push()pub fn push(&mut self, ch: char)将一个 char 追加到此 String 的末尾。
replace()pub fn replace<'a, P>(&'a self, from: P, to: &str) -> String用另一个字符串替换所有匹配的模式,并返回一个新的 String。
len()pub fn len(&self) -> usize返回 String 的字节长度(而非字符长度)。
trim()pub fn trim(&self) -> &str返回一个移除了前导和尾随空白字符的字符串切片。
split_whitespace()pub fn split_whitespace(&self) -> SplitWhitespace返回一个迭代器,它根据空白字符分割子字符串。
chars()pub fn chars(&self) -> Chars返回一个迭代器,它遍历字符串的 char。

你可以使用 + 运算符或 format! 宏来组合字符串。format! 宏通常更受欢迎,因为它更具可读性并且不获取其参数的所有权。

fn main() {
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
// 使用 `+` 会获取 s1 的所有权,因此在此之后 s1 不能再被使用。
// `+` 运算符使用 `add` 方法,其签名是:fn add(self, s: &str) -> String
// 这就是为什么 s2 必须是一个引用(`&s2`)。
let s3 = s1 + &s2;
// println!("{}", s1); // 这将导致编译错误:值在此处移动后被借用
println!("Concatenated with +: {}", s3);
// 使用 `format!` 宏通常更简洁,并且不会获取所有权。
let s4 = String::from("tic");
let s5 = String::from("tac");
let s6 = String::from("toe");
let formatted = format!("{}-{}-{}", s4, s5, s6);
println!("Formatted: {}", formatted);
println!("s4 is still available: {}", s4); // s4 仍然有效!
}

与其他一些语言不同,Rust 不允许你通过整数索引(例如 my_string[0])访问 String 中的字符。这是因为 String 是 UTF-8 编码的,并且字符的字节长度可能是可变的。例如,'a' 是 1 字节,但 'é' 是 2 字节,而 '🦀' 是 4 字节。索引可能指向一个不是有效字符开头的位置,这将是不安全的。

相反,你应该迭代或创建切片。

fn main() {
let hello = "Здравствуйте"; // 一个俄语字符串
// 不要这样做:如果索引不在字符边界上,它将崩溃。
// let slice = &hello[0..1]; // 这将是无效的!
// 切片必须在字符边界上。
// 第一个字符 'З' 长 2 字节。
let slice = &hello[0..2];
println!("Slice of first char: {}", slice);
// 处理字符的最佳方式是迭代。
println!("遍历字符:");
for c in hello.chars() {
print!("{} ", c);
}
println!();
}

在编写函数时,将 &str 作为参数而非 String 通常会更灵活。这使得你的函数可以同时处理 String 类型和 &str 切片,而无需不必要的内存分配。这是可能的,因为 Rust 可以自动将 &String 转换为 &str(一个称为解引用强制转换的概念)。

// 这个函数很灵活,因为它接受一个字符串切片。
fn log_message(message: &str) {
println!("LOG: {}", message);
}
fn main() {
let owned_string = String::from("An owned message.");
let string_literal = "A literal message.";
// 我们可以将这两种类型传递给函数!
log_message(&owned_string);
log_message(string_literal);
}