Rust语言圣经中定义
str
Rust 语言类型大致分为两种:基本类型和标准库类型,前者由语言特性直接提供,后者在标准库中定义
str 是唯一定义在 Rust 语言特性中的字符串,但也是几乎不会用到的字符串类型
str 字符串是 DST 动态大小类型,编译器无法在编译期知道 str 类型的大小,只有到了运行期才能动态获知
这对于强类型、强安全的 Rust 语言来说不可接受
let string: str = "banana";
创建一个 str 类型的字符串,编译会报错:
error[E0277]: the size for values of type `str` cannot be known at compilation time
--> src/main.rs:4:9
|
4 | let string: str = "banana";
| ^^^^^^ doesn't have a size known at compile-time
原因:
所有的切片都是动态类型,无法直接被使用,str 是字符串切片,[u8] 是数组切片
str 是 String 和 &str 的底层数据类型
String
str 类型是硬编码进可执行文件,无法被修改
String 是一个可增长、可改变且具有所有权的 UTF-8 编码的字符串
Rust 提到字符串时,往往指的是 String 类型和 &str 字符串切片类型,这两个类型都是 UTF-8 编码
示例
其他
Rust 的标准库还提供了其他类型的字符串,例如 OsString, OsStr, CsString 和 CsStr 等
这些名字都以 String 或者 Str 结尾,它们分别对应的是具有所有权和被借用的变量
具体区分
String
类型本质:String 是一个可变的、拥有所有权的字符串类型,存储在堆上,并且可以动态增长
内存管理:由 Rust 的所有权系统管理,当 String 离开作用域时,其占用的内存会被自动释放
使用场景:当需要对字符串进行修改(如追加、删除字符)时使用
fn main() {
let mut s = String::from("hello");
s.push_str(", world!");
println!("{}", s);
}
str
类型本质:str 是一个不可变的、无固定大小的字符串切片类型,通常被称为 “字符串切片”。没有所有权,只是对存储在其他地方的字符串数据的引用
内存管理:由于 str 是无固定大小的类型,不能直接使用,通常以引用的形式 &str
出现
使用场景:用于表示一个字符串的一部分,如字符串的子串
&str
类型本质:&str
是对 str 类型的引用,是一个不可变的字符串切片。它指向一个连续的 UTF - 8 编码的字节序列
内存管理:是一个借用类型,不拥有底层数据的所有权,只要借用的对象存在,&str
就有效
使用场景:当只需要读取字符串内容而不需要修改它时,使用 &str
作为函数参数可以接受多种字符串类型(如 String 和字面量字符串)
fn print_string(s: &str) {
println!("{}", s);
}
fn main() {
let s1 = String::from("hello");
print_string(&s1);
print_string("world");
}
&String
类型本质:&String
是对 String 类型的引用
内存管理:是一个借用类型,不拥有 String 的所有权,只要 String 对象存在,&String
就有效
使用场景:通常在已经有 String 类型的变量,而函数参数要求引用时使用
fn print_string_ref(s: &String) {
println!("{}", s);
}
fn main() {
let s = String::from("hello");
print_string_ref(&s);
}
Box<str>
类型本质:Box<str>
是将 str 类型装箱到堆上的类型。它拥有底层 str 的所有权
内存管理:当 Box<str>
离开作用域时,底层的 str 数据会被自动释放
使用场景:当需要在堆上存储 str 数据,并且希望拥有其所有权时使用
fn main() {
let s: Box<str> = "hello".into();
println!("{}", s);
}
Box<&str>
类型本质:Box<&str>
不是一个常见用法,&str
本身是引用类型,将其装箱没意义。Box<&str>
会在堆上存储一个 &str
引用
内存管理:Box<&str>
拥有这个引用的所有权,但引用指向的数据本身的生命周期需要单独管理
使用场景:一般不建议使用,容易造成混淆
OsString 和 OsStr
类型本质:OsString 是一个拥有所有权的字符串类型,用于表示操作系统相关的字符串,如文件路径。OsStr 是 OsString 的不可变视图,类似于 str 是 String 的不可变视图
内存管理:OsString 管理自己的内存,OsStr 是借用类型
使用场景:在处理与操作系统交互的字符串时使用,如文件系统操作
use std::ffi::OsString;
use std::path::PathBuf;
fn main() {
let os_string = OsString::from("example.txt");
let path = PathBuf::from(os_string);
println!("{:?}", path);
}
注意:OsString 和 String 的内存管理不一样
CsString 和 CsStr
类型本质:CsString 和 CsStr 是 cstr_core crate 中的类型,用于处理以空字符结尾的 C 风格字符串。CsString 拥有字符串数据,CsStr 是不可变视图
内存管理:CsString 管理自己的内存,CsStr 是借用类型
使用场景:在与 C 代码进行交互时,需要处理 C 风格字符串时使用
use cstr_core::CStr;
use std::ffi::CString;
fn main() {
let c_string = CString::new("hello").unwrap();
let c_str = c_string.as_c_str();
println!("{:?}", c_str);
}
注意:CsString 和 String 的内存管理不一样
如何查看字符串的大小
String
fn main() {
// String 内部是以 UTF - 8 字节序列存储
// String 是 Rust 标准库中用于表示可增长、拥有所有权的 UTF - 8 编码字符串的类型
let s = String::from("Hello, 世界");
// 获取字节长度
let byte_length = s.len();
// 13
println!("Byte length: {}", byte_length);
// 获取字符数量
let char_count = s.chars().count();
// 9
println!("Character count: {}", char_count);
}
CsString
use cstr_core::CString;
fn main() {
let cs_string = CString::new("Hello, C-style").expect("Failed to create CString");
// 获取不包含空字符的字节长度
let byte_length_without_nul = cs_string.as_c_str().to_bytes().len();
// 14
println!("Byte length without null terminator: {}", byte_length_without_nul);
// 获取包含空字符的字节长度
let byte_length_with_nul = cs_string.as_c_str().to_bytes_with_nul().len();
// 15
println!("Byte length with null terminator: {}", byte_length_with_nul);
}
OsString
可以将 OsString 转换为 OsStr,然后根据操作系统的编码方式将其转换为 &str
或字节切片来获取长度
use std::ffi::OsString;
fn main() {
let os_string = OsString::from("Hello, OS");
// 尝试将 OsString 转换为 &str 并获取长度
if let Some(s) = os_string.to_str() {
let byte_length = s.len();
// 9
println!("Byte length: {}", byte_length);
}
}
在上述代码中,os_string.to_str() 尝试将 OsString 转换为 &str,如果转换成功,则可以使用 len() 方法获取其字节长度。需要注意的是,to_str() 可能会失败,因为 OsString 可能包含非 UTF - 8 编码的数据。如果需要处理非 UTF - 8 数据,可以使用 os_string.into_vec() 方法将其转换为字节向量,然后获取向量的长度。
综上所述,不同类型的字符串获取长度的方法有所不同,需要根据具体类型和需求选择合适的方法。
std::ffi::OsString的os_string.to_str()
pub fn to_str(&self) -> Option<&str>
将&OsString
转为Option<&str>
pub trait Deref {
type Target: ?Sized;
// Required method
fn deref(&self) -> &Self::Target;
}
//
impl ops::Deref for OsString {
type Target = OsStr;
#[inline]
fn deref(&self) -> &OsStr {
&self[..]
}
}
to_str() 是 OsStr 类型的一个方法,OsString 可以通过自动解引用转换为 OsStr 来调用这个方法。
to_str() 方法的作用是尝试将 OsStr 转换为 &str
它的实现原理是检查 OsStr 内部存储的字节序列是否是有效的 UTF - 8 编码。如果是有效的 UTF - 8 编码,就返回一个 Some(&str)
;如果不是有效的 UTF - 8 编码,则返回 None
Deref 机制主要用于在需要某个类型的引用时,自动将一个类型的引用转换为另一个类型的引用。
String 实现了 Deref<Target = str>
,意味着当有一个 &String
类型的变量时,Rust 会自动将其转换为 &str
,以便可以调用 str 类型的方法。
os_string.to_str()
并不是依赖 Deref 来完成从 OsString 到 &str
的转换。它是通过检查 OsStr 内部字节序列的 UTF - 8 有效性来进行转换的。
自动解引用
以 OsString 转换 OsStr 为例
在 Rust 中,OsString 可以自动解引用转换为 OsStr,这种转换主要发生在以下几种场景:
调用 OsStr 方法时
当调用一个 OsStr 类型的方法,而实际操作的是 OsString 实例时,Rust 会自动进行解引用转换。
这是因为 OsString 实现了 Deref<Target = OsStr>
特征,该特征允许 Rust 在需要 OsStr 引用的地方使用 OsString 引用
use std::ffi::OsString;
fn main() {
let os_string = OsString::from("example.txt");
// 调用 OsStr 的 to_str 方法,这里自动将 OsString 转换为 OsStr
if let Some(s) = os_string.to_str() {
println!("Converted to &str: {}", s);
}
}
在上述代码中,os_string 是 OsString 类型,但 to_str 是 OsStr 类型的方法
作为函数参数传递时
当一个函数的参数类型是 &OsStr
,而传递的是 &OsString
时,Rust 会自动进行解引用转换。
use std::ffi::{OsStr, OsString};
fn print_os_str(os_str: &OsStr) {
if let Some(s) = os_str.to_str() {
println!("{}", s);
}
}
fn main() {
let os_string = OsString::from("test.txt");
// 自动将 &OsString 转换为 &OsStr 传递给函数
print_os_str(&os_string);
}
赋值给 &OsStr 类型变量时
当将一个 &OsString
赋值给一个 &OsStr
c类型的变量时,也会发生自动解引用转换。
use std::ffi::OsString;
fn main() {
let os_string = OsString::from("file.txt");
// 自动将 &OsString 转换为 &OsStr
let os_str: &OsStr = &os_string;
if let Some(s) = os_str.to_str() {
println!("{}", s);
}
}
这里,&os_string
是 &OsString
类型,而 os_str 是 &OsStr
类型,Rust 会自动完成转换。
既然可以通过Defef自动转换,那还要as_os_str干嘛
pub fn as_os_str(&self) -> &OsStr
显式表达意图
代码的可读性和可维护性在软件开发中至关重要。使用 as_os_str 方法可以更清晰地表达想要将 OsString 转换为 &OsStr
的意图。相比自动解引用,显式调用方法能让阅读代码的人一眼就明白正在进行类型转换操作。
use std::ffi::OsString;
fn main() {
let os_string = OsString::from("example.txt");
// 显式转换,意图清晰
let os_str = os_string.as_os_str();
if let Some(s) = os_str.to_str() {
println!("{}", s);
}
}
避免潜在的混淆
在某些复杂的代码场景中,自动解引用可能会导致代码的行为变得难以理解。自动解引用是 Rust 编译器在背后自动完成的,当代码中有多个类型实现了 Deref 特征时,可能会引发混淆。使用 as_os_str 可以避免这种潜在的混淆,让代码的行为更加明确。
与其他类型转换保持一致性
在 Rust 标准库中,很多类型都提供了显式的类型转换方法,比如 String 有 as_str 方法用于转换为 &str
,Vec<T>
有 as_slice 方法用于转换为 &[T]
。OsString 的 as_os_str 方法与这些设计保持一致,使得代码的风格更加统一。
代码审查和调试
在代码审查过程中,显式的类型转换方法更容易被审查人员识别和理解。同时,在调试代码时,显式调用方法可以让调试者更清楚地看到类型转换的位置和过程,有助于快速定位问题。
总结
尽管 Deref 自动转换提供了便利,但 as_os_str 方法通过显式表达意图、避免混淆、保持一致性以及方便代码审查和调试等方面,为代码的质量和可维护性提供了保障。