在開發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

rc-writer = "*"