文章目錄
- Rust所有權
- 所有權規則
- 作用域
- 內存和分配
- 移動與克隆
- 棧空間
- 堆空間
- 關于函數的所有權機制
- 作為參數
- 作為返回值
- 引用與租借
- 垂懸引用
Rust所有權
C/C++中我們對于堆內存通常需要自己手動管理,手動申請和釋放,即便有了智能指針,對于效率的影響和安全性問題也沒有完全解決
Rust為了高效的使用和管理內存,以及對安全性的考量,提出了所有權的概念以及一系列規則
所有權規則
所有權有三條核心規則
- Rust中的每個值都有一個隱含的變量,稱為所有者
- 一個值 同一時刻只能有一個所有者
- 當所有者離開作用域時,值會被丟棄(調用drop函數釋放資源)
fn main() {let s1 = String::from("Hello"); // s1 是 "Hello" 的所有者let s2 = s1; // s1 的所有權被轉移給 s2,s1 失效// println!("{}", s1); // ? 編譯錯誤:s1 已經失去所有權println!("{}", s2); // ? s2 仍然是 "Hello" 的所有者
} // s2 離開作用域,"Hello" 被釋放
作用域
這里的作用域和C/C++的作用域基本類似
{// 在聲明以前,變量 s 無效let s = "runoob";// 這里是變量 s 的可用范圍
}
// 變量范圍已經結束,變量 s 無效
內存和分配
Rust是靜態語言,這意味著我們無法像C++那樣運行時擴容,例如vector會在滿時進行擴容
在C++中=
是賦值的意思,理解就是將一個變量的值,賦值給一個新的變量,這是一種拷貝語義(Copy)
但是在Rust中,變量和數據交互的方式主要是移動(Move)和克隆(Clone)
移動與克隆
如果你學過C++11,那你一定知道移動語義,例如移動拷貝或者移動賦值
這實際上是一種所有權的轉移,主要是避免頻繁申請和釋放堆空間
但是有一些情況,我們并不希望只是所有權的轉移,而是真的創建一個副本進行操作,這就需要使用clone()
方法
fn main() {let s1 = String::from("hello");let s2 = s1.clone(); // 深拷貝println!("s1: {}, s2: {}", s1, s2); // ? 仍然可以使用 s1
}
棧空間
在棧空間內,Rust變量“移動”的方式其實就是復制,因為棧空間內基本上都是基本數據類型的,通常占用空間和復制時間不會很久,就會是直接復制,這時候兩個變量都是可以使用的
let x = 1;let y = x;println!("{x}, {y}");
"基本數據"類型有這些:
- 所有整數類型,例如 i32 、 u32 、 i64 等。
- 布爾類型 bool,值為 true 或 false 。
- 所有浮點類型,f32 和 f64。
- 字符類型 char。
- 僅包含以上類型數據的元組(Tuples)。
堆空間
String對象的hello
存儲的位置可就不是棧空間了而是堆空間
例如
let s1 = String::from("hello");
let s2 = s1;
當執行到第二步時,s1就會把自己對hello字符串對所有權移交給s2,此時s1,就會失效
因此在移動之后,繼續使用s1會報錯
關于函數的所有權機制
作為參數
當一個變量作為參數傳遞給函數時,所有權應該怎么處理
fn main() {let s = String::from("hello");// s 被聲明有效takes_ownership(s);// s 的值被當作參數傳入函數,相當于s的所有權移交給函數了// 所以可以當作 s 已經被移動,從這里開始已經無效let x = 5;// x 被聲明有效makes_copy(x);// x 的值被當作參數傳入函數// 但 x 是基本類型,依然有效// 在這里依然可以使用 x 卻不能使用 s} // 函數結束, x 無效, 然后是 s. 但 s 已被移動, 所以不用被釋放fn takes_ownership(some_string: String) { // 一個 String 參數 some_string 傳入,有效println!("{}", some_string);
} // 函數結束, 參數 some_string 在這里釋放fn makes_copy(some_integer: i32) { // 一個 i32 參數 some_integer 傳入,有效println!("{}", some_integer);
} // 函數結束, 參數 some_integer 是基本類型, 無需釋放
作為返回值
fn main() {let s1 = gives_ownership();// gives_ownership 移動它的返回值到 s1let s2 = String::from("hello");// s2 被聲明有效let s3 = takes_and_gives_back(s2);// s2 被當作參數移動, s3 獲得返回值所有權
} // s3 無效被釋放, s2 被移動, s1 無效被釋放.fn gives_ownership() -> String {let some_string = String::from("hello");// some_string 被聲明有效return some_string;// some_string 被當作返回值移動出函數
}fn takes_and_gives_back(a_string: String) -> String { // a_string 被聲明有效a_string // a_string 被當作返回值移出函數
}
引用與租借
這里的引用和C++中的引用是類似的,如果不了解C++的引用,也可以認為是一種指針
例如
fn main() {let s1 = String::from("hello");let s2 = &s1;println!("s1 is {}, s2 is {}", s1, s2);
}
按照原先的理解,s1內部會存一個指向"hello"的指針,s2內部其實也是一個指向"hello"的指針,但是s2是后來的,我們就認為s2是s1的一個引用,也就是別名
- 引用可以認為是單獨的一種類型
- 引用不會獲得值的所有權
- 引用只能租借(Borrow)值的所有權
- 當一個值被移動時,原先的引用會失效,必須重新租借所有權
fn main() {let s1 = String::from("hello");let mut s2 = &s1;let s3 = s1;s2 = &s3; // 重新從 s3 租借所有權println!("{}", s2);
}
一般的租借引用是不允許修改數據內容的,除非原先的數據是mut的,引用時也是mut引用,才允許修改
let mut s1 = String::from("hello");
let s2 = &mut s1;
此時s2允許修改s1的內容
可變引用和不可變引用除了權限不同以外,可變引用不允許多重引用(多個變量引用同一個值),但是不可變引用是允許的
這樣做主要是為了避免同時有多個使用者可以進行寫操作
垂懸引用
這個其實就是對應著空指針的概念(已經釋放資源的指針也算),例如
fn main() {let reference_to_nothing = dangle();
}fn dangle() -> &String {let s = String::from("hello");&s
}
s是在函數里申請的,函數結束自動釋放,但是返回了s的引用,被main函數接收到了,這里就相當于得到了一個空指針