蛋疼,在工作電腦上寫了一半編程概念的筆記忘 push 了。用著 Obsidian 的 git 插件,又不喜歡自動備份,每次寫完得記著手動同步.
一刷聖經到後面就意識到所有權是一系列看起來就複雜的設計的源頭,當時都打算刷一半返回來再看幾遍所有權了,當然最終還是選擇先整體過完一遍。之前試著學過幾次 Rust 基本都止步於所有權。現在二刷看起來就有點親切感了.
所有權是什麼#
對於寫慣了高級語言 JS 的前端來說,除了面試前,平常基本不需要考慮內存管理,因為 JS 有 GC (Garbage Collection) - 面試常說的垃圾回收,隔一陣自動幫你掃一掃用不上的內存。傳統低級語言如 C 語言,寫的時候除了考慮邏輯實現,還得自己分配回收內存。而 Rust, 在玩一種很新的東西來管理內存 ➡ 所有權.
為什麼需要所有權#
低級語言總得考慮性能和產物體積,GC 方便是方便,得跟程序一起跑,儘管引擎做了優化,但繞不過運行時的掃描和分析,算是犧牲性能提供方便1. 讓開發者直接管理內存,容易因人出纰漏導致內存泄漏或空指針崩了. Rust 主打一手內存安全,靠的就是所有權。我理解所有權是一個抽象的約束,開發按特定模式寫代碼,方便編譯器靜態分析最終插入內存管理的內容。感覺管理內存了,但沒經你手,沒完全管。此外,所有權模型幫助開發更好的限制行為和理解代碼,避免常見的錯誤,減少不必要的內存分配和複製提高性能.
堆和栈在這就不討論了,這段幫我回憶了不少遺忘的知識.
怎麼用#
照著編譯報錯改就完了🤪
復述一下規則:一個值 (字符,數字,對象..) 在一個時刻只能存在一個所有者 (變量), 最終所有者出了作用域值就被丟棄.
理解#
Rust 為了避免內存的二次釋放引起的漏洞,對堆上的內存只允許一個指針指向,不允許類似 JS 淺拷貝的行為,對值的傳遞默認使用移動. 另外還可以使用消耗資源的 clone
實現深拷貝, 對於存在栈上的簡單數據可以通過實現 Copy
trait 做到快速複製無需考慮所有權.
容量#
講 String 的內存分配時提到了其存儲在栈上的內容,指針 ptr 不用說,len 和 capacity 分別代表長度和容量,在 Vec 文檔中可以找到二者的區別:
Vector 的容量是分配給未來添加到 Vector 中的任何元素的空間量。不能與其 length 相混淆,後者指定了 Vector 中實際元素的數量。如果一個 Vector 的長度超過了它的容量,它的容量將自動增加,但它的元素將不得不被重新分配.
例如,一個容量為 10、長度為 0 的 Vector 是一個空的 Vector, 其空間可再容納 10 個元素。把 10 個或更少的元素 push 進去不會改變其容量或導致重新分配。但是,如果 Vector 的長度增加到 11, 它將不得不重新分配,而且可能很慢。出於這個原因,建議儘可能使用Vec::with_capacity
來指定 Vector 的預期大小.
引用和借用#
因為所有權機制導致想要復用一個值變得複雜,尤其當所有權傳入函數時,想要復用就需要把所有權從函數裡返回。可以使用 &
關鍵字創建值的引用來避免重複的傳值 (所有權) 和返回,引用是一個指向值的指針,值本身的所有權不會被傳遞。創建引用的行為叫借用.
持有#
我編的概念這好像是生命週期的概念啊
引用也分可變和不可變,還有額外的限制是如果持有了一個可變引用,就不能持有其他可變或不可變引用。持有就是從創建到最後一次使用中間的階段,不管創建的先後,可變引用和其他引用持有的階段都不能交錯 (有一點點抽象). 這個限制是為了避免數據競爭,如果創建以後同一層級沒再用就不存在數據競爭的問題,同理持有多個不可變引用也是允許的. Rust 對可變性的限制真不少.
let str = String::from("x");
let s1 = &mut str;
// 在中間這段一直持有 s1, 不能再有其他引用
println!("{s1}")
引用可能導致懸垂指針, 即引用還在,引用指向的內存已經不在或者不是引用創建時指向的那位. Rust 通過 [[生命週期]] 避免這種情況。感覺像在不停的打補丁,借用補所有權帶來的不便,生命週期補借用帶來的坑.
取引用和取地址#
Lv777: Rust 中
&v[0]
應該理解為是 v 的引用的索引 0 元素還是 v 的索引 0 元素的引用
Raycast AI: 在 Rust 中,
&v[0]
表示對向量v
的索引為 0 的元素的引用。具體來說,v
是一個向量,而&v[0]
是一個指向該向量第一個元素的不可變引用。這個表達式的類型是&T
,其中T
是向量中元素的類型。因此,你可以通過*(&v[0])
或v[0]
來訪問這個元素的值。
Lv777:
&
關鍵字的優先級
Raycast AI: 在 Rust 中,
&
符號有兩種不同的用法,分別是取引用和取地址。它們的優先級是不同的。
取引用的優先級比取地址的優先級更高。這意味著,如果你寫了類似於&x.foo()
的代碼,它會被解析為&(x.foo())
,而不是(&x).foo()
。
如果你想要取一個變量的地址並將其轉換為指針類型,你可以使用as
關鍵字,它的優先級比&
符號低。例如,&x as *const T
將x
的地址轉換為一個指向類型T
的常量指針。
Lv777: 什麼是取引用和取地址
Raycast AI: 在 Rust 中,
&
符號有兩種不同的用法,分別是取引用和取地址。
取引用是指使用&
符號來創建一個指向某個值的不可變引用。不可變引用允許你讀取值,但是不能修改它。例如,如果x
是一個整數,那麼&x
將創建一個指向x
的不可變引用。你可以使用*
符號來解引用引用並訪問值,例如*(&x)
或者x
。
取地址是指使用&
符號來獲取某個變量的內存地址。這通常用於將變量的地址傳遞給函數或者其他代碼,以便它們可以訪問變量的值。例如,如果x
是一個整數,那麼&x
將返回x
的內存地址。你可以將這個地址轉換為指針類型,例如&x as *const i32
或者&x as *mut i32
,並將其傳遞給其他代碼。
需要注意的是,取引用和取地址的操作是不同的,它們返回的類型也不同。取引用返回一個指向值的引用,類型為&T
,其中T
是值的類型。取地址返回一個指向變量的指針,類型為*const T
或者*mut T
,其中T
是變量的類型。
Slice#
這概念頭一次刷的時候沒注意,後面忘了哪章突然蹦出來給我整不會了.
Slice 是值的一部分引用,聲明用 Range 指定起始和結束索引,起始索引是要截取的第一個位置,結束索引是截取的最後一個位置的後一個值.
let str = String::from("123456")
let sli = str[1..3]; // 23
let all = str[..]; // 起始索引為 0, 或結束索引為 len 則可以省略
對結束索引有疑惑,看看 why-is-slice-end-index-logic-as-it-is.
在 [[# 容量]] 裡說過 String 在栈上存儲的內容,指針指向堆上數據的起始位置,Slice 也類似,其指針指向創建 Slice 時聲明的起始索引位置,沒有 capacity, len 通過聲明時的結束索引減去起始索引計算得出. Slice 是值的特殊不可變引用,也受 [[# 持有]] 的限制.
文檔這裡用 String 的使用場景引出 Slice 存在的便利性,確實不好描述。我一開始理解困難實際是把 JS 裡對字符串的認知代入了,Rust 裡的 String 不是標量,不能用簡單的 Copy 思想去處理.