網站首頁 編程語言 正文
what's ownership?
常見的高級語言都有自己的 Garbage Collection(GC)機制來管理程序運行的內存,例如 Java、Go 等。而 Rust 引入了一種全新的內存管理機制,就是 ownership(所有權)。它在編譯時就能夠保證內存安全,而不需要 GC 來進行運行時的內存回收。
在 Rust 中 ownership 有以下幾個規則:
- 每個值都有一個 woner(所有者)
- 在同一時間,每個值只能有一個 owner
- 當 owner 離開作用域,這個值就會被丟棄
Scope (作用域)
通過作用域來劃分 owner 的生命周期,作用域是一段代碼的范圍,例如函數體、代碼塊、if 語句等。當 owner 離開作用域,這個值就會被丟棄。
example:
fn main() { let s = String::from("hello"); // 變量 s 進入作用域,分配內存 // s 在這里可用 } // 函數體結束,變量 s 離開作用域,s 被丟棄,內存被回收
ownership transfer(所有權轉移)
和大多數語言一樣,Rust 在棧上分配基本類型的值,例如整型、浮點型、布爾型等。而在堆上分配復雜類型的值,例如 String、Vec 等。所以,這里就引入了兩個概念,move
和 clone
。
move
move
操作會將變量的所有權轉移給另一個變量,這樣原來的變量就不能再使用了。這里需要注意的是,move
操作只會發生在棧上的值,因為在堆上的值是不可復制的,所以只能通過 clone
操作來復制。
example:
fn main(){ let s1 = String::from("hello"); let s2 = s1; print!("s1 = {}, s2 = {}", s1, s2); }
在上面的代碼例子中,如果你執行就會在編譯時報錯:
--> src/main.rs:11:32 | 9 | let s1 = String::from("hello"); | -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait 10 | let s2 = s1; | -- value moved here 11 | print!("s1 = {}, s2 = {}", s1, s2); | ^^ value borrowed here after move
編譯器提示我們,s1
在賦值給 s2
時發生了 move
的操作,它把字符串 hello
的所有權移交給了 s2
,此時 s1
的作用域到這里就結束了,所以后面再使用 s1
就會報錯。
clone
clone
操作會將變量的值復制一份,這樣原來的變量和新的變量就都可以使用了。
這里需要注意的是,clone
操作只會發生在堆上的值,因為在棧上的值是可復制的,所以只能通過 move
操作來轉移所有權。
example:
fn main(){ let s1 = String::from("hello"); let s2 = s1.clone(); print!("s1 = {}, s2 = {}", s1, s2); }
我們對 s1
進行 clone
操作,這樣 s1
和 s2
都可以使用了,而且 s1
的所有權也沒有被轉移,所以后面還可以繼續使用 s1
。
copy
如果一個類型實現了 copy
這個 trait,使用它的變量不會移動,而是被簡單地復制,使它們在分配給另一個變量后仍然有效。
example:
fn main() { let x = 5; let y = x; print!("x = {}, y = {}", x, y); }
當 x
賦值給 y
后,x
和 y
都可以使用,而且 x
的所有權也沒有被轉移,所以后面還可以繼續使用 x
。這是因為 i32
這個類型實現了 copy
這個 trait,所以 x
的值被復制了一份,所以 x
和 y
都可以使用。
以下這些數據類型實現了 copy
這個 trait:
- 所有的整數類型,例如:
u32
、i32
。 - 布爾類型,
bool
,有true
和false
兩個值。 - 所有的浮點數類型,例如:
f64
、f32
。 - 字符類型,
char
。 - 元組,當且僅當它們的元素類型都實現了
copy
這個 trait。例如,(i32, i32)
實現了copy
,但是(i32, String)
就沒有實現。
References and Borrowing(引用和借用)
我們將創建引用的動作稱為借用。就像在現實生活中一樣,如果一個人擁有某樣東西,你可以向他們借用。完成后,您必須將其歸還。你不擁有它。 引用有以下幾個規則:
- 在任何給定時間,你可以擁有任意數量的引用,但是只能擁有一個可變引用。
- 引用必須總是有效的。
example1:
fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len); } fn calculate_length(s: &String) -> usize { s.len() } // s 作用域失效,但是由于 s 是一個引用,沒有所有權,所以不會發生任何事情
上面代碼中,我們使用符號 &
來創造一個變量的引用。這里我們使用 &s1
來把這個引用指向 s1
。函數 calculate_length
的參數 s
的類型是 &String
,這意味著它是一個指向 String
類型的引用,然后在函數體內獲取 s
的長度并返回給調用者。
example2:
fn main(){ // 同一時間可以擁有多個不可變引用 let s1 = String::from("hello"); let s2 = &s1; let s3 = &s1; println!("s1 = {}, s2 = {}, s3 = {}", s1, s2, s3); }
Mutable References(可變引用)
可變引用指的是可以改變引用值的引用。在同一作用域中,同一時間只能有一個可變引用。
example:
fn main(){ let mut s = String::from("hello"); change(&mut s); println!("{}", s); } fn change(some_string: &mut String) { some_string.push_str(", world"); }
上面代碼中,我們用 mut
先創建了一個可變變量 s
,然后使用 &mut s
創建了一個指向 s
的可變引用。函數 change
的入參也是一個指向 String
類型的可變引用,這樣我們就可以在函數 change
中改變 s
的值了。
example2:
fn main() { let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; // 在這里。編譯器會報錯,因為在同一作用域中,同一時間只能有一個可變引用。 println!("{}, {}", r1, r2); }
--> src/main.rs:41:14 | 40 | let r1 = &mut s; | ------ first mutable borrow occurs here 41 | let r2 = &mut s; | ^^^^^^ second mutable borrow occurs here 42 | 43 | println!("{}, {}", r1, r2); | -- first borrow later used here
Dangling References(懸垂引用)
懸垂引用是指引用一個不存在的值。在 Rust 中,這是不可能的,因為編譯器會在編譯時就檢查這種情況。下面是一個例子:
fn main() { let reference_to_nothing = dangle(); // 獲得一個指向不存在值的引用 } fn dangle() -> &String { let s = String::from("hello"); // s 進入作用域 &s // 返回 s 的引用 } // s 作用域結束,s 被丟棄,內存被釋放
--> src/main.rs:51:16 | 51 | fn dangle() -> &String { | ^ expected named lifetime parameter
因為變量 s
的作用域只在 dangle
函數內,當 dangle
函數返回 s
的引用時,s
已經被釋放了,所以這個引用就是懸垂引用了。 解決這個的方法是返回一個 String
而不是一個引用,這樣 s
就不會被釋放,而是把 s
的所有權轉移給了調用者,也就不存在懸垂引用了。
fn dangle() -> String { let s = String::from("hello"); s }
原文鏈接:https://juejin.cn/post/7184247646293819451
相關推薦
- 2022-04-15 python中yield函數的用法詳解_python
- 2023-02-17 golang基礎之waitgroup用法以及使用要點_Golang
- 2022-07-13 Golang實現常見排序算法的示例代碼_Golang
- 2022-12-27 python中getopt()函數用法詳解_python
- 2022-05-22 python中的sys模塊和os模塊_python
- 2022-11-02 golang服務報錯:?write:?broken?pipe的解決方案_Golang
- 2022-05-19 ASP.NET?Core框架探索之Authentication的權限認證過程解析_實用技巧
- 2022-06-09 忘記Grafana不要緊2種Grafana重置admin密碼方法詳細步驟_服務器其它
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支