Rust常用特型之ToOwned特型示例詳解
在Rust標(biāo)準(zhǔn)庫中,存在很多常用的工具類特型,它們能幫助我們寫出更具有Rust風(fēng)格的代碼。
ToOwned
這次我們來學(xué)一個(gè)和Borrow
特型相關(guān)的特型,叫ToOwned
類型??醋置嬉馑?code>Borrow是代表借出,而ToOwned
代表去擁有它。
在Rust中,假定某類型實(shí)現(xiàn)了Clone
特型,如果給你一個(gè)對它引用,那我們得到它指向內(nèi)容的備份的最常見方式是調(diào)用其clone()
函數(shù)。但是如果你想克隆&str
或者&[i32]
時(shí)會發(fā)生什么呢?你的目的可能是想得到一個(gè)String
或者Vec<i32>
。但是根據(jù)Clone
特型的定義,你無法得到它們。根據(jù)定義,對一個(gè)&T
調(diào)用clone()
會返回一個(gè)T
的值,也就是說會返回str
或者[i32]
。而我們前面學(xué)習(xí)Sized
特型的時(shí)候提到過,str
或者[i32]
是切片類型,無固定大小,是不能保存在變量中或者作為函數(shù)結(jié)果返回的。
std::borrow::ToOwned
特型提供了一個(gè)稍微寬松的方法將一引用轉(zhuǎn)換為可擁有的值。
trait ToOwned { type Owned: Borrow<Self>; fn to_owned(&self) -> Self::Owned; }
上面的定義中,to_owned
函數(shù)返回一個(gè)類型為Self::Owned
的新鮮值,但是這個(gè)Owned
可不是任意類型,它有個(gè)類型約束,也就是實(shí)現(xiàn)了Borrow<Self>
. 也就是說A能借出B/&B
(實(shí)現(xiàn)了Borrow<B>
),B才能擁有A.
例如,你可以從Vec<T>
中借出一個(gè)&[T]
(這里的泛型U 為 [T]
),因此[T]
可以實(shí)現(xiàn)ToOwned<Owned=Vec<T>>
,只要T
實(shí)現(xiàn)了Clone
特型。這里為什么要對T
限制呢?畢竟你要得到一個(gè)備份,如果一個(gè)T
不能克隆,那么這個(gè)備份是無法實(shí)現(xiàn)的,因?yàn)樾枰亚衅脑貜?fù)制到新的向量中去。相似的,str
實(shí)現(xiàn)了ToOwned<Owned=String>
,因此我們可以調(diào)用&str
的to_owned
函數(shù)得到一個(gè)全新的字符串。Path
也實(shí)現(xiàn)了ToOwned<Owned=PathBuf>
,我們也可以從Path
引用中得到一個(gè)全新的PathBuf
值。
Humble Cow
Borrow
和ToOwned
聯(lián)動可以實(shí)現(xiàn)一個(gè)很有意思的類型,Cow
,注意它不是奶牛的意思,而是指 clone on write
我們趁熱來學(xué)習(xí)它。
充分利用 Rust 需要深思熟慮所有權(quán)問題,例如某個(gè)函數(shù)是否應(yīng)該通過引用或值接收參數(shù)。通常你能確定使用其中的一種或者另一種(使用引用還是值),函數(shù)的參數(shù)類型代表了你的決定。但是存在這樣一些場景,你只有在運(yùn)行時(shí)才知道到底是需要借用還是引用,這時(shí),std::borrow::Cow
類型就派上用場了,它的定義如下:
enum Cow<'a, B: ?Sized> where B: ToOwned { Borrowed(&'a B), Owned(<B as ToOwned>::Owned), }
這里可以看到,Cow是一個(gè)枚舉,有兩個(gè)變量,分別代表借用和擁有。其Borrow
變量綁定了一個(gè)&B
(這里先忽視生命周期標(biāo)記),這個(gè)B是個(gè)泛型,它的約束為B: ToOwned
。它的目標(biāo)類型我們先假定為U,那么U 必定實(shí)現(xiàn)了Borrow<B>
。
它的第二個(gè)枚舉變量為Owned
,綁定了一個(gè)<B as ToOwned>::Owned
的值,也就是U的值,所以Owned
變量可以寫成Owned<U>
。其中可以從U借出B,當(dāng)然,也可以從B擁有U的新值。
第一個(gè)枚舉變量,是綁定了&B,因此我們可以很方便的得到&B
,第二個(gè)變量,是綁定了U,然而U又可以借出B,因此我們?nèi)匀豢梢院苋菀椎牡玫?code>&B( 通過U的borrow() 函數(shù))。兩個(gè)變量都可以方便的得到&B
,因此它也實(shí)現(xiàn)了Deref
特型,這樣你可以直接在Cow上調(diào)用B的相關(guān)函數(shù),而不管Cow是借用了B還是擁有了U。
你還可以在Cow
類型的值上調(diào)用to_mut
函數(shù)得到一個(gè)&mut B
。 如果Cow
變量剛好好Borrowed
,則to_mut
函數(shù)會先調(diào)用&B
的to_owned
方法得到它自己擁有的一個(gè)U的Copy,并對原來的變量進(jìn)行重新賦值,這樣就從Borrowed
變量轉(zhuǎn)換成了Owned
變量,然后再從新?lián)碛械腢的值中借出一個(gè)mut 引用。這里正是clone on write
的含義所在(寫時(shí)clone).
我們來看一下這個(gè)Deref
的實(shí)現(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
是引用 ,直接將這個(gè)引用返回,如果是擁有的U值,則從U值借出,這里有一個(gè)細(xì)節(jié):
Owned(ref owned) => owned.borrow(),
因?yàn)槲覀兊膁eref函數(shù)接收參數(shù)為&self
,因此我們無法在函數(shù)內(nèi)部消耗掉Cow
本身,而match
直接匹配時(shí)便會消耗這個(gè)值,因此為了阻止這種行為,添加了ref owned
,代表這個(gè)owned
只是獲取一個(gè)引用 ,因此這里的owned的類型其實(shí)為&U
,所以直接調(diào)用其borrow()函數(shù)也就得到了一個(gè)&B. 注意borrow函數(shù)也是接收一個(gè)引用而非值作為參數(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
變量,擁有一個(gè)共享的引用,到最后也變成了一個(gè)Owned
變量。
我們來看一下實(shí)現(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的一個(gè)新值,然后再將self重新賦值為Owned,然后再重新對self進(jìn)行match操作,
此時(shí)已經(jīng)是一個(gè)Owned,所以直接借出了ref mut,注意因?yàn)閙atch操作會消耗值,所以這里的Owned(ref mut owned) => owned,
中加了ref 代表是一個(gè)引用,結(jié)合上例,我們就得到了一個(gè)&mut String。
相似的,Cow
也實(shí)現(xiàn)了into_owned
方法將引用轉(zhuǎn)換為一個(gè)擁有的值。如果必須,則可以將值的所有權(quán)轉(zhuǎn)移給調(diào)用者,在這個(gè)過程中Cow
本身的值會被消耗掉。
Cow一個(gè)常見的用法是返回一個(gè)靜態(tài)的字符串文字值常量或者一個(gè)動態(tài)的字符串。例如,假定你需要將一個(gè)枚舉類型轉(zhuǎn)換成一個(gè)消息,枚舉的大多數(shù)變量都可用于固定的字符串,但是有一些變量或者一些額外的信息,因此你可以返回一個(gè)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
特型實(shí)現(xiàn)來構(gòu)造值。這里其實(shí)是Cow
的From
實(shí)現(xiàn),然后相對應(yīng)的&str
就有了Into
實(shí)現(xiàn)。這個(gè)Match的絕大多數(shù)分支都返回一個(gè)靜態(tài)分配的文字串文本用于Cow::Borrowed
綁定,只有最后一個(gè)分支返回一個(gè)String
用于Owned
變量綁定。
describe
函數(shù)的調(diào)用者不用管返回的到底Cow的哪個(gè)變量,它只用簡單的將返回值看成是&str
就行了,例如:
println!("Disaster has struck: {}", describe(&error));
如果你需要一個(gè)擁有的值,調(diào)用into_owned
函數(shù)就可,例如 (describe(&error).into_owned()
就返回一個(gè)String
。
使用Cow可以讓describle
函數(shù)和它的調(diào)用者直到在需要時(shí)才會分配內(nèi)存來保存新生成的字符串。
到此這篇關(guān)于Rust常用特型之ToOwned特型的文章就介紹到這了,更多相關(guān)Rust ToOwned特型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust?編程語言中的所有權(quán)ownership詳解
這篇文章主要介紹了Rust?編程語言中的所有權(quán)ownership詳解的相關(guān)資料,需要的朋友可以參考下2023-02-02Rust 的 into_owned() 方法實(shí)例詳解
into_owned是Rust語言中std::borrow::Cow 枚舉的一個(gè)方法,into_owned確保了調(diào)用者獲得數(shù)據(jù)的獨(dú)立所有權(quán),無論Cow之前是引用還是已經(jīng)擁有數(shù)據(jù),本文給大家介紹Rust 的 into_owned() 方法,感興趣的的朋友跟隨小編一起看看吧2024-03-03Rust?搭建一個(gè)小程序運(yùn)行環(huán)境的方法詳解
rust是一門比較新的編程語言,2015年5月15日,Rust編程語言核心團(tuán)隊(duì)正式宣布發(fā)布Rust?1.0版本,本文給大家介紹Rust?搭建一個(gè)小程序運(yùn)行環(huán)境,以iOS?為例介紹開發(fā)環(huán)境的準(zhǔn)備,感興趣的朋友跟隨小編一起看看吧2022-05-05Rust 中的 Packages 與 Crates模塊化構(gòu)建的基礎(chǔ)及開發(fā)流程
Rust中的Crate是編譯器處理的最小代碼單元,可以是二進(jìn)制或庫,每個(gè)Crate由一個(gè)CrateRoot文件(通常是src/main.rs或src/lib.rs)定義,本文給大家介紹Rust 中的 Packages 與 Crates模塊化構(gòu)建的基礎(chǔ)及開發(fā)流程,感興趣的朋友一起看看吧2025-02-02