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

