“如何在 String、&str、Vec<u8> 和 &[u8] 之间转换?”—— 您的 Rust 优化检查清单
\n\n如果您曾经用 Rust 编写过处理文本或字节的代码,您很可能遇到过这样的问题:“如何将 String 转换为 &str?那 Vec<u8> 转换为 &[u8] 呢?反过来又怎么做?” 这是 Stack Overflow 上最常见的问题之一(有数万次浏览),这并非偶然。看似简单的转换背后隐藏着陷阱,尤其是在涉及性能时。
在本文中,我们不仅仅列举“如何做”的方法。我们将深入探讨为什么某些方法比其他方法更快,以及何时应该使用它们。您将学习如何避免不必要的复制、多余的内存分配,以及如何编写真正高效的 Rust 代码。
\n\n为什么需要了解这些?背景与性能
\nRust 是一种让您控制内存的语言。但控制力也伴随着责任。字符串与字节之间的转换是最常见的操作之一。例如,在处理网络协议、数据序列化(JSON、Protobuf)或仅仅是读取文件时。选择错误的转换方式可能导致复制本可以无需复制就能使用的数据。在系统编程的世界里,这至关重要。
\n\n让我们分析四种基本类型,并学习如何无损耗地在它们之间切换。
\n\nString 和 &str:一枚硬币的两面
\nString 是一个拥有所有权的、可变的、UTF-8 编码的字符串。&str 是一个借用的、不可变的字符串切片。主要规则是:String 是数据的所有者,&str 是对数据的引用。
如何从 String 获取 &str(以及反向操作)
\n这是最简单、最安全的情况。
\n\nlet my_string = String::from("Привет, мир!");\n// 1. 从 String 获取 &str(仅仅是借用)\nlet my_str: &str = &my_string;\n// 或者使用 as_str()\nlet my_str2: &str = my_string.as_str();\n\n// 2. 从 &str 获取 String(创建一个新的副本)\nlet new_string: String = my_str.to_string();\n// 或者\nlet new_string2: String = String::from(my_str);\n\n重要提示: 将 &str 转换为 String 总是会分配新的内存。这很昂贵。将 String 转换为 &str 仅仅是获取一个引用(零成本)。
如何从 String 获取 Vec<u8>(以及反向操作)
\n这里开始变得有趣,因为 String 内部实际上就是一个 Vec<u8>,并带有 UTF-8 有效性的保证。
let my_string = String::from("Rust");\n\n// 3. String -> Vec<u8>\n// 选项 A(复制):安全但昂贵\nlet bytes_vec_copy: Vec<u8> = my_string.as_bytes().to_vec();\n\n// 选项 B(无复制,消费 String):\nlet bytes_vec_owned: Vec<u8> = my_string.into_bytes(); // my_string 不再存在!\n\n// 4. Vec<u8> -> String(检查 UTF-8 有效性)\nlet new_string: String = String::from_utf8(bytes_vec_owned).expect("无效的 UTF-8");\n// 如果您确定其有效性:\nlet new_string_unchecked: String = unsafe { String::from_utf8_unchecked(bytes_vec_copy) };\n\n关键点: into_bytes() 是一个零成本的转换。它获取内部的 Vec<u8> 缓冲区并将其交给您,不复制任何字节。as_bytes().to_vec() 则是显式的复制。
from_utf8_unchecked 方法是 unsafe 的。仅当您 100% 确定字节是有效的 UTF-8 时才使用它。否则,程序可能会 panic 崩溃,或者更糟,导致未定义行为。