在開發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」是筆者開發的套件,提供了四種結構體,RcU8Reader
、RcRefCellU8Reader
、ArcU8Reader
和ArcMutexU8Reader
,可以分別用來包裹Rc<T>
、Rc<RefCell<T>>
、Arc<T>
、Arc<Mutex<T>>
型別的資料,使其擁有Read
特性。此處的泛型參數T
為必須要有實作AsRef<u8>
特性的型別。
Crates.io
Cargo.toml
使用方法
直接用「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);
順帶一提,RcU8Reader
和ArcU8Reader
結構體,除了有實作Read
特性外,還有實作Seek
特性。