一文帶你掌握Rust中的字符串與切片
字符串與切片是所有新手遇到的第一個(gè)門(mén)檻,不同于 Java、Python 等語(yǔ)言對(duì)字符串的高度封裝,Rust 的字符串與切片深度綁定了所有權(quán)、借用、生命周期與 UTF-8 編碼,從編譯期就將亂碼、內(nèi)存安全等問(wèn)題解決。
什么是切片(Slice)
字符串是切片的特殊場(chǎng)景,想要理解字符串,必須先搞懂切片。在 Rust 里,切片是描述一組屬于同一類型、長(zhǎng)度不確定的、在內(nèi)存中連續(xù)存放的數(shù)據(jù)結(jié)構(gòu),用 [T] 來(lái)表述。因?yàn)殚L(zhǎng)度不確定,切片屬于動(dòng)態(tài)大小類型(DST, Dynamically Sized Type),無(wú)法直接在棧上存儲(chǔ),必須通過(guò)引用(&[T],不可變切片)或可變引用(&mut [T],可變切片)來(lái)使用。
切片通過(guò) Rust 的范圍語(yǔ)法 [start..end] 創(chuàng)建,遵循左閉右開(kāi)原則,支持多種簡(jiǎn)寫(xiě)形式:
[a..b]:從索引a到b-1的切片[a..]:從索引a到集合末尾的切片[..b]:從集合開(kāi)頭到索引b-1的切片[..]:覆蓋整個(gè)集合的全切片
示例如下:
fn main() {
// 原數(shù)組:切片的底層數(shù)據(jù)所有者
let arr = [1, 2, 3, 4, 5];
// 創(chuàng)建切片:引用數(shù)組的第1到第3個(gè)元素
let slice: &[i32] = &arr[1..4];
println!("切片內(nèi)容: {:?}", slice); // 輸出 [2, 3, 4]
println!("切片長(zhǎng)度: {}", slice.len()); // 輸出 3
println!("切片是否為空: {}", slice.is_empty()); // 輸出 false
}
Rust 的切片從編譯期就杜絕了兩類經(jīng)典內(nèi)存問(wèn)題:
- 越界訪問(wèn):編譯期會(huì)校驗(yàn)切片范圍,運(yùn)行時(shí)也會(huì)做邊界檢查,直接拒絕非法訪問(wèn)
- 懸垂引用:生命周期規(guī)則保證,只要切片有效,底層數(shù)據(jù)的所有者就一定不會(huì)被釋放或修改
示例如下:
// 獲取數(shù)組中第一個(gè)非零元素的切片
fn first_non_zero(arr: &[i32]) -> &[i32] {
for (i, &num) in arr.iter().enumerate() {
if num != 0 {
return &arr[i..];
}
}
&arr[..0]
}
fn main() {
let arr = [0, 0, 3, 5, 7];
let non_zero_slice = first_non_zero(&arr);
// 編譯報(bào)錯(cuò):無(wú)法修改原數(shù)組,因?yàn)樗呀?jīng)被切片借用
// arr[2] = 0;
println!("非零切片: {:?}", non_zero_slice); // 輸出 [3, 5, 7]
}
這個(gè)例子體現(xiàn)了 Rust 的安全哲學(xué):在切片的生命周期內(nèi),原數(shù)據(jù)無(wú)法被修改,徹底避免了數(shù)據(jù)競(jìng)爭(zhēng)與懸垂引用。
Rust 字符串的兩大核心類型
Rust 的字符串體系看似復(fù)雜,核心只有兩個(gè)類型,其他都是面向特定場(chǎng)景的擴(kuò)展:
&str:字符串切片,無(wú)所有權(quán),只讀,是切片的 UTF-8 版本String:可擁有、可修改的字符串類型,底層是堆上分配的Vec<u8>封裝
兩者的關(guān)系如同 &[T] 與 Vec<T>:&str 是數(shù)據(jù)的“視圖”,String 是數(shù)據(jù)的“所有者”。
Rust 字符串的核心設(shè)計(jì):強(qiáng)制 UTF-8 編碼
很多新手可能會(huì)困惑為什么 Python、Go 可以直接用索引 s[i] 取字符,而 Rust 卻不行?原因在于:Rust 的字符串強(qiáng)制使用 UTF-8 編碼,而 UTF-8 是變長(zhǎng)編碼。
- ASCII 字符占 1 個(gè)字節(jié)
- 中文、日文等東亞字符占 3 個(gè)字節(jié)
- Emoji 等特殊字符占 4 個(gè)字節(jié)
如果允許直接按索引訪問(wèn),會(huì)帶來(lái)兩個(gè)致命問(wèn)題:
- 索引訪問(wèn)的時(shí)間復(fù)雜度不再是
O(1),必須遍歷字符串才能定位到對(duì)應(yīng)字符 - 極易訪問(wèn)到字符的中間字節(jié),生成非法 UTF-8 序列,引發(fā)未定義行為(UB)
因此 Rust 從語(yǔ)法層面禁止了直接通過(guò)索引訪問(wèn)字符串字符,只允許通過(guò)合法的方式遍歷和操作。
字節(jié)與字符的正確操作
fn main() {
let s = "你好Rust";
println!("字節(jié)長(zhǎng)度: {}", s.len()); // 輸出 10(2個(gè)中文*3字節(jié) + 4個(gè)ASCII字符=10)
println!("字符數(shù)量: {}", s.chars().count()); // 輸出 6
// 正確方式1:遍歷所有字符(Unicode標(biāo)量值)
println!("=== 字符遍歷 ===");
for c in s.chars() {
println!("字符: {}", c);
}
// 正確方式2:遍歷所有字節(jié)
println!("=== 字節(jié)遍歷 ===");
for b in s.bytes() {
println!("字節(jié): {}", b);
}
}
字符串切片的致命坑:字符邊界問(wèn)題
Rust 編譯期不會(huì)檢查切片范圍是否符合 UTF-8 字符邊界,只有運(yùn)行時(shí)會(huì)校驗(yàn),一旦切到字符中間,會(huì)直接觸發(fā) panic。
fn main() {
let s = "你好";
// 反例:運(yùn)行時(shí) panic!"你"占3個(gè)字節(jié),[0..2]切到了字符中間
// let sub = &s[0..2];
// 正確示例:嚴(yán)格按字符邊界切片
let sub = &s[0..3];
println!("{}", sub); // 輸出 你
}
所以在實(shí)際開(kāi)發(fā)中最佳實(shí)踐是:不要硬編碼索引切片字符串,優(yōu)先通過(guò) chars().enumerate() 定位字符位置,再進(jìn)行切片操作。
字符串與切片的實(shí)戰(zhàn)常用操作
String 的修改操作(僅所有者可用)
只有持有所有權(quán)的 String 可以修改內(nèi)容,常用方法如下:
fn main() {
let mut s = String::from("Hello");
// 追加字符串切片
s.push_str(" World");
// 追加單個(gè)字符
s.push('!');
println!("{}", s); // 輸出 Hello World!
// 插入字符
s.insert(5, ',');
println!("{}", s); // 輸出 Hello, World!
// 彈出最后一個(gè)字符
let last_char = s.pop();
println!("彈出的字符: {:?}", last_char); // 輸出 Some('!')
// 清空字符串
s.clear();
println!("清空后是否為空: {}", s.is_empty()); // 輸出 true
}
字符串拼接的三種方式與選型
| 方法 | 用法 | 所有權(quán)影響 | 適用場(chǎng)景 |
|---|---|---|---|
| + 運(yùn)算符 | String + &str | 左側(cè) String 所有權(quán)被轉(zhuǎn)移 | 簡(jiǎn)單拼接,無(wú)需保留原 String |
| format! 宏 | format!("{} {}", a, b) | 不轉(zhuǎn)移任何所有權(quán) | 復(fù)雜拼接、多變量拼接,可讀性優(yōu)先 |
| push_str 方法 | s.push_str(&str) | 僅修改原 String,不轉(zhuǎn)移 | 循環(huán)追加、動(dòng)態(tài)構(gòu)建字符串 |
示例如下:
fn main() {
let s1 = String::from("Hello");
let s2 = "Rust";
// + 運(yùn)算符:s1所有權(quán)被轉(zhuǎn)移,后續(xù)無(wú)法使用
let s3 = s1 + " " + s2;
println!("+ 拼接結(jié)果: {}", s3); // 輸出 Hello Rust
// format! 宏:不轉(zhuǎn)移所有權(quán),最靈活
let a = String::from("你好");
let b = String::from("世界");
let c = format!("{},{}!", a, b);
println!("format! 結(jié)果: {}", c); // 輸出 你好,世界!
println!("a仍可用: {}", a); // 所有權(quán)未轉(zhuǎn)移,可正常使用
}
字符串切片的常用工具方法
fn main() {
let s = " Hello Rust ";
// 去除首尾空白
println!("trim后: '{}'", s.trim()); // 輸出 'Hello Rust'
// 前綴/后綴判斷
let trimmed = s.trim();
println!("是否以Hello開(kāi)頭: {}", trimmed.starts_with("Hello")); // 輸出 true
println!("是否以Rust結(jié)尾: {}", trimmed.ends_with("Rust")); // 輸出 true
// 包含判斷與查找
println!("是否包含Rust: {}", s.contains("Rust")); // 輸出 true
println!("Rust的起始位置: {:?}", s.find("Rust")); // 輸出 Some(8)
// 分割字符串
let parts: Vec<&str> = trimmed.split_whitespace().collect();
println!("分割結(jié)果: {:?}", parts); // 輸出 ["Hello", "Rust"]
}
最常用的轉(zhuǎn)換:String 與 & str 的互轉(zhuǎn)
Rust 提供了非常便捷的轉(zhuǎn)換能力,核心是 Deref 強(qiáng)制轉(zhuǎn)換機(jī)制:&String 可以自動(dòng)被編譯器轉(zhuǎn)換為 &str,無(wú)需手動(dòng)處理。
這也是實(shí)際開(kāi)發(fā)中的一個(gè)最佳實(shí)踐:函數(shù)參數(shù)優(yōu)先使用 &str,而非 &String。因?yàn)?&str 可以同時(shí)接受字符串字面量、&String、其他字符串切片,通用性最大。
// 函數(shù)參數(shù)使用&str,獲得最大通用性
fn print_info(s: &str) {
println!("內(nèi)容: {},字節(jié)長(zhǎng)度: {}", s, s.len());
}
fn main() {
let s = String::from("Hello Rust");
// 直接傳&String,自動(dòng)轉(zhuǎn)換為&str
print_info(&s);
// 傳字符串字面量,完全兼容
print_info("你好世界");
// 手動(dòng)創(chuàng)建全切片,效果完全一致
print_info(&s[..]);
}
擴(kuò)展:其他字符串相關(guān)類型
除了核心的 &str 和 String,Rust 標(biāo)準(zhǔn)庫(kù)還提供了面向特定場(chǎng)景的字符串類型,無(wú)需深入學(xué)習(xí),了解其用途即可:
OsStr/OsString:操作系統(tǒng)兼容的字符串,支持非 UTF-8 的路徑、系統(tǒng)參數(shù),位于std::ffi模塊CStr/CString:與 C 語(yǔ)言交互的字符串,以\0結(jié)尾,符合 C 語(yǔ)言字符串規(guī)范,位于std::ffi模塊Path/PathBuf:路徑專用類型,基于OsStr封裝,提供路徑相關(guān)的專用方法,位于std::path模塊
總結(jié)
Rust 的字符串與切片設(shè)計(jì),看似嚴(yán)苛,實(shí)則是圍繞內(nèi)存安全、UTF-8 原生支持和零成本抽象三個(gè)核心目標(biāo),它把其他語(yǔ)言中運(yùn)行時(shí)才會(huì)暴露的字符串亂碼、越界、懸垂引用等問(wèn)題,提前到了編譯期解決。
理解這些設(shè)計(jì)背后的邏輯,你就能真正掌握 Rust 字符串與切片,寫(xiě)出既安全又高效的代碼。
以上就是一文帶你掌握Rust中的字符串與切片的詳細(xì)內(nèi)容,更多關(guān)于Rust字符串與切片的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Rust調(diào)用Windows API 如何獲取正在運(yùn)行的全部進(jìn)程信息
本文介紹了如何使用Rust調(diào)用WindowsAPI獲取正在運(yùn)行的全部進(jìn)程信息,通過(guò)引入winapi依賴并添加相應(yīng)的features,可以實(shí)現(xiàn)對(duì)不同API集的調(diào)用,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-11-11
rust 一個(gè)日志緩存記錄的通用實(shí)現(xiàn)方法
本文給出了一個(gè)通用的設(shè)計(jì)模式,通過(guò)建造者模式實(shí)例化記錄對(duì)象,可自定義格式化器將實(shí)例化后的記錄對(duì)象寫(xiě)入到指定的緩存對(duì)象中,這篇文章主要介紹了rust 一個(gè)日志緩存記錄的通用實(shí)現(xiàn)方法,需要的朋友可以參考下2024-04-04
Rust-使用dotenvy加載和使用環(huán)境變量的過(guò)程詳解
系統(tǒng)的開(kāi)發(fā),測(cè)試和部署離不開(kāi)環(huán)境變量,今天分享在Rust的系統(tǒng)開(kāi)發(fā)中,使用dotenvy來(lái)讀取和使用環(huán)境變量,感興趣的朋友跟隨小編一起看看吧2023-11-11
使用環(huán)境變量實(shí)現(xiàn)Rust程序中的不區(qū)分大小寫(xiě)搜索方式
本文介紹了如何在Rust中實(shí)現(xiàn)不區(qū)分大小寫(xiě)的搜索功能,并通過(guò)測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(TDD)方法逐步實(shí)現(xiàn)該功能,通過(guò)修改運(yùn)行函數(shù)和獲取環(huán)境變量,程序可以根據(jù)環(huán)境變量控制搜索模式2025-02-02
教你使用RustDesk?搭建一個(gè)自己的遠(yuǎn)程桌面中繼服務(wù)器
這篇文章主要介紹了RustDesk?搭建一個(gè)自己的遠(yuǎn)程桌面中繼服務(wù)器,主要包括服務(wù)端安裝和客戶端配置方法,配置好相關(guān)操作輸入控制碼即可發(fā)起遠(yuǎn)程或文件傳輸,本文通過(guò)圖文給大家講解的非常詳細(xì),需要的朋友可以參考下2022-08-08

