rust類(lèi)型轉(zhuǎn)換的實(shí)現(xiàn)
Rust 是類(lèi)型安全的語(yǔ)言,因此在 Rust 中做類(lèi)型轉(zhuǎn)換不是一件簡(jiǎn)單的事。
as轉(zhuǎn)換
Rust 不提供原生類(lèi)型之間的隱式類(lèi)型轉(zhuǎn)換(coercion),但可以使用 as 關(guān)鍵字進(jìn)行顯式類(lèi)型轉(zhuǎn)換(casting)。例如:
fn main() { cast(); } // as 進(jìn)行的顯示類(lèi)型強(qiáng)制轉(zhuǎn)換 fn cast() { let n: u8 = 123; let m: i32 = n as i32; // 將u8強(qiáng)制轉(zhuǎn)換為i32類(lèi)型 println!("u8({})轉(zhuǎn)i32({})", n, m); let a = 12345; // 整型字面值常量是i32類(lèi)型 let b: i8 = a as i8; // 能容納更大數(shù)值的類(lèi)型i32轉(zhuǎn)容納范圍較小的i8,存在數(shù)據(jù)溢出的風(fēng)險(xiǎn)。 println!("i32({})轉(zhuǎn)i8({})", a, b); let c = '我'; // char類(lèi)型 let d = c as u32; println!("char({})轉(zhuǎn)u32({})", c, d); let f = 100u8; let h = f as char; // 只有u8才能轉(zhuǎn)char(相當(dāng)于只支持ASCII碼的值和字符轉(zhuǎn)換) println!("u8({})轉(zhuǎn)char({})", f, h); let f = 123.123; let q = f as i32; println!("f64({})轉(zhuǎn)i32({})", f, q); let mut num = [1, 2, 3]; let mut y = num.as_mut_ptr(); // 可變的指針類(lèi)型 let mut p = y as usize; // 把指針轉(zhuǎn)為usize類(lèi)型 p += 4; // 指針步進(jìn)一步(i32類(lèi)型占4字節(jié),因此加4即可) y = p as *mut i32; // 將 usize轉(zhuǎn)為指針 unsafe { println!("{}", *y); // 在unsafe模塊中操作指針 } }
轉(zhuǎn)換不具有傳遞性 就算 e as U1 as U2
是合法的,也不能說(shuō)明 e as U2
是合法的(e 不能直接轉(zhuǎn)換成 U2)。as轉(zhuǎn)換基本上只用于數(shù)值類(lèi)型之間的轉(zhuǎn)換。而且需要注意,當(dāng)你從可以容納范圍更大的數(shù)據(jù)類(lèi)型向可以容納范圍較小的數(shù)據(jù)類(lèi)型轉(zhuǎn)換的時(shí)候會(huì)發(fā)生溢出,因此你要人為保證數(shù)據(jù)轉(zhuǎn)換是正確的。
into和from
From 和 Into 兩個(gè) trait 是內(nèi)部相關(guān)聯(lián)的,實(shí)際上這是它們實(shí)現(xiàn)的一部分。如果我們能夠從類(lèi)型 B 得到類(lèi)型 A,那么很容易相信我們也能夠把類(lèi)型 B 轉(zhuǎn)換為類(lèi)型 A。
From
From trait 允許一種類(lèi)型定義 “怎么根據(jù)另一種類(lèi)型生成自己”,因此它提供了一種類(lèi)型轉(zhuǎn)換的簡(jiǎn)單機(jī)制。在標(biāo)準(zhǔn)庫(kù)中有無(wú)數(shù) From 的實(shí)現(xiàn),規(guī)定原生類(lèi)型及其他常見(jiàn)類(lèi)型的轉(zhuǎn)換功能。
比如,可以很容易地把 str 轉(zhuǎn)換成 String:
let s = String::from("qwert"); println!("s={s}");
也可以為我們自己的類(lèi)型定義轉(zhuǎn)換機(jī)制:
#[derive(Debug)] #[allow(unused)] struct Number { value: i32, } impl From<i32> for Number { fn from(item: i32) -> Self { Number { value: item } } } let num = Number::from(30); println!("My number is {:?}", num);
Into
Into trait 就是把 From trait 倒過(guò)來(lái)而已。也就是說(shuō),如果你為你的類(lèi)型實(shí)現(xiàn)了 From,那么同時(shí)你也就免費(fèi)獲得了 Into。
使用 Into trait 通常要求指明要轉(zhuǎn)換到的類(lèi)型,因?yàn)榫幾g器大多數(shù)時(shí)候不能推斷它。不過(guò)考慮到我們免費(fèi)獲得了 Into,這點(diǎn)代價(jià)不值一提。
// 需要指明轉(zhuǎn)換到的類(lèi)型是Number let a: Number = 1.into(); println!("My number is {:?}", a);
TryInto和TryFrom
類(lèi)似于 From 和 Into,TryFrom 和 TryInto 是類(lèi)型轉(zhuǎn)換的通用 trait。不同于 From/Into 的是,TryFrom 和 TryInto trait 用于易出錯(cuò)的轉(zhuǎn)換,也正因如此,其返回值是 Result 型。
pub fn catsing(){ let b = 123; let a: u8 = b.try_into().unwrap(); // try_into println!("{a}"); let b:i32 = 12345; // 有一點(diǎn)非常奇怪,那就是必須顯示聲明b的類(lèi)型,否則編譯器無(wú)法推斷e的類(lèi)型,導(dǎo)致錯(cuò)誤。 let _a: u8 = match b.try_into() { // try_into Ok(v) => v, Err(e) => { println!("{:?}", e.to_string()); 0 } }; }
如果我們需要自己實(shí)現(xiàn)try_from和try_into方法,那么需要實(shí)現(xiàn)TryFrom trait即可。例如:
#[derive(Debug, PartialEq)] struct EvenNumber(i32); impl TryFrom<i32> for EvenNumber { type Error = (); fn try_from(value: i32) -> Result<Self, Self::Error> { if value % 2 == 0 { Ok(EvenNumber(value)) } else { Err(()) } } } // TryFrom assert_eq!(EvenNumber::try_from(8), Ok(EvenNumber(8))); assert_eq!(EvenNumber::try_from(5), Err(())); // TryInto let result: Result<EvenNumber, ()> = 8i32.try_into(); assert_eq!(result, Ok(EvenNumber(8))); let result: Result<EvenNumber, ()> = 5i32.try_into(); assert_eq!(result, Err(()));
ToString 和 FromStr
上面的這些轉(zhuǎn)換適大多數(shù)時(shí)候不適合字符串。它更需要ToString
Display
要把任何類(lèi)型轉(zhuǎn)換成 String,只需要實(shí)現(xiàn)那個(gè)類(lèi)型的 ToString trait。然而不要直接這么做,您應(yīng)該實(shí)現(xiàn)fmt::Display trait,它會(huì)自動(dòng)提供 ToString,并且還可以用來(lái)打印類(lèi)型。
pub fn format_string() { use std::fmt; struct Circle { radius: i32 } impl fmt::Display for Circle { // 為 Circle 實(shí)現(xiàn) Display trait fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Circle of radius {}", self.radius) } } let circle = Circle { radius: 6 }; println!("{}", circle.to_string()); // to_string是由Display trait實(shí)現(xiàn)的。 }
當(dāng)然了,也可以實(shí)現(xiàn)ToString trait。例如:
pub fn to_stirng() { struct Circle { radius: i32 } impl ToString for Circle { fn to_string(&self) -> String { format!("Circle of radius {:?}", self.radius) } } let circle = Circle { radius: 6 }; println!("{}", circle.to_string()); }
字符串轉(zhuǎn)數(shù)字
只要對(duì)目標(biāo)類(lèi)型實(shí)現(xiàn)了 FromStr trait,就可以用 parse 把字符串轉(zhuǎn)換成目標(biāo)類(lèi)型。 標(biāo)準(zhǔn)庫(kù)中已經(jīng)給無(wú)數(shù)種類(lèi)型實(shí)現(xiàn)了 FromStr。如果要轉(zhuǎn)換到用戶定義類(lèi)型,只要手動(dòng)實(shí)現(xiàn) FromStr 就行。
我們得提供要轉(zhuǎn)換到的類(lèi)型,這可以通過(guò)顯示聲明類(lèi)型,或者用 “渦輪魚(yú)” 語(yǔ)法(turbo fish,<>)實(shí)現(xiàn)。例如:
pub fn string_to_number(){ let num = "12345"; let num = num.parse::<i32>().unwrap(); // turbo fish寫(xiě)法 println!("{}", num); let num = "12345"; let num: u64 = num.parse().unwrap(); // 顯示聲明類(lèi)型寫(xiě)法 println!("{}", num); }
點(diǎn)操作符
方法調(diào)用的點(diǎn)操作符看起來(lái)簡(jiǎn)單,實(shí)際上非常不簡(jiǎn)單,它在調(diào)用時(shí),會(huì)發(fā)生很多魔法般的類(lèi)型轉(zhuǎn)換,例如:自動(dòng)引用、自動(dòng)解引用,強(qiáng)制類(lèi)型轉(zhuǎn)換直到類(lèi)型能匹配等。
假設(shè)有一個(gè)方法 foo,它有一個(gè)接收器(接收器就是 self、&self、&mut self 參數(shù))。如果調(diào)用 value.foo(),編譯器在調(diào)用 foo 之前,需要決定到底使用哪個(gè) Self 類(lèi)型來(lái)調(diào)用。現(xiàn)在假設(shè) value 擁有類(lèi)型 T。再進(jìn)一步,我們使用完全限定語(yǔ)法來(lái)進(jìn)行準(zhǔn)確的函數(shù)調(diào)用:
- 首先,編譯器檢查它是否可以直接調(diào)用 T::foo(value),稱(chēng)之為值方法調(diào)用
- 如果上一步調(diào)用無(wú)法完成(例如方法類(lèi)型錯(cuò)誤或者特征沒(méi)有針對(duì) Self 進(jìn)行實(shí)現(xiàn),上文提到過(guò)特征不能進(jìn)行強(qiáng)制轉(zhuǎn)換),那么編譯器會(huì)嘗試增加自動(dòng)引用,例如會(huì)嘗試以下調(diào)用:
<&T>::foo(value)
和<&mut T>::foo(value)
,稱(chēng)之為引用方法調(diào)用 - 若上面兩個(gè)方法依然不工作,編譯器會(huì)試著解引用 T ,然后再進(jìn)行嘗試。這里使用了 Deref 特征 —— 若
T: Deref<Target = U>
(T 可以被解引用為 U),那么編譯器會(huì)使用 U 類(lèi)型進(jìn)行嘗試,稱(chēng)之為解引用方法調(diào)用 - 若 T 不能被解引用,且 T 是一個(gè)定長(zhǎng)類(lèi)型(在編譯器類(lèi)型長(zhǎng)度是已知的),那么編譯器也會(huì)嘗試將 T 從定長(zhǎng)類(lèi)型轉(zhuǎn)為不定長(zhǎng)類(lèi)型,例如將 [i32; 2] 轉(zhuǎn)為
[i32]
- 若還是不行,那么調(diào)用失敗
因此點(diǎn)操作符的背后是按照 值方法調(diào)用->引用方法調(diào)用->解引用方法調(diào)用->其它 的順序來(lái)進(jìn)行調(diào)用的。下面是一個(gè)例子:
fn do_stuff<T: Clone>(value: &T) { let cloned = value.clone(); }
上面例子中 cloned 的類(lèi)型是什么?首先編譯器檢查能不能進(jìn)行值方法調(diào)用, value 的類(lèi)型是 &T,同時(shí) clone 方法的簽名也是 &T : fn clone(&T) -> T,因此可以進(jìn)行值方法調(diào)用,再加上編譯器知道了 T 實(shí)現(xiàn)了 Clone,因此 cloned 的類(lèi)型是 T。
如果 T: Clone 的特征約束被移除呢?
fn do_stuff<T>(value: &T) { let cloned = value.clone(); }
首先,從直覺(jué)上來(lái)說(shuō),該方法會(huì)報(bào)錯(cuò),因?yàn)?T 沒(méi)有實(shí)現(xiàn) Clone 特征,但是真實(shí)情況是什么呢?
我們先來(lái)推導(dǎo)一番。 首先通過(guò)值方法調(diào)用就不再可行,因?yàn)?T 沒(méi)有實(shí)現(xiàn) Clone 特征,也就無(wú)法調(diào)用 T 的 clone 方法。接著編譯器嘗試引用方法調(diào)用,此時(shí) T 變成 &T,在這種情況下, clone 方法的簽名如下: fn clone(&&T) -> &T,接著我們現(xiàn)在對(duì) value 進(jìn)行了引用。 編譯器發(fā)現(xiàn) &T 實(shí)現(xiàn)了 Clone 類(lèi)型(所有的引用類(lèi)型都可以被復(fù)制,因?yàn)槠鋵?shí)就是復(fù)制一份地址),因此可以推出 cloned 也是 &T 類(lèi)型。
最終,我們復(fù)制出一份引用指針,這很合理,因?yàn)橹殿?lèi)型 T 沒(méi)有實(shí)現(xiàn) Clone,只能去復(fù)制一個(gè)指針了。
下面是一個(gè)更復(fù)雜的例子:
#[derive(Clone)] struct Container<T>(Arc<T>); fn clone_containers<T>(foo: &Container<i32>, bar: &Container<T>) { let foo_cloned = foo.clone(); let bar_cloned = bar.clone(); }
上面代碼中,Container<i32>
實(shí)現(xiàn)了 Clone 特征,因此編譯器可以直接進(jìn)行值方法調(diào)用,此時(shí)相當(dāng)于直接調(diào)用 foo.clone,其中 clone 的函數(shù)簽名是 fn clone(&T) -> T,由此可以看出 foo_cloned 的類(lèi)型是 Container<i32>
。
然而,bar_cloned 的類(lèi)型卻是 &Container<T>
。這是因?yàn)閐erive 宏最終生成的代碼大概如下所示:
impl<T> Clone for Container<T> where T: Clone { fn clone(&self) -> Self { Self(Arc::clone(&self.0)) } }
從上面代碼可以看出,派生 Clone 能實(shí)現(xiàn)的根本是 T 實(shí)現(xiàn)了Clone特征:where T: Clone, 因此 Container<T>
就沒(méi)有實(shí)現(xiàn) Clone 特征。
編譯器接著會(huì)去嘗試引用方法調(diào)用,此時(shí) &Container<T>
引用實(shí)現(xiàn)了 Clone,最終可以得出 bar_cloned 的類(lèi)型是 &Container<T>
。
當(dāng)然,也可以為 Container<T>
手動(dòng)實(shí)現(xiàn) Clone 特征:
impl<T> Clone for Container<T> { fn clone(&self) -> Self { Self(Arc::clone(&self.0)) } }
此時(shí),編譯器首次嘗試值方法調(diào)用即可通過(guò),因此 bar_cloned 的類(lèi)型變成 Container<T>
。
參考資料
到此這篇關(guān)于rust類(lèi)型轉(zhuǎn)換的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)rust類(lèi)型轉(zhuǎn)換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust在寫(xiě)庫(kù)時(shí)實(shí)現(xiàn)緩存的操作方法
Moka是一個(gè)用于Rust的高性能緩存庫(kù),它提供了多種類(lèi)型的緩存數(shù)據(jù)結(jié)構(gòu),包括哈希表、LRU(最近最少使用)緩存和?支持TTL(生存時(shí)間)緩存,這篇文章給大家介紹Rust在寫(xiě)庫(kù)時(shí)實(shí)現(xiàn)緩存的相關(guān)知識(shí),感興趣的朋友一起看看吧2024-01-01Rust字符串字面值的一些經(jīng)驗(yàn)總結(jié)
字符串有兩種表現(xiàn)形式,一種是基本類(lèi)型,表示字符串的切片,以&str表示,另一種是可變的string類(lèi)型,下面這篇文章主要給大家介紹了關(guān)于Rust字符串字面值的相關(guān)資料,需要的朋友可以參考下2022-04-04如何使用rust實(shí)現(xiàn)簡(jiǎn)單的單鏈表
實(shí)現(xiàn)單鏈表在別的語(yǔ)言里面可能是一件簡(jiǎn)單的事情,單對(duì)于Rust來(lái)說(shuō),絕對(duì)不簡(jiǎn)單,下面這篇文章主要給大家介紹了關(guān)于如何使用rust實(shí)現(xiàn)簡(jiǎn)單的單鏈表的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03Rust 入門(mén)之函數(shù)和注釋實(shí)例詳解
這篇文章主要為大家介紹了Rust 入門(mén)之函數(shù)和注釋實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08Rust 中的閉包之捕獲環(huán)境的匿名函數(shù)
這篇文章介紹了Rust編程語(yǔ)言中的閉包,包括閉包的定義、使用、捕獲環(huán)境中的變量、類(lèi)型推斷與注解、與函數(shù)的比較以及實(shí)際應(yīng)用,閉包具有捕獲環(huán)境、類(lèi)型推斷和高效性等特性,是Rust中一個(gè)非常強(qiáng)大的工具,感興趣的朋友一起看看吧2025-02-02Rust?use關(guān)鍵字妙用及模塊內(nèi)容拆分方法
這篇文章主要介紹了Rust?use關(guān)鍵字妙用|模塊內(nèi)容拆分,文中還給大家介紹use關(guān)鍵字的習(xí)慣用法,快速引用自定義模塊內(nèi)容或標(biāo)準(zhǔn)庫(kù),以此優(yōu)化代碼書(shū)寫(xiě),需要的朋友可以參考下2022-09-09