Rust常用特型之ToOwned特型示例詳解
在Rust標準庫中,存在很多常用的工具類特型,它們能幫助我們寫出更具有Rust風格的代碼。
ToOwned
這次我們來學一個和Borrow特型相關(guān)的特型,叫ToOwned類型??醋置嬉馑?code>Borrow是代表借出,而ToOwned代表去擁有它。
在Rust中,假定某類型實現(xiàn)了Clone特型,如果給你一個對它引用,那我們得到它指向內(nèi)容的備份的最常見方式是調(diào)用其clone()函數(shù)。但是如果你想克隆&str或者&[i32]時會發(fā)生什么呢?你的目的可能是想得到一個String或者Vec<i32>。但是根據(jù)Clone特型的定義,你無法得到它們。根據(jù)定義,對一個&T調(diào)用clone() 會返回一個T的值,也就是說會返回str或者[i32]。而我們前面學習Sized特型的時候提到過,str或者[i32] 是切片類型,無固定大小,是不能保存在變量中或者作為函數(shù)結(jié)果返回的。
std::borrow::ToOwned 特型提供了一個稍微寬松的方法將一引用轉(zhuǎn)換為可擁有的值。
trait ToOwned {
type Owned: Borrow<Self>;
fn to_owned(&self) -> Self::Owned;
}上面的定義中,to_owned函數(shù)返回一個類型為Self::Owned的新鮮值,但是這個Owned可不是任意類型,它有個類型約束,也就是實現(xiàn)了Borrow<Self>. 也就是說A能借出B/&B(實現(xiàn)了Borrow<B>),B才能擁有A.
例如,你可以從Vec<T>中借出一個&[T](這里的泛型U 為 [T] ),因此[T]可以實現(xiàn)ToOwned<Owned=Vec<T>>,只要T實現(xiàn)了Clone特型。這里為什么要對T限制呢?畢竟你要得到一個備份,如果一個T不能克隆,那么這個備份是無法實現(xiàn)的,因為需要把切片的元素復制到新的向量中去。相似的,str實現(xiàn)了ToOwned<Owned=String>,因此我們可以調(diào)用&str的to_owned函數(shù)得到一個全新的字符串。Path也實現(xiàn)了ToOwned<Owned=PathBuf>,我們也可以從Path引用中得到一個全新的PathBuf值。
Humble Cow
Borrow和ToOwned聯(lián)動可以實現(xiàn)一個很有意思的類型,Cow,注意它不是奶牛的意思,而是指 clone on write 我們趁熱來學習它。
充分利用 Rust 需要深思熟慮所有權(quán)問題,例如某個函數(shù)是否應該通過引用或值接收參數(shù)。通常你能確定使用其中的一種或者另一種(使用引用還是值),函數(shù)的參數(shù)類型代表了你的決定。但是存在這樣一些場景,你只有在運行時才知道到底是需要借用還是引用,這時,std::borrow::Cow類型就派上用場了,它的定義如下:
enum Cow<'a, B: ?Sized>
where B: ToOwned
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}這里可以看到,Cow是一個枚舉,有兩個變量,分別代表借用和擁有。其Borrow變量綁定了一個&B(這里先忽視生命周期標記),這個B是個泛型,它的約束為B: ToOwned。它的目標類型我們先假定為U,那么U 必定實現(xiàn)了Borrow<B>。
它的第二個枚舉變量為Owned,綁定了一個<B as ToOwned>::Owned的值,也就是U的值,所以Owned變量可以寫成Owned<U>。其中可以從U借出B,當然,也可以從B擁有U的新值。
第一個枚舉變量,是綁定了&B,因此我們可以很方便的得到&B,第二個變量,是綁定了U,然而U又可以借出B,因此我們?nèi)匀豢梢院苋菀椎牡玫?code>&B( 通過U的borrow() 函數(shù))。兩個變量都可以方便的得到&B,因此它也實現(xiàn)了Deref特型,這樣你可以直接在Cow上調(diào)用B的相關(guān)函數(shù),而不管Cow是借用了B還是擁有了U。
你還可以在Cow類型的值上調(diào)用to_mut函數(shù)得到一個&mut B。 如果Cow變量剛好好Borrowed,則to_mut函數(shù)會先調(diào)用&B的to_owned方法得到它自己擁有的一個U的Copy,并對原來的變量進行重新賦值,這樣就從Borrowed變量轉(zhuǎn)換成了Owned變量,然后再從新?lián)碛械腢的值中借出一個mut 引用。這里正是clone on write的含義所在(寫時clone).
我們來看一下這個Deref的實現(xiàn)代碼:
#[stable(feature = "rust1", since = "1.0.0")]
impl<B: ?Sized + ToOwned> Deref for Cow<'_, B>
where
B::Owned: Borrow<B>,
{
type Target = B;
fn deref(&self) -> &B {
match *self {
Borrowed(borrowed) => borrowed,
Owned(ref owned) => owned.borrow(),
}
}
}你代碼中我們可以看到,如果Cow是引用 ,直接將這個引用返回,如果是擁有的U值,則從U值借出,這里有一個細節(jié):
Owned(ref owned) => owned.borrow(), 因為我們的deref函數(shù)接收參數(shù)為&self,因此我們無法在函數(shù)內(nèi)部消耗掉Cow本身,而match直接匹配時便會消耗這個值,因此為了阻止這種行為,添加了ref owned,代表這個owned只是獲取一個引用 ,因此這里的owned的類型其實為&U,所以直接調(diào)用其borrow()函數(shù)也就得到了一個&B. 注意borrow函數(shù)也是接收一個引用而非值作為參數(shù)。
to_mut函數(shù)的解釋為:
Acquires a mutable reference to the owned form of the data.
Clones the data if it is not already owned.
使用示例為:
use std::borrow::Cow;
let mut cow = Cow::Borrowed("foo");
cow.to_mut().make_ascii_uppercase();
assert_eq!(
cow,
Cow::Owned(String::from("FOO")) as Cow<'_, str>
);通過上面的示例我們可以看到,就算我們的cow是Borrowed變量,擁有一個共享的引用,到最后也變成了一個Owned變量。
我們來看一下實現(xiàn)過程:
#[stable(feature = "rust1", since = "1.0.0")]
pub fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned {
match *self {
Borrowed(borrowed) => {
*self = Owned(borrowed.to_owned());
match *self {
Borrowed(..) => unreachable!(),
Owned(ref mut owned) => owned,
}
}
Owned(ref mut owned) => owned,
}
}這里 如果是Borrowed,則首先會to_owned得到U的一個新值,然后再將self重新賦值為Owned,然后再重新對self進行match操作,
此時已經(jīng)是一個Owned,所以直接借出了ref mut,注意因為match操作會消耗值,所以這里的Owned(ref mut owned) => owned, 中加了ref 代表是一個引用,結(jié)合上例,我們就得到了一個&mut String。
相似的,Cow也實現(xiàn)了into_owned方法將引用轉(zhuǎn)換為一個擁有的值。如果必須,則可以將值的所有權(quán)轉(zhuǎn)移給調(diào)用者,在這個過程中Cow本身的值會被消耗掉。
Cow一個常見的用法是返回一個靜態(tài)的字符串文字值常量或者一個動態(tài)的字符串。例如,假定你需要將一個枚舉類型轉(zhuǎn)換成一個消息,枚舉的大多數(shù)變量都可用于固定的字符串,但是有一些變量或者一些額外的信息,因此你可以返回一個Cow<'static str>。
use std::path::PathBuf;
use std::borrow::Cow;
fn describe(error: &Error) -> Cow<'static, str> {
match *error {
Error::OutOfMemory => "out of memory".into(),
Error::StackOverflow => "stack overflow".into(),
Error::MachineOnFire => "machine on fire".into(),
Error::Unfathomable => "machine bewildered".into(),
Error::FileNotFound(ref path) => {
format!("file not found: {}", path.display()).into()
}
}
}上面的代碼使用了Cow的Into特型實現(xiàn)來構(gòu)造值。這里其實是Cow的From實現(xiàn),然后相對應的&str就有了Into實現(xiàn)。這個Match的絕大多數(shù)分支都返回一個靜態(tài)分配的文字串文本用于Cow::Borrowed綁定,只有最后一個分支返回一個String用于Owned變量綁定。
describe函數(shù)的調(diào)用者不用管返回的到底Cow的哪個變量,它只用簡單的將返回值看成是&str就行了,例如:
println!("Disaster has struck: {}", describe(&error));
如果你需要一個擁有的值,調(diào)用into_owned函數(shù)就可,例如 (describe(&error).into_owned() 就返回一個String。
使用Cow可以讓describle函數(shù)和它的調(diào)用者直到在需要時才會分配內(nèi)存來保存新生成的字符串。
到此這篇關(guān)于Rust常用特型之ToOwned特型的文章就介紹到這了,更多相關(guān)Rust ToOwned特型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust語言之Prometheus系統(tǒng)監(jiān)控工具包的使用詳解
Prometheus?是一個開源的系統(tǒng)監(jiān)控和警報工具包,最初是由SoundCloud構(gòu)建的,隨著時間的發(fā)展,Prometheus已經(jīng)具有適用于各種使用場景的版本,為了開發(fā)者方便開發(fā),更是有各種語言版本的Prometheus的開發(fā)工具包,本文主要介紹Rust版本的Prometheus開發(fā)工具包2023-10-10

