在開發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
結構實體的擁有權。
為了解釋這個問題,我們用以下這個程式來說明:
use std::io::{self, Write};
pub struct Encoder<W: Write> {
writer: W
}
impl<W: Write> Encoder<W> {
pub fn new(writer: W) -> Encoder<W> {
Encoder {
writer
}
}
pub fn save(&mut self) -> Result<(), io::Error> {
let buffer: Vec<u8> = Vec::new();
// do something
// ...
// and finally
self.writer.write(&buffer)?;
self.writer.flush()?;
Ok(())
}
}
mod encode;
fn main() {
let encoded_result = Vec::new();
let mut encoder = encode::Encoder::new(encoded_result);
encoder.save();
// How can I get the encoded_result back?
}
以上程式,若將encoded_result
這個Vec
結構實體作為Writer
交給Encoder
結構實體來使用,我們就再也拿不回它的擁有權了。
在先前的文章中有提到Rc智慧型指標,我們可以利用Rc智慧型指標,來讓Vec
結構實體能夠允許多個擁有者。將以上的main.rs
改寫如下:
mod encode;
use std::rc::Rc;
fn main() {
let encoded_result: Rc<Vec<u8>> = Rc::new(Vec::new());
let encoded_result_writer = Rc::clone(encoded_result);
let mut encoder = encode::Encoder::new(encoded_result_writer);
encoder.save().unwrap();
println!("{:?}", encoded_result.borrow()); // Am I getting the encoded_result back?
}
編譯以上程式後,會發現程式編譯失敗了,因為Rc智慧型指標並沒有實作Write
特性。看到這裡,想必各位也應該知道要怎麼解決這個問題了,那就是我們只要實作出一個新的結構體,讓它可以儲存Rc<Vec<u8>>
實體,並且有實作Write
特性,再把這個結構體的實體交給Encoder
結構體使用即可!當然,還有一個要修改的地方是,因為Write
特性的write
相關方法都必須使用& mut self
,因此我們傳給這個新結構體的智慧型指標必須要允許取得可變參考,也就是說我們除了要使用Rc智慧型指標外,還要使用RefCell智慧型指標。
程式修改如下:
mod encode;
use std::rc::Rc;
use std::cell::RefCell;
use std::io::{self, Write};
pub struct RcWriter<W: Write> {
inner: Rc<RefCell<W>>
}
impl<W: Write> RcWriter<W> {
pub fn new(writer: Rc<RefCell<W>>) -> RcWriter<W> {
RcWriter {
inner: writer
}
}
}
impl<W: Write> Write for RcWriter<W> {
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
self.inner.borrow_mut().write(buf)
}
fn flush(&mut self) -> Result<(), io::Error> {
self.inner.borrow_mut().flush()
}
}
fn main() {
let encoded_result: Rc<RefCell<Vec<u8>>> = Rc::new(RefCell::new(Vec::new()));
let encoded_result_writer = RcWriter::new(Rc::clone(&encoded_result));
let mut encoder = encode::Encoder::new(encoded_result_writer);
encoder.save().unwrap();
println!("{:?}", encoded_result.borrow()); // I can get it!
}
如此一來,就算套件提供的API會拿走Writer
的擁有權,我們還是可以建立出RcWriter
結構實體來製作出新的擁有者,使其乖乖讓其它套件搶走,因為有Rc智慧型指標的關係,我們之後依然可以繼續使用同樣的Writer
。
當然,因為在一開始我們就把Writer
的擁有權交給RefCell
智慧型指標和Rc
智慧型指標來管理了,因此之後也只能使用borrow
或是borrow_mut
方法來取得Writer
的不可變參考或是可變參考。如果連Writer
的擁有權都想要能夠完全取回的話,我們還可以再加一層Option
列舉來包裹我們的Writer
。程式改寫如下:
mod encode;
use std::rc::Rc;
use std::cell::RefCell;
use std::io::{self, Write, ErrorKind};
pub struct RcOptionWriter<W: Write> {
inner: Rc<RefCell<Option<W>>>
}
impl<W: Write> RcOptionWriter<W> {
pub fn new(writer: Rc<RefCell<Option<W>>>) -> RcOptionWriter<W> {
RcOptionWriter {
inner: writer
}
}
}
impl<W: Write> Write for RcOptionWriter<W> {
fn write(&mut self, buf: &[u8]) -> Result<usize, io::Error> {
match self.inner.borrow_mut().as_mut() {
Some(writer) => writer.write(buf),
None => Err(io::Error::new(ErrorKind::BrokenPipe, "the writer has been removed out"))
}
}
fn flush(&mut self) -> Result<(), io::Error> {
match self.inner.borrow_mut().as_mut() {
Some(writer) => writer.flush(),
None => Err(io::Error::new(ErrorKind::BrokenPipe, "the writer has been removed out"))
}
}
}
fn main() {
let encoded_result: Rc<RefCell<Option<Vec<u8>>>> = Rc::new(RefCell::new(Some(Vec::new())));
let encoded_result_writer = RcOptionWriter::new(Rc::clone(&encoded_result));
let mut encoder = encode::Encoder::new(encoded_result_writer);
encoder.save().unwrap();
let my_dear_writer: Vec<u8> = encoded_result.borrow_mut().take().unwrap();
println!("{:?}", my_dear_writer);
}
利用Option
列舉實體提供的take
方法,我們可以將Some
變體中包裹的資料取出,改變該資料的擁有者,並將原本的Option
列舉實體變為Some
變體。於是我們就能夠取回最一開始的Writer
的擁有權啦!
Rc Writer
「Rc Writer」是筆者開發的套件,將以上實作的RcWriter
結構體和RcOptionWriter
結構體包進這個套件,方便重複使用。
Crates.io
Cargo.toml