Rust字符串類型全解析(最新推薦)
字符串是每種編程語言都繞不開的類型,
不過,在Rust中,你會看到遠比其他語言更加豐富多樣的字符串類型。
如下圖:

為什么Rust中需要這么多種表示字符串的類型呢?
初學(xué)Rust時,可能無法理解為什么要這樣設(shè)計?為什么要給使用字符串帶來這么多不必要的復(fù)雜性?
其實,Rust中對于字符串的設(shè)計,優(yōu)先考慮的是安全,高效和靈活,
所以在易用性方面,感覺沒有其他語言(比如python,golang)那么易于理解和掌握。
本文嘗試解釋Rust中的所有不同的字符串類型,以及它們各自的特點。
希望能讓大家更好的理解Rust為了安全和發(fā)揮最大性能的同時,是如何處理字符串的。
1. 機器中的字符串
我們代碼中的字符串或者數(shù)字,存儲在機器中,都是二進制,也就是0和1組成的序列。

程序?qū)⒍M制數(shù)據(jù)轉(zhuǎn)換為人類可讀的字符串 需要兩個關(guān)鍵信息:
- 字符編碼
- 字符串長度
常見的編碼有ASCII,UTF-8等等,編碼就是二進制序列對應(yīng)的字符,
比如,ASCII是8位二進制對應(yīng)一個字符,所以它最多只能表示256種不同的字符。
而UTF-8可以使用8位~32位二進制來表示一個字符,這意味著它可以編碼超過一百萬個字符,
包括世界上的每種語言和各種表情符號等復(fù)雜字符。
通過字符編碼,我們可以將二進制和字符互相轉(zhuǎn)換,
再通過字符串長度信息,我們將內(nèi)存中的二進制轉(zhuǎn)換為字符串時,就能知道何時停止。
Rust中的字符串,統(tǒng)一采用UTF-8編碼,下面一一介紹各種字符串類型及其使用場景。
2. String 和 &str
String和&str是Rust中使用最多的兩種字符串類型,也是在使用中容易混淆的兩種類型。
String是分配在堆上的,可增長的UTF-8字符串,
它擁有底層的數(shù)據(jù),并且在超出其定義的范圍被自動清理釋放。
let my_string = String::from("databook");
println!(
"pointer: {:p}, length: {}, capacity: {}",
&my_string,
my_string.len(),
my_string.capacity()
);
對于一個String,主要部分有3個:
Pointer:指向堆內(nèi)存中字符串的起始位置Length:有效字符串的長度Capacity:字符串my_string總共占用的空間
注意這里Length和Capacity的區(qū)別,Length是my_string中有效字符的長度,也就是字符串實際的長度;
Capacity表示系統(tǒng)為my_string分配的內(nèi)存空間,一般來說,Capacity >= Length。
通常不需要直接處理Capacity,但它的存在對于編寫高效且資源敏感的Rust代碼時很重要。
特別是,當(dāng)你知道即將向String添加大量內(nèi)容時,可能會事先手動保留足夠的Capacity以避免多次內(nèi)存重新分配。
&str則是一個字符串的切片,它表示一個連續(xù)的字符序列,
它是一個借用類型,并不擁有字符串?dāng)?shù)據(jù),只包含指向切片開頭的指針和切片長度。
let my_str: &str = "databook";
println!("pointer: {:p}, length: {}", &my_str, my_str.len());
注意,&str沒有Capacity方法,因為它只是一個借用,內(nèi)容不可能增加。
最后,對于String和&str,使用時建議:
- 在運行時動態(tài)創(chuàng)建或修改字符串?dāng)?shù)據(jù)時,請使用
String - 讀取或分析字符串?dāng)?shù)據(jù)而不對其進行更改時,請使用
&str
3. Vec[u8] 和 &[u8]
這兩種形式是將字符串表示位字節(jié)的形式,其中Vec[u8]是字節(jié)向量,&[u8]是字節(jié)切片。
它們只是將字符串中的各個字符轉(zhuǎn)換成字節(jié)形式。
as_bytes方法可將&str轉(zhuǎn)換為&[u8];
into_bytes方法可將String轉(zhuǎn)換為Vec<u8>。
let my_str: &str = "databook";
let my_string = String::from("databook");
let s: &[u8] = my_str.as_bytes();
let ss: Vec<u8> = my_string.into_bytes();
println!("s: {:?}", s);
println!("ss: {:?}", ss);
/* 運行結(jié)果
s: [100, 97, 116, 97, 98, 111, 111, 107]
ss: [100, 97, 116, 97, 98, 111, 111, 107]
*/
在UTF-8編碼中,每個英文字母對應(yīng)1個字節(jié),而一個中文漢字對應(yīng)3個字節(jié)。
let my_str: &str = "中文";
let my_string = String::from("中文");
let s: &[u8] = my_str.as_bytes();
let ss: Vec<u8> = my_string.into_bytes();
println!("s: {:?}", s);
println!("ss: {:?}", ss);
/* 運行結(jié)果
s: [228, 184, 173, 230, 150, 135]
ss: [228, 184, 173, 230, 150, 135]
*/
Vec[u8]和&[u8]以字節(jié)的形式存儲字符串,不用關(guān)心字符串的具體編碼,
這在網(wǎng)絡(luò)中傳輸二進制文件或者數(shù)據(jù)包時非常有用,可以有效每次傳輸多少個字節(jié)。
4. str 系列
str類型本身是不能直接使用的,因為它的大小在編譯期無法確定,不符合Rust的安全規(guī)則。
但是,它可以與其他具有特殊用途的指針類型一起使用。
4.1. Box<str>
如果需要一個字符串切片的所有權(quán)(&str是借用的,沒有所有權(quán)),那么可以使用Box智能指針。
當(dāng)你想要凍結(jié)字符串以防止進一步修改或通過刪除額外容量來節(jié)省內(nèi)存時,它非常有用。
比如,下面的代碼,我們將一個String轉(zhuǎn)換為Box<str>,
這樣,可以確保它不會在其他地方被修改,也可以刪除它,因為Box<str>擁有字符串的所有權(quán)。
let my_string = String::from("databook");
let my_box_str = my_string.into_boxed_str();
println!("{}", my_box_str);
// 這一步會報錯,因為所有權(quán)已經(jīng)轉(zhuǎn)移
// 這是 Box<str> 和 &str 的區(qū)別
// println!("{}", my_string);4.2. Rc<str>
當(dāng)你想要在多個地方共享一個不可變的字符串的所有權(quán),但是又不克隆實際的字符串?dāng)?shù)據(jù)時,
可以嘗試使用Rc<str>智能指針。
比如,我們有一個非常大的文本,想在多個地方使用,又不想復(fù)制多份占用內(nèi)存,可以用Rc<str>。
let my_str: &str = "very long text ....";
let rc_str1: Rc<str> = Rc::from(my_str);
let rc_str2 = Rc::clone(&rc_str1);
let rc_str3 = Rc::clone(&rc_str1);
println!("rc_str1: {}", rc_str1);
println!("rc_str2: {}", rc_str2);
println!("rc_str3: {}", rc_str3);
/* 運行結(jié)果
rc_str1: very long text ....
rc_str2: very long text ....
rc_str3: very long text ....
*/這樣,在不實際克隆字符串?dāng)?shù)據(jù)的情況下,讓多個變量擁有其所有權(quán)。
4.3. Arc<str>
Arc<str>與Rc<str>的功能類似,主要的區(qū)別在于Arc<str>是線程安全的。
如果在多線程環(huán)境下,請使用Arc<str>。
let my_str: &str = "very long text ....";
let arc_str: Arc<str> = Arc::from(my_str);
let mut threads = vec![];
let mut cnt = 0;
while cnt < 5 {
let s = Arc::clone(&arc_str);
let t = thread::spawn(move || {
println!("thread-{}: {}", cnt, s);
});
threads.push(t);
cnt += 1;
}
for t in threads {
t.join().unwrap();
}
/* 運行結(jié)果
thread-0: very long text ....
thread-3: very long text ....
thread-2: very long text ....
thread-1: very long text ....
thread-4: very long text ....
*/上面的代碼中,在5個線程中共享了字符串?dāng)?shù)據(jù)。
上面運行結(jié)果中,線程順序是不固定的,多執(zhí)行幾遍會有不一樣的順序。
4.4. Cow<str>
Cow是Copy-on-Write(寫入時復(fù)制)的縮寫,
當(dāng)你需要實現(xiàn)一個功能,根據(jù)字符串的內(nèi)容來決定是否需要修改它,使用Cow就很合適。
比如,過濾敏感詞匯時,我們把敏感詞匯替換成xx。
fn filter_words(input: &str) -> Cow<str> {
if input.contains("sb") {
let output = input.replace("sb", "xx");
return Cow::Owned(output);
}
Cow::Borrowed(input)
}當(dāng)輸入字符串input中含有敏感詞sb時,會重新分配內(nèi)存,生成新字符串;
否則直接使用原字符串,提高內(nèi)存效率。
5. CStr 和 CString
CStr和CString是與C語言交互時用于處理字符串的兩種類型。
CStr用于在Rust中安全地訪問由C語言分配的字符串;
而CString用于在Rust中創(chuàng)建和管理可以安全傳遞給C語言函數(shù)的字符串。
C風(fēng)格的字符串與Rust中的字符串實現(xiàn)方式不一樣,
比如,C語言中的字符串都是以null字符\0結(jié)尾的字節(jié)數(shù)組,這點就與Rust很不一樣。
所以Rust單獨封裝了這兩種類型(CStr和CString),可以安全的與C語言進行字符串交互,從而實現(xiàn)與現(xiàn)有的C語言庫和API無縫集成。
6. OsStr 和 OsString
OsStr 和 OsString 是用于處理與操作系統(tǒng)兼容的字符串類型。
主要用于需要與操作系統(tǒng)API進行交互的場景,這些API一般特定于平臺的字符串編碼(比如Windows上的UTF-16,以及大多數(shù)Unix-like系統(tǒng)上的UTF-8)。
OsStr 和OsString 也相當(dāng)于str和String的關(guān)系,所以OsStr 一般不直接在代碼中使用,
使用比較多的是&OsStr和OsString。
這兩個類型一般用于讀取/寫入操作系統(tǒng)環(huán)境變量或者與系統(tǒng)API交互時,幫助我們確保字符串以正確的格式傳遞。
7. Path 和 PathBuf
這兩個類型看名字似乎和字符串關(guān)系不大,實際上它們是專門用來處理文件路徑字符串的。
在不同的文件系統(tǒng)中,對于文件路徑的格式,路徑中允許使用的字符都不一樣,比如,windows系統(tǒng)中文件路徑甚至不區(qū)分大小寫。
使用Path 和 PathBuf,我們編碼時就不用分散精力去關(guān)心具體使用的是哪種文件系統(tǒng)。
Path和PathBuf的主要區(qū)別在于可變性和所有權(quán),
如果需要頻繁讀取和查詢路徑信息而不修改它,Path是一個好選擇;
如果需要動態(tài)構(gòu)建或修改路徑內(nèi)容,PathBuf則更加合適。
8. 總結(jié)
總之,Rust中字符串類型之所以多,是因為根據(jù)不同的用途對字符串類型做了分類。
這也是為了處理不同的應(yīng)用場景時讓程序發(fā)揮最大的性能,畢竟,安全和高性能一直是Rust最大的賣點。
到此這篇關(guān)于Rust字符串類型全解析(最新推薦)的文章就介紹到這了,更多相關(guān)Rust字符串類型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust中類型轉(zhuǎn)換在錯誤處理中的應(yīng)用小結(jié)
隨著項目的進展,關(guān)于Rust的故事又翻開了新的一頁,今天來到了服務(wù)器端的開發(fā)場景,發(fā)現(xiàn)錯誤處理中的錯誤類型轉(zhuǎn)換有必要分享一下,對Rust錯誤處理相關(guān)知識感興趣的朋友一起看看吧2023-09-09
Go調(diào)用Rust方法及外部函數(shù)接口前置
這篇文章主要為大家介紹了Go調(diào)用Rust方法及外部函數(shù)接口前置示例實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06
Rust開發(fā)環(huán)境搭建到運行第一個程序HelloRust的圖文教程
本文主要介紹了Rust開發(fā)環(huán)境搭建到運行第一個程序HelloRust的圖文教程,文中通過圖文介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-12-12
Rust循環(huán)控制結(jié)構(gòu)用法詳解
Rust提供了多種形式的循環(huán)結(jié)構(gòu),每種都適用于不同的場景,在Rust中,循環(huán)有三種主要的形式:loop、while和for,本文將介紹Rust中的這三種循環(huán),并通過實例展示它們的用法和靈活性,感興趣的朋友一起看看吧2024-02-02
Rust聲明宏在不同K線bar類型中的應(yīng)用小結(jié)
在K線bar中,往往有很多不同分時k線圖,比如1,2,3,5,,,,,60,120,250,300…,,不同分鐘類型,如果不用宏,那么手寫會比較麻煩,下面就試用一下宏來實現(xiàn)不同類型的bar,感興趣的朋友一起看看吧2024-05-05

