rust高级矿场_高级 Rust 所有权管理
暨 Repository 簡介
引子Rust 在處理數據時表現第一好情況的是處理樹狀數據,第二好的情況是數據能夠看作有向無環圖,但是其中執行數據變化的修改脈絡路徑仍然能看作是樹形,同樣好的也還有粗粒度執行的可以看作是多讀單寫鎖的協議下的數據處理,諸如此類。在這個舒適帶之外,Rust工作的就沒有那么好。雖然還能用,但是處理變得棘手,很多為了安全性和友好性準備的語言特征都不再能夠協助你,使用體驗變得不方便起來。這本書(指《Learn Rust With Entirely Too Many Linked Lists》)的目標之一就是探索(在Rust開發中)如何與雙向鏈表這樣的東西打交道。 雙向鏈表是一個 Rust 在當前的設計中,讓你不能寫,或者至少不能輕易的不小心的寫出來的結構。雙向鏈表不只是具有不明確的所有權,它實際上具有的是循環的所有權,顯然離 Rust 的舒適區域相去甚遠。顯然,最直接、也是在智力上最誠實的對這本書的回應就是:它深入地論斷反對了在Rust中使用循環依賴數據。原則就是開發中“不要讓數據循環依賴”,看看你能做到什么地步。這也是 Rust 設計的思路。我不認為我寫的 Rust 程序中有很多的循環依賴數據,少數用到的地方都是使用封裝好的庫,或者是不透明的引用句柄(而不是 Rust 所有權系統跟蹤著的原生引用)。
我覺得這段話總結的蠻好的,除了“不要讓數據循環依賴”略顯武斷。在實際的業務開發中,在某些特定的場景之下,“不要讓數據循環依賴”總是要讓路于“讓數據循環依賴變得可管理”。當然這段話的末尾實際上已經提到了解決方案——使用封裝好的庫(讓別人使用某種機制來替你管理并封裝起來),而別人實際實現的方法就是使用不透明的引用句柄。
最最常見的不透明的引用句柄其實就是原生指針。原生指針尺寸小,速度快。缺點也是顯而易見的,它對bug幾乎完全沒有容災性。遇到數據的不一致,甚至有時候只是業務邏輯上的邏輯錯誤導致的,就很可能要與SIGSEGV相伴了。如果是部署到用戶手上的系統,喏,一個嶄新的CVE可能就誕生了。
另外一種很常見的不透明引用句柄就是索引。索引尺寸小,速度也很好,具有最基本的容災性——至少很容易就能保證不會訪問到不存在的值。它最大的缺點是會丟失類型信息。的確在很多情況下,會想要擦除類型信息,但是在邏輯上想要找回類型信息的時候,單純的索引顯然會捉襟見肘。另外就是需要小心處理值刪除引起其他值的索引偏移。最簡單的辦法就是讓索引無法偏移,當然這也并不是全無代價的。
Repository
repository的其他功能,希望對大家有借鑒意義。
repository目前的最大的限制是要求其中所有的數據都符合core::any::Any這個特質,所以無法使用帶有非'static引用類型的數據。這并不是什么大問題,如果真的需要,你可以將非'static類型的值留在Repo之外,使用某種方法建立對應關系,然后封裝一層即可使用。另外就是我目前沒有實現多線程版本。我自己是有用這個庫試驗性的寫了一些東西,感覺至少對于單線程環境下的一般使用還是很方便的。
用 Repo 定義雙向鏈表和節點
先上代碼:
userepository::{Repo,EntityPtr,ErrorasRepoError};usecore::any::Any;struct LinkedList{repo: Repo,ends: Option>,EntityPtr>)>,len: usize,}struct Node{value: T,prev: Option>>,next: Option>>,}
這里用到了 repository 里的 Repo 這個類型,它是一塊存儲空間,你可以把它想像成類似 Vec 一樣的東西,但是它可以放多種類型的數據,所以沒有泛型約束。
在這里我們會用它來存儲雙向鏈表的節點,因為節點的值的類型是用戶指定的 T ,所以用泛型指定。我們打算把Node放進Repo,這要求它符合Any特質約束,所以T也需要符合Any特質約束,寫成T: Any。
將類型為 Node 的數據放入 Repo 會得到一個EntityPtr> 這是一個滿足Copy的大小為usize兩倍的不透明的引用句柄。你可以隨意存儲、序列化、傳輸。它的類型上記錄了實際內容值的類型。我們在Node內,就是用它套上Option用來存儲前一個節點和后一個節點的。
那么我想要訪問實際的Node怎么辦呢?Rust 分為共享借用和獨占借用。如果ptr是一個EntityPtr>,那么獲取共享借用和獨占借用分別是:
// 獲取共享借用letnode_ref=ptr.get_ref(&repo)?;// 獲取獨占借用letnode_mut=ptr.get_mut(&mutrepo)?;
因為EntityPtr是完全獨立于Repo存在的,所以這一步有報錯的可能:比如對應的值可能已經刪掉了,之類的。這里用了問號做錯誤處理。
接下來就是實現雙向鏈表的各種方法了。其實都比較簡單,在這里不做贅述。
Repo 里的類型擦除
有時候我們需要把一堆不同類型的指針存在一起。這個時候EntityPtr會因為上面帶了類型而做不到這一點。EntityPtr 上提供了entity_id()方法,獲取到一個EntityId。它是一個只有一個usize大小的,不帶類型標識的索引編號。從 EntityId 重新獲取EntityPtr則是通過cast_ptr方法。
// 獲取 ptr 對應的idletnode_id=ptr.entity_id(&repo)?;// 獲取 id 對應的ptrletnode_ptr=node_id.cast_ptr::>(&repo);assert_eq!(node_ptr,Some(ptr));
Repo 里的預分配機制
有時候我們在構建數據結構時,它們存在循環引用,然而因為構建是逐個進行的,那么就會存在前一個值需要引用后一個值,但是后一個值還沒有創建的情況。 repository 提供了預分配機制,允許你先為一個值構建EntityPtr,然后在稍后再加入實際對應的值數據。
舉個例子:假如有個數據結構叫“文檔”,存儲“根節點”,而在節點上又需要存儲對應的文檔的引用。代碼如下:
struct Doc{root_node: EntityPtr}struct Node{doc: EntityPtr}repo.transaction_preallocate(|tx|-> EntityPtr{letdoc_ptr=tx.preallocate::();letnode=Node{doc: doc_ptr};letnode_ptr=tx.repo_mut().insert(node);letdoc=Doc{root_node: node_ptr};// 只是演示,所以省去了錯誤處理,直接unwrap()。tx.init_preallocation(doc_ptr,doc).unwrap();doc_ptr});
好啦,這次就簡單介紹到這,歡迎討論。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的rust高级矿场_高级 Rust 所有权管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 机器人滚边有波浪_汽车开启件机器人滚边缺
- 下一篇: 注册不上zookeeper无报错_Zoo