一、棧內(nèi)存與堆內(nèi)存
棧內(nèi)存用于存儲固定大小的數(shù)據(jù),速度快。
堆內(nèi)存用于存儲動態(tài)大小的數(shù)據(jù),靈活性高。
Rust 中的內(nèi)存管理主要涉及棧內(nèi)存和堆內(nèi)存,這兩種內(nèi)存的使用方式不同,適用于不同的場景。
- 棧內(nèi)存:存儲大小固定的數(shù)據(jù),分配和釋放速度非??臁?nèi)存遵循“后進先出”的原則,就像疊盤子一樣,放盤子和取盤子都只能從頂部進行。例子:基礎(chǔ)類型(如 i32、char、f64)存儲在棧內(nèi)存中。
- 堆內(nèi)存:存儲大小動態(tài)變化的數(shù)據(jù),分配和釋放速度較慢,但更靈活。需要先找到一塊足夠大的空間,然后返回一個指向該空間的指針。例子:動態(tài)字符串(String 類型)存儲在堆內(nèi)存中。
二、動態(tài)字符串與內(nèi)存管理
Rust 中的 String 類型是一個動態(tài)字符串,它允許在運行時動態(tài)管理堆內(nèi)存中的數(shù)據(jù),比如分配、增長和修改字符串內(nèi)容。
// 創(chuàng)建動態(tài)字符串
let s1 = String::from("hello"); // 在堆內(nèi)存中分配空間存儲 "hello",此時,s1 在棧內(nèi)存中存儲了堆內(nèi)存的指針、字符串長度和容量信息。
// 克隆(深拷貝)
let s3 = s1.clone(); // 在堆內(nèi)存中復(fù)制一份數(shù)據(jù),s3 指向新的內(nèi)存空間
println!("s1 = {}, s3 = {}", s1, s3); // s1 和 s3 是兩個獨立的數(shù)據(jù)
注意:
- 如果需要對字符串進行修改而不影響原字符串,可以使用
clone方法進行深拷貝: - 克隆操作會復(fù)制堆內(nèi)存中的數(shù)據(jù),因此對性能有一定影響,尤其是處理大數(shù)據(jù)或頻繁操作時。
三、所有權(quán)機制
Rust 的所有權(quán)機制是其內(nèi)存安全的核心,確保程序在運行時不發(fā)生數(shù)據(jù)競爭、懸垂指針等內(nèi)存安全問題。
所有權(quán)三原則
- 每個值都有一個所有者(變量)
- 一個值同時只能有一個所有者
- 當所有者離開作用域時,值會被自動釋放(drop)。
所有權(quán)轉(zhuǎn)移(Move)
當值被賦值給另一個變量時,所有權(quán)會轉(zhuǎn)移,原變量將無法再使用該值。
在其他語言(如 Java)中,淺拷貝只復(fù)制棧內(nèi)存中的指針,多個變量共享同一塊堆內(nèi)存數(shù)據(jù)。這種方式效率高,但容易引發(fā)數(shù)據(jù)競爭問題。
Rust 通過所有權(quán)機制避免了淺拷貝帶來的問題:
- 所有權(quán)轉(zhuǎn)移時,只進行“淺拷貝”(復(fù)制棧內(nèi)存中的指針信息),但原變量會失去所有權(quán)。
- 新變量接管所有權(quán)后,原變量失效,確保每個值只有一個清晰定義的所有者。
let s1 = String::from("hello");
let s2 = s1; // 所有權(quán)從 s1 轉(zhuǎn)移到 s2
// println!("{}", s1); // 錯誤:s1 不再擁有數(shù)據(jù)
println!("{}", s2); // 正確:s2 擁有數(shù)據(jù)
函數(shù)形式的轉(zhuǎn)移:(注意所有權(quán)的轉(zhuǎn)移和借用的不同)
fn main() {
/* 所有權(quán)轉(zhuǎn)移 */
let my_book = String::from("Rust Programming");
// my_book 的所有權(quán)被移交至 give_book 函數(shù)
give_book(my_book);
// 此后 my_book 便無法再被使用
// println!("my_book: {}", my_book); // 編譯出錯
/* 所有權(quán)借用 */
let my_book = String::from("Rust Programming");
// my_book 的所有權(quán)被移交至 give_book 函數(shù)
borrow_book(&my_book);
// 此后 my_book 便無法再被使用
println!("my_book: {}", my_book); // 編譯出錯
}
// 所有權(quán)轉(zhuǎn)移
fn give_book(book: String) {
println!("Given book: {}", book);
// book 在這里被 dropped 釋放
}
// 所有權(quán)借用
fn borrow_book(book: &String) {
println!("borrow book: {}", book);
// book 在這里被 dropped 釋放
}
作用域與內(nèi)存釋放
變量的有效范圍從聲明的地方開始,直到當前作用域結(jié)束。當變量離開作用域時,其占用的內(nèi)存會被自動釋放。
{
let s1 = String::from("hello"); // s1 進入作用域
} // s1 離開作用域,內(nèi)存被釋放
四、借用與解引用
基礎(chǔ)
fn main() {
// 定義變量
let my_book = String::from("Rust Programming");
// 借用,語法:&變量
let borrowed_book = &my_book;
// 解引用(讀取變量內(nèi)容),語法:*引用
println!("my_book:{}, book_content:{}", my_book, *borrowed_book)
}
不可變引用與可變引用
借用分為兩種:不可變引用和可變引用。不可變引用就像你借給朋友的書,他只能讀,不能寫:
fn main() {
// 定義變量
let my_book = String::from("Rust Programming");
// 借用,語法:&變量 - 不可變借用;&mut 變量 - 可變借用
let borrowed_book = &my_book;
// 本身還是保留了訪問權(quán)限
println!("my_book:{},", my_book);
// 解引用(讀取變量內(nèi)容),語法:*引用
println!("book_content:{}", *borrowed_book);
// 不可變借用 - 只讀
println!("read_book:{}", read_book(borrowed_book));
// 不可變借用不可以寫
// write_in_book(borrowed_book); // 編譯出錯,不可變不可以寫
// &mut 變量 - 可變借用
let mut mut_my_book = String::from("Rust Programming");
let mut_borrowed_book: &mut String = &mut mut_my_book;
println!("read_book2:{}", read_book(mut_borrowed_book));
write_in_book(mut_borrowed_book);
println!("read_book3:{}", read_book(mut_borrowed_book));
}
// 讀
fn read_book(book: &String) -> usize {
book.len()
}
// 寫
fn write_in_book(book: &mut String) {
book.push_str(" - Notes by friend");
}
懸垂引用
懸垂引用(Dangling Reference)是指一個引用指向了已經(jīng)被釋放的內(nèi)存
Rust 編譯器會在編譯期自行檢查該問題
fn main() {
let tenant;
{
let house = String::from("Apartment 101");
tenant = &house; // house 的作用域結(jié)束了,但 tenant 還在引用它
}
println!("Tenant lives in: {}", tenant); // 編譯失?。篽ouse 已經(jīng)被釋放
}
分級釋放
釋放結(jié)構(gòu)體時,Rust 會先釋放父結(jié)構(gòu)體,再釋放子結(jié)構(gòu)體:
struct Chapter {
content: String,
}
struct Book {
title: String,
chapter: Chapter,
}
fn main() {
let my_book = Book {
title: String::from("Rust Programming"),
chapter: Chapter {
content: String::from("Chapter 1: Ownership"),
},
};
println!("Book: {}, Chapter: {}", my_book.title, my_book.chapter.content);
// my_book 首先被 dropped 釋放
// 緊接著是 my_book.chapter
}