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();// 13println!("Byte length: {}", byte_length);// 獲取字符數量let char_count = s.chars().count();// 9println!("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();// 14println!("Byte length without null terminator: {}", byte_length_without_nul);// 獲取包含空字符的字節長度let byte_length_with_nul = cs_string.as_c_str().to_bytes_with_nul().len();// 15println!("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();// 9println!("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 methodfn 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 轉換為 OsStrif 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 轉換為 &OsStrlet 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 方法通過顯式表達意圖、避免混淆、保持一致性以及方便代碼審查和調試等方面,為代碼的質量和可維護性提供了保障。