Rust 所有權(quán)(Ownership)的使用小結(jié)
在rust編程中,內(nèi)存管理是核心挑戰(zhàn)之一。不同編程語(yǔ)言采用不同策略處理內(nèi)存,而 Rust 獨(dú)有的所有權(quán)系統(tǒng),在編譯期就能確保內(nèi)存安全,且不影響運(yùn)行時(shí)性能。本教程將從內(nèi)存管理背景出發(fā),逐步講解所有權(quán)的核心概念、規(guī)則及實(shí)踐應(yīng)用。
一、內(nèi)存管理的三種流派
所有程序都需與內(nèi)存交互,如何申請(qǐng)、釋放內(nèi)存是語(yǔ)言設(shè)計(jì)的關(guān)鍵。目前主流內(nèi)存管理方式分為三類(lèi):
| 管理方式 | 核心邏輯 | 典型語(yǔ)言 | 優(yōu)缺點(diǎn)分析 |
|---|---|---|---|
| 垃圾回收(GC) | 程序運(yùn)行時(shí)自動(dòng)掃描“不再使用的內(nèi)存”并釋放 | Java、Go | 優(yōu)點(diǎn):無(wú)需手動(dòng)管理;缺點(diǎn):運(yùn)行時(shí)性能損耗 |
| 手動(dòng)管理內(nèi)存 | 通過(guò)代碼顯式調(diào)用函數(shù)申請(qǐng)(如 malloc)和釋放(如 free)內(nèi)存 | C++ | 優(yōu)點(diǎn):性能可控;缺點(diǎn):易出現(xiàn)內(nèi)存泄漏、野指針 |
| 所有權(quán)系統(tǒng)(Ownership) | 編譯器通過(guò)預(yù)設(shè)規(guī)則在編譯期檢查內(nèi)存使用,無(wú)運(yùn)行時(shí)開(kāi)銷(xiāo) | Rust | 優(yōu)點(diǎn):兼顧安全與性能;缺點(diǎn):需理解新規(guī)則 |
Rust 選擇所有權(quán)系統(tǒng),核心優(yōu)勢(shì)是:內(nèi)存安全檢查僅在編譯期執(zhí)行,運(yùn)行時(shí)無(wú)任何性能損失。
二、前置知識(shí):棧(Stack)與堆(Heap)
Rust 的所有權(quán)規(guī)則與內(nèi)存存儲(chǔ)位置(棧/堆)緊密相關(guān),理解二者差異是掌握所有權(quán)的基礎(chǔ)。
1. 棧(Stack):有序、高效的內(nèi)存區(qū)域
棧是后進(jìn)先出(LIFO) 的數(shù)據(jù)結(jié)構(gòu),類(lèi)似“疊盤(pán)子”——新數(shù)據(jù)放在頂部,取數(shù)據(jù)也從頂部開(kāi)始,無(wú)法直接操作中間數(shù)據(jù)。
- 存儲(chǔ)要求:數(shù)據(jù)大小必須已知且固定(編譯期可確定)。
- 操作效率:極高。入棧(存數(shù)據(jù))、出棧(取數(shù)據(jù))僅需移動(dòng)“棧指針”,無(wú)需復(fù)雜計(jì)算。
- 典型存儲(chǔ)數(shù)據(jù):基本類(lèi)型(如 i32、bool)、函數(shù)參數(shù)、局部變量。
2. 堆(Heap):靈活、無(wú)序的內(nèi)存區(qū)域
堆用于存儲(chǔ)大小未知或動(dòng)態(tài)變化的數(shù)據(jù)。當(dāng)需要存儲(chǔ)數(shù)據(jù)時(shí),程序會(huì)向操作系統(tǒng)申請(qǐng)一塊“足夠大的空閑內(nèi)存”,操作系統(tǒng)標(biāo)記該內(nèi)存為“已使用”并返回內(nèi)存地址(指針),程序再將指針存入棧中。
- 存儲(chǔ)流程:申請(qǐng)內(nèi)存(分配)→ 操作系統(tǒng)返回指針 → 指針存入棧 → 通過(guò)指針訪問(wèn)堆數(shù)據(jù)。
- 操作效率:較低。分配內(nèi)存時(shí)需操作系統(tǒng)尋找空閑區(qū)域,訪問(wèn)數(shù)據(jù)需先通過(guò)棧指針跳轉(zhuǎn),步驟更多。
- 典型存儲(chǔ)數(shù)據(jù):動(dòng)態(tài)大小類(lèi)型(如 String、數(shù)組)。
3. 棧與堆的性能對(duì)比
| 對(duì)比維度 | 棧(Stack) | 堆(Heap) |
|---|---|---|
| 分配速度 | 極快(僅移動(dòng)棧指針) | 較慢(需操作系統(tǒng)查找空閑內(nèi)存) |
| 訪問(wèn)速度 | 快(直接訪問(wèn)) | 較慢(需通過(guò)棧指針跳轉(zhuǎn)) |
| 數(shù)據(jù)大小要求 | 固定且已知(編譯期確定) | 可動(dòng)態(tài)變化(運(yùn)行時(shí)確定) |
| 自動(dòng)釋放 | 函數(shù)結(jié)束后自動(dòng)出棧釋放 | 需手動(dòng)/所有權(quán)系統(tǒng)管理釋放 |
三、所有權(quán)的核心規(guī)則
所有權(quán)是 Rust 管理堆內(nèi)存的核心機(jī)制,必須牢記以下三條規(guī)則:
- Rust 中每一個(gè)值都被一個(gè)變量“擁有”,該變量稱為值的所有者。
- 一個(gè)值同時(shí)只能有一個(gè)所有者(即“唯一所有權(quán)”)。
- 當(dāng)所有者(變量)離開(kāi)作用域時(shí),該值會(huì)被自動(dòng)丟棄(調(diào)用 drop 函數(shù)釋放內(nèi)存)。
1. 變量作用域:所有權(quán)的“生命周期”
作用域是變量的“有效范圍”——變量從聲明處開(kāi)始生效,離開(kāi)作用域后失效(觸發(fā) drop 釋放內(nèi)存)。這一點(diǎn)與其他語(yǔ)言(如 Java、C++)類(lèi)似。
示例代碼:
#![allow(unused)]
fn main() {
// 作用域1:s尚未聲明,無(wú)效
{
let s = "hello"; // 作用域2:s聲明,開(kāi)始生效
println!("{}", s); // 有效:可使用s
} // 作用域2結(jié)束:s失效,自動(dòng)釋放內(nèi)存
// 作用域1:s已失效,無(wú)法使用
}
四、所有權(quán)的實(shí)踐:以 String 類(lèi)型為例
字符串是理解所有權(quán)的最佳案例——Rust 中有兩種字符串類(lèi)型:
- 字符串字面值(&str):硬編碼到程序中的不可變字符串(如 "hello"),存儲(chǔ)在“常量區(qū)”,無(wú)需堆內(nèi)存。
- String 類(lèi)型:動(dòng)態(tài)可變字符串(如 String::from("hello")),存儲(chǔ)在堆中,需所有權(quán)管理。
下面通過(guò) String 類(lèi)型,講解所有權(quán)的核心交互場(chǎng)景。
1. 場(chǎng)景1:轉(zhuǎn)移所有權(quán)(Move)
當(dāng)變量綁定到“堆上數(shù)據(jù)”(如 String)時(shí),賦值操作會(huì)觸發(fā)所有權(quán)轉(zhuǎn)移,而非數(shù)據(jù)拷貝。
為什么不直接拷貝?
String 由三部分組成(存儲(chǔ)在棧中):
- ptr:指向堆中字符串內(nèi)容的指針;
- len:字符串當(dāng)前長(zhǎng)度(已使用的內(nèi)存);
- capacity:字符串的總?cè)萘浚ǚ峙涞亩褍?nèi)存大?。?。
若賦值時(shí)拷貝整個(gè) String(包括堆中數(shù)據(jù)),會(huì)產(chǎn)生深拷貝,對(duì)性能消耗極大(尤其大字符串)。因此 Rust 采用“所有權(quán)轉(zhuǎn)移”策略:
所有權(quán)轉(zhuǎn)移示例
#![allow(unused)]
fn main() {
let s1 = String::from("hello"); // s1 是 "hello" 的所有者
let s2 = s1; // 所有權(quán)從 s1 轉(zhuǎn)移到 s2
// println!("{}", s1); // 錯(cuò)誤!s1 已失效,無(wú)法使用
println!("{}", s2); // 正確!s2 是當(dāng)前所有者
}
轉(zhuǎn)移后邏輯:
- s1 不再指向堆中數(shù)據(jù),變?yōu)?ldquo;無(wú)效狀態(tài)”;
- 只有 s2 擁有堆中數(shù)據(jù)的所有權(quán);
- 當(dāng) s2 離開(kāi)作用域時(shí),自動(dòng)調(diào)用 drop 釋放堆內(nèi)存,避免“二次釋放”(內(nèi)存安全 bug)。
2. 場(chǎng)景2:克?。–lone):主動(dòng)深拷貝
若確實(shí)需要“完整拷貝堆中數(shù)據(jù)”(深拷貝),可使用 clone 方法。這是 Rust 中唯一主動(dòng)觸發(fā)深拷貝的方式,需顯式調(diào)用(避免無(wú)意識(shí)的性能損耗)。
克隆示例
#![allow(unused)]
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone(); // 深拷貝:棧中 String 結(jié)構(gòu) + 堆中字符串內(nèi)容均拷貝
println!("s1 = {}, s2 = {}", s1, s2); // 正確!s1 仍有效(未轉(zhuǎn)移所有權(quán))
}
注意:clone 性能開(kāi)銷(xiāo)較大,僅在必要時(shí)使用(如初始化程序、低頻操作),避免在“熱點(diǎn)代碼”(高頻執(zhí)行邏輯)中調(diào)用。
3. 場(chǎng)景3:拷貝(Copy):棧上數(shù)據(jù)的自動(dòng)淺拷貝
對(duì)于棧中存儲(chǔ)的基本類(lèi)型(如 i32、bool),賦值操作會(huì)自動(dòng)觸發(fā)“淺拷貝”——拷貝數(shù)據(jù)本身(而非轉(zhuǎn)移所有權(quán)),原變量仍有效。
這是因?yàn)闂V袛?shù)據(jù)大小固定、拷貝速度極快,無(wú)需通過(guò)“所有權(quán)轉(zhuǎn)移”管理。Rust 為這類(lèi)類(lèi)型實(shí)現(xiàn)了 Copy 特征,標(biāo)記其支持“自動(dòng)拷貝”。
拷貝示例
#![allow(unused)]
fn main() {
let x = 5; // 基本類(lèi)型 i32,存儲(chǔ)在棧中
let y = x; // 自動(dòng)拷貝:棧中數(shù)據(jù) 5 復(fù)制給 y,x 仍有效
println!("x = {}, y = {}", x, y); // 正確!x 和 y 均有效
}
支持Copy特征的類(lèi)型
滿足“大小固定、無(wú)需堆內(nèi)存”的類(lèi)型均支持 Copy,常見(jiàn)類(lèi)型包括:
- 所有整數(shù)類(lèi)型(u8、i32、u64 等);
- 布爾類(lèi)型(bool);
- 所有浮點(diǎn)數(shù)類(lèi)型(f32、f64);
- 字符類(lèi)型(char);
- 元組(僅當(dāng)所有元素均支持 Copy 時(shí),如 (i32, bool) 支持,(i32, String) 不支持);
- 不可變引用(&T,如 &str);
注意:可變引用(&mut T)不支持 Copy,避免多個(gè)可變引用修改同一數(shù)據(jù)導(dǎo)致沖突。
五、函數(shù)中的所有權(quán)
函數(shù)傳參和返回值的過(guò)程,同樣遵循所有權(quán)規(guī)則——本質(zhì)與變量賦值一致(轉(zhuǎn)移或拷貝)。
1. 函數(shù)傳參:所有權(quán)的轉(zhuǎn)移/拷貝
- 若傳入堆上類(lèi)型(如 String):所有權(quán)從調(diào)用方轉(zhuǎn)移到函數(shù)參數(shù),調(diào)用方后續(xù)無(wú)法使用該變量;
- 若傳入棧上類(lèi)型(如 i32):自動(dòng)拷貝,調(diào)用方仍可使用原變量。
示例代碼
fn main() {
let s = String::from("hello"); // s 是 String 所有者(堆上數(shù)據(jù))
takes_ownership(s); // 所有權(quán)轉(zhuǎn)移到函數(shù)參數(shù) some_string,s 失效
// println!("{}", s); // 錯(cuò)誤!s 已失效
let x = 5; // x 是 i32(棧上數(shù)據(jù))
makes_copy(x); // 自動(dòng)拷貝,x 仍有效
println!("x = {}", x); // 正確!x 未轉(zhuǎn)移所有權(quán)
}
// 接收 String 類(lèi)型參數(shù):參數(shù)進(jìn)入作用域時(shí)獲得所有權(quán)
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string 離開(kāi)作用域,調(diào)用 drop 釋放堆內(nèi)存
// 接收 i32 類(lèi)型參數(shù):參數(shù)是 Copy 類(lèi)型,拷貝后原變量仍有效
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // some_integer 離開(kāi)作用域,無(wú)堆內(nèi)存需釋放
2. 函數(shù)返回值:所有權(quán)的傳遞
函數(shù)返回值會(huì)將所有權(quán)傳遞給“接收返回值的變量”,本質(zhì)是“轉(zhuǎn)移所有權(quán)”。
示例代碼
fn main() {
// 1. 函數(shù)返回 String,所有權(quán)轉(zhuǎn)移給 s1
let s1 = gives_ownership();
// 2. s2 是 String 所有者
let s2 = String::from("hello");
// 3. s2 所有權(quán)轉(zhuǎn)移到函數(shù),函數(shù)返回后轉(zhuǎn)移給 s3
let s3 = takes_and_gives_back(s2);
// println!("{}", s2); // 錯(cuò)誤!s2 已轉(zhuǎn)移所有權(quán)
}
// 返回 String:將 some_string 的所有權(quán)轉(zhuǎn)移給調(diào)用方
fn gives_ownership() -> String {
let some_string = String::from("hello");
some_string // 隱式返回,所有權(quán)轉(zhuǎn)移
}
// 接收 String 并返回:先獲得 a_string 的所有權(quán),再轉(zhuǎn)移給調(diào)用方
fn takes_and_gives_back(a_string: String) -> String {
a_string // 所有權(quán)轉(zhuǎn)移給調(diào)用方
}
六、總結(jié)
1. 核心要點(diǎn)回顧
- 所有權(quán)是 Rust 解決內(nèi)存安全的核心機(jī)制,編譯期檢查,無(wú)運(yùn)行時(shí)開(kāi)銷(xiāo);
- 三條核心規(guī)則:唯一所有者、所有者失效則值釋放、值僅一個(gè)所有者;
- 堆上類(lèi)型(如 String)賦值觸發(fā)所有權(quán)轉(zhuǎn)移,棧上類(lèi)型(如
i32)賦值觸發(fā)自動(dòng)拷貝; - 函數(shù)傳參/返回值遵循所有權(quán)規(guī)則,本質(zhì)是“轉(zhuǎn)移或拷貝”。
到此這篇關(guān)于Rust 所有權(quán)(Ownership)的使用小結(jié)的文章就介紹到這了,更多相關(guān)Rust 所有權(quán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust生命周期之驗(yàn)證引用有效性與防止懸垂引用方式
本文介紹了Rust中生命周期注解的應(yīng)用,包括防止懸垂引用、在函數(shù)中使用泛型生命周期、生命周期省略規(guī)則、在結(jié)構(gòu)體中使用生命周期、靜態(tài)生命周期以及如何將生命周期與泛型和特質(zhì)約束結(jié)合,通過(guò)這些機(jī)制,Rust在編譯時(shí)就能捕獲內(nèi)存安全問(wèn)題2025-02-02
關(guān)于Rust命令行參數(shù)解析以minigrep為例
本文介紹了如何使用Rust的std::env::args函數(shù)來(lái)解析命令行參數(shù),并展示了如何將這些參數(shù)存儲(chǔ)在變量中,隨后,提到了處理文件和搜索邏輯的步驟,包括讀取文件內(nèi)容、搜索匹配項(xiàng)和輸出搜索結(jié)果,最后,總結(jié)了Rust標(biāo)準(zhǔn)庫(kù)在命令行參數(shù)處理中的便捷性和社區(qū)資源的支持2025-02-02
Rust中的Drop特性之解讀自動(dòng)化資源清理的魔法
Rust通過(guò)Drop特性實(shí)現(xiàn)了自動(dòng)清理機(jī)制,確保資源在對(duì)象超出作用域時(shí)自動(dòng)釋放,避免了手動(dòng)管理資源時(shí)可能出現(xiàn)的內(nèi)存泄漏或雙重釋放問(wèn)題,智能指針如Box、Rc和RefCell都依賴于Drop來(lái)管理資源,提供了靈活且安全的資源管理方案2025-02-02
Rust?語(yǔ)言中符號(hào)?::?的使用場(chǎng)景解析
Rust?是一種強(qiáng)調(diào)安全性和速度的系統(tǒng)編程語(yǔ)言,這篇文章主要介紹了Rust?語(yǔ)言中符號(hào)?::?的使用場(chǎng)景,本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2024-03-03

