在開發Rust程式的時候,會使用到各式各樣別人開發的套件。這些套件雖然很方便,可以替我們節省不少開發時間,但是套件提供的API,卻不一定能夠完全滿足我們的需求。「Writer」的擁有權被取走,就是一個常見的例子。



有些套件定義的API,會需要輸入有實作「Write」特性的實體,來輸出處理結果。以Rust標準函式庫提供的「File」結構體來說,它本身就有實作「Write」特性,可以一段一段地將資料寫進檔案中。如果套件需要輸入有實作「Write」特性的實體,才能輸出處理結果的話,那就意味著將「File」結構實體交給這個套件處理後就拿不回來了。之後如果還要繼續使用這個「File」結構實體來讀寫檔案,若是套件沒有再提供能夠操作「File」結構實體的API,就只能再重新產生一個新的「File」結構實體了。

再舉一個例子,Rust標準函式庫提供的「Vec」結構體,它本身也有實作「Write」特性。有些套件的設計理念是要把處理結果寫進檔案系統中,保存為一個完整的檔案,因此它們提供的API多半是要直接輸入有實作「Write」特性的實體來給套件使用。但是,我們可能因為還要繼續處理資料,而不想把資料寫進檔案系統中,所以將用有「Write」特性的「Vec」結構實體傳給套件使用,但這樣做的話,若是套件沒有再提供能夠操作「Vec」結構實體的API,傳進去的「Vec」結構實體根本就拿不回來,也就無法繼續使用了。為了要能夠存取到寫進記憶體中的結果,就必須不能把「Vec」結構實體的擁有權直接交給套件,我們需要透過別種方式來留住「Vec」結構實體的擁有權。

為了解釋這個問題,我們用以下這個程式來說明:

以上程式,若將「encoded_result」這個「Vec」結構實體作為「Writer」交給「Encoder」結構實體來使用,我們就再也拿不回它的擁有權了。

先前的文章中有提到Rc智慧型指標,我們可以利用Rc智慧型指標,來讓「Vec」結構實體能夠允許多個擁有者。將以上的「main.rs」改寫如下:

編譯以上程式後,會發現程式編譯失敗了,因為Rc智慧型指標並沒有實作「Write」特性。看到這裡,想必各位也應該知道要怎麼解決這個問題了,那就是我們只要實作出一個新的結構體,讓它可以儲存「Rc<Vec<u8>>」實體,並且有實作「Write」特性,再把這個結構體的實體交給「Encoder」結構體使用即可!當然,還有一個要修改的地方是,因為「Write」特性的「write」相關方法都必須使用「& mut self」,因此我們傳給這個新結構體的智慧型指標必須要允許取得可變參考,也就是說我們除了要使用Rc智慧型指標外,還要使用RefCell智慧型指標。

程式修改如下:

如此一來,就算套件提供的API會拿走「Writer」的擁有權,我們還是可以建立出「RcWriter」結構實體來製作出新的擁有者,使其乖乖讓其它套件搶走,因為有Rc智慧型指標的關係,我們之後依然可以繼續使用同樣的「Writer」。

當然,因為在一開始我們就把「Writer」的擁有權交給「RefCell」智慧型指標和「Rc」智慧型指標來管理了,因此之後也只能使用「borrow」或是「borrow_mut」方法來取得「Writer」的不可變參考或是可變參考。如果連「Writer」的擁有權都想要能夠完全取回的話,我們還可以再加一層「Option」列舉來包裹我們的「Writer」。程式改寫如下:

利用「Option」列舉實體提供的「take」方法,我們可以將「Some」變體中包裹的資料取出,改變該資料的擁有者,並將原本的「Option」列舉實體變為「Some」變體。於是我們就能夠取回最一開始的「Writer」的擁有權啦!

Rc Writer

「Rc Writer」是筆者開發的套件,將以上實作的「RcWriter」結構體和「RcOptionWriter」結構體包進這個套件,方便重複使用。

Crates.io

https://crates.io/crates/rc-writer

Cargo.toml

rc-writer = "*"