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



有些套件定義的API,會需要輸入有實作Read特性的實體,來作為資料的輸入來源。以Rust標準函式庫提供的Vec<u8>結構體來說,它本身雖然並沒有實作Read特性,但是可以藉由使用Cursor結構體來包裹Vec<u8>結構實體,使其也能擁有Read特性。例如以下程式:

use std::io::{self, Read, Cursor};

fn read_to_string<R: Read>(mut reader: R) -> Result<String, io::Error> {
    let mut s = String::new();

    reader.read_to_string(&mut s)?;

    Ok(s)
}

...

let data: Vec<u8> = vec![48, 49, 50, 51, 52];

let result = read_to_string(Cursor::new(data)).unwrap();

assert_eq!("01234".to_string(), result);

在這個程式中,read_to_string函數要傳入一個有實作Read特性的型別的實體,所以我們把data這個Vec<u8>結構實體包裹進Cursor結構體中,作為參數傳給read_to_string函數。當然,這個程式如果只寫到這樣是沒有問題的,但是如果我們有另外一個函數也要處理data的話怎麼辦呢?

use std::io::{self, Read, Cursor};

fn read_to_string<R: Read>(mut reader: R) -> Result<String, io::Error> {
    let mut s = String::new();

    reader.read_to_string(&mut s)?;

    Ok(s)
}

fn sum<R: Read>(mut reader: R) -> Result<u64, io::Error> {
    let mut sum = 0;

    let mut buffer = [0; 32];

    loop {
        let c = reader.read(&mut buffer)?;

        if c == 0 {
            break;
        }

        sum += buffer[0..c].iter().map(|&v| v as u64).sum::<u64>();
    }

    Ok(sum)
}

...

let data: Vec<u8> = vec![48, 49, 50, 51, 52];

let result = read_to_string(Cursor::new(data)).unwrap();

assert_eq!("01234".to_string(), result);

let result = sum(Cursor::new(data)).unwrap();

assert_eq!(250, result);

以上程式會編譯失敗,因為傳給sum函數使用的data的值的擁有權,在之前就已經為了要給read_to_string函數使用而被轉移了。所以我們可以怎麼解決這個問題呢?很簡單,把先前有需要轉移data的值的擁有權的地方,通通在使用data時加上.clone()來先複製成新的一份!

use std::io::{self, Read, Cursor};

fn read_to_string<R: Read>(mut reader: R) -> Result<String, io::Error> {
    let mut s = String::new();

    reader.read_to_string(&mut s)?;

    Ok(s)
}

fn sum<R: Read>(mut reader: R) -> Result<u64, io::Error> {
    let mut sum = 0;

    let mut buffer = [0; 32];

    loop {
        let c = reader.read(&mut buffer)?;

        if c == 0 {
            break;
        }

        sum += buffer[0..c].iter().map(|&v| v as u64).sum::<u64>();
    }

    Ok(sum)
}

...

let data: Vec<u8> = vec![48, 49, 50, 51, 52];

let result = read_to_string(Cursor::new(data.clone())).unwrap();

assert_eq!("01234".to_string(), result);

let result = sum(Cursor::new(data)).unwrap();

assert_eq!(250, result);

這樣做雖然可以通過編譯,也可以正常運行,但程式效能是很差的,因為同一份資料會被複製成兩份以上(在這個例子中是兩份)。在先前的文章中有提到Rc智慧型指標,我們可以利用Rc智慧型指標,來讓Vec<u8>結構實體能夠允許多個擁有者。於是為了讓程式效能更好,我們嘗試使用Rc智慧型指標先包裹data變數所儲存的Vec<u8>結構實體,使其在進行複製的時候只需要複製Rc智慧型指標就好,而不必複製整個Vec<u8>結構實體。將以上的程式改寫如下:

use std::io::{self, Read, Cursor};
use std::rc::Rc;

fn read_to_string<R: Read>(mut reader: R) -> Result<String, io::Error> {
    let mut s = String::new();

    reader.read_to_string(&mut s)?;

    Ok(s)
}

fn sum<R: Read>(mut reader: R) -> Result<u64, io::Error> {
    let mut sum = 0;

    let mut buffer = [0; 32];

    loop {
        let c = reader.read(&mut buffer)?;

        if c == 0 {
            break;
        }

        sum += buffer[0..c].iter().map(|&v| v as u64).sum::<u64>();
    }

    Ok(sum)
}

...

let data: Rc<Vec<u8>> = Rc::new(vec![48, 49, 50, 51, 52]);

let result = read_to_string(Cursor::new(data.clone())).unwrap();

assert_eq!("01234".to_string(), result);

let result = sum(Cursor::new(data)).unwrap();

assert_eq!(250, result);

然而,這樣做是無法通過編譯的,因為Rc<Vec<u8>>並沒有實作AsRef<u8>特性,所以無法交給Cursor結構體來包裹。

為了解決這個問題,就需要用到以下要介紹的套件啦!

Rc U8 Reader

「Rc U8 Reader」是筆者開發的套件,提供了四種結構體,RcU8ReaderRcRefCellU8ReaderArcU8ReaderArcMutexU8Reader,可以分別用來包裹Rc<T>Rc<RefCell<T>>Arc<T>Arc<Mutex<T>>型別的資料,使其擁有Read特性。此處的泛型參數T為必須要有實作AsRef<u8>特性的型別。

Crates.io

Cargo.toml

rc-u8-reader = "*"

使用方法

直接用「Rc U8 Reader」套件來修改上面的例子,程式如下:

use std::io::{self, Read};
use std::rc::Rc;

use rc_u8_reader::RcU8Reader;

fn read_to_string<R: Read>(mut reader: R) -> Result<String, io::Error> {
    let mut s = String::new();

    reader.read_to_string(&mut s)?;

    Ok(s)
}

fn sum<R: Read>(mut reader: R) -> Result<u64, io::Error> {
    let mut sum = 0;

    let mut buffer = [0; 32];

    loop {
        let c = reader.read(&mut buffer)?;

        if c == 0 {
            break;
        }

        sum += buffer[0..c].iter().map(|&v| v as u64).sum::<u64>();
    }

    Ok(sum)
}

...

let data: Rc<Vec<u8>> = Rc::new(vec![48, 49, 50, 51, 52]);

let result = read_to_string(RcU8Reader::new(data.clone())).unwrap();

assert_eq!("01234".to_string(), result);

let result = sum(RcU8Reader::new(data)).unwrap();

assert_eq!(250, result);

順帶一提,RcU8ReaderArcU8Reader結構體,除了有實作Read特性外,還有實作Seek特性。