rust類型轉(zhuǎn)換的實(shí)現(xiàn)
Rust 是類型安全的語言,因此在 Rust 中做類型轉(zhuǎn)換不是一件簡單的事。
as轉(zhuǎn)換
Rust 不提供原生類型之間的隱式類型轉(zhuǎn)換(coercion),但可以使用 as 關(guān)鍵字進(jìn)行顯式類型轉(zhuǎn)換(casting)。例如:
fn main() { cast(); } // as 進(jìn)行的顯示類型強(qiáng)制轉(zhuǎn)換 fn cast() { let n: u8 = 123; let m: i32 = n as i32; // 將u8強(qiáng)制轉(zhuǎn)換為i32類型 println!("u8({})轉(zhuǎn)i32({})", n, m); let a = 12345; // 整型字面值常量是i32類型 let b: i8 = a as i8; // 能容納更大數(shù)值的類型i32轉(zhuǎn)容納范圍較小的i8,存在數(shù)據(jù)溢出的風(fēng)險。 println!("i32({})轉(zhuǎn)i8({})", a, b); let c = '我'; // char類型 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(); // 可變的指針類型 let mut p = y as usize; // 把指針轉(zhuǎn)為usize類型 p += 4; // 指針步進(jìn)一步(i32類型占4字節(jié),因此加4即可) y = p as *mut i32; // 將 usize轉(zhuǎn)為指針 unsafe { println!("{}", *y); // 在unsafe模塊中操作指針 } }
轉(zhuǎn)換不具有傳遞性 就算 e as U1 as U2
是合法的,也不能說明 e as U2
是合法的(e 不能直接轉(zhuǎn)換成 U2)。as轉(zhuǎn)換基本上只用于數(shù)值類型之間的轉(zhuǎn)換。而且需要注意,當(dāng)你從可以容納范圍更大的數(shù)據(jù)類型向可以容納范圍較小的數(shù)據(jù)類型轉(zhuǎn)換的時候會發(fā)生溢出,因此你要人為保證數(shù)據(jù)轉(zhuǎn)換是正確的。
into和from
From 和 Into 兩個 trait 是內(nèi)部相關(guān)聯(lián)的,實(shí)際上這是它們實(shí)現(xiàn)的一部分。如果我們能夠從類型 B 得到類型 A,那么很容易相信我們也能夠把類型 B 轉(zhuǎn)換為類型 A。
From
From trait 允許一種類型定義 “怎么根據(jù)另一種類型生成自己”,因此它提供了一種類型轉(zhuǎn)換的簡單機(jī)制。在標(biāo)準(zhǔn)庫中有無數(shù) From 的實(shí)現(xiàn),規(guī)定原生類型及其他常見類型的轉(zhuǎn)換功能。
比如,可以很容易地把 str 轉(zhuǎn)換成 String:
let s = String::from("qwert"); println!("s={s}");
也可以為我們自己的類型定義轉(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 倒過來而已。也就是說,如果你為你的類型實(shí)現(xiàn)了 From,那么同時你也就免費(fèi)獲得了 Into。
使用 Into trait 通常要求指明要轉(zhuǎn)換到的類型,因?yàn)榫幾g器大多數(shù)時候不能推斷它。不過考慮到我們免費(fèi)獲得了 Into,這點(diǎn)代價不值一提。
// 需要指明轉(zhuǎn)換到的類型是Number let a: Number = 1.into(); println!("My number is {:?}", a);
TryInto和TryFrom
類似于 From 和 Into,TryFrom 和 TryInto 是類型轉(zhuǎn)換的通用 trait。不同于 From/Into 的是,TryFrom 和 TryInto trait 用于易出錯的轉(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的類型,否則編譯器無法推斷e的類型,導(dǎo)致錯誤。 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ù)時候不適合字符串。它更需要ToString
Display
要把任何類型轉(zhuǎn)換成 String,只需要實(shí)現(xiàn)那個類型的 ToString trait。然而不要直接這么做,您應(yīng)該實(shí)現(xiàn)fmt::Display trait,它會自動提供 ToString,并且還可以用來打印類型。
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ù)字
只要對目標(biāo)類型實(shí)現(xiàn)了 FromStr trait,就可以用 parse 把字符串轉(zhuǎn)換成目標(biāo)類型。 標(biāo)準(zhǔn)庫中已經(jīng)給無數(shù)種類型實(shí)現(xiàn)了 FromStr。如果要轉(zhuǎn)換到用戶定義類型,只要手動實(shí)現(xiàn) FromStr 就行。
我們得提供要轉(zhuǎn)換到的類型,這可以通過顯示聲明類型,或者用 “渦輪魚” 語法(turbo fish,<>)實(shí)現(xiàn)。例如:
pub fn string_to_number(){ let num = "12345"; let num = num.parse::<i32>().unwrap(); // turbo fish寫法 println!("{}", num); let num = "12345"; let num: u64 = num.parse().unwrap(); // 顯示聲明類型寫法 println!("{}", num); }
點(diǎn)操作符
方法調(diào)用的點(diǎn)操作符看起來簡單,實(shí)際上非常不簡單,它在調(diào)用時,會發(fā)生很多魔法般的類型轉(zhuǎn)換,例如:自動引用、自動解引用,強(qiáng)制類型轉(zhuǎn)換直到類型能匹配等。
假設(shè)有一個方法 foo,它有一個接收器(接收器就是 self、&self、&mut self 參數(shù))。如果調(diào)用 value.foo(),編譯器在調(diào)用 foo 之前,需要決定到底使用哪個 Self 類型來調(diào)用?,F(xiàn)在假設(shè) value 擁有類型 T。再進(jìn)一步,我們使用完全限定語法來進(jìn)行準(zhǔn)確的函數(shù)調(diào)用:
- 首先,編譯器檢查它是否可以直接調(diào)用 T::foo(value),稱之為值方法調(diào)用
- 如果上一步調(diào)用無法完成(例如方法類型錯誤或者特征沒有針對 Self 進(jìn)行實(shí)現(xiàn),上文提到過特征不能進(jìn)行強(qiáng)制轉(zhuǎn)換),那么編譯器會嘗試增加自動引用,例如會嘗試以下調(diào)用:
<&T>::foo(value)
和<&mut T>::foo(value)
,稱之為引用方法調(diào)用 - 若上面兩個方法依然不工作,編譯器會試著解引用 T ,然后再進(jìn)行嘗試。這里使用了 Deref 特征 —— 若
T: Deref<Target = U>
(T 可以被解引用為 U),那么編譯器會使用 U 類型進(jìn)行嘗試,稱之為解引用方法調(diào)用 - 若 T 不能被解引用,且 T 是一個定長類型(在編譯器類型長度是已知的),那么編譯器也會嘗試將 T 從定長類型轉(zhuǎn)為不定長類型,例如將 [i32; 2] 轉(zhuǎn)為
[i32]
- 若還是不行,那么調(diào)用失敗
因此點(diǎn)操作符的背后是按照 值方法調(diào)用->引用方法調(diào)用->解引用方法調(diào)用->其它 的順序來進(jìn)行調(diào)用的。下面是一個例子:
fn do_stuff<T: Clone>(value: &T) { let cloned = value.clone(); }
上面例子中 cloned 的類型是什么?首先編譯器檢查能不能進(jìn)行值方法調(diào)用, value 的類型是 &T,同時 clone 方法的簽名也是 &T : fn clone(&T) -> T,因此可以進(jìn)行值方法調(diào)用,再加上編譯器知道了 T 實(shí)現(xiàn)了 Clone,因此 cloned 的類型是 T。
如果 T: Clone 的特征約束被移除呢?
fn do_stuff<T>(value: &T) { let cloned = value.clone(); }
首先,從直覺上來說,該方法會報錯,因?yàn)?T 沒有實(shí)現(xiàn) Clone 特征,但是真實(shí)情況是什么呢?
我們先來推導(dǎo)一番。 首先通過值方法調(diào)用就不再可行,因?yàn)?T 沒有實(shí)現(xiàn) Clone 特征,也就無法調(diào)用 T 的 clone 方法。接著編譯器嘗試引用方法調(diào)用,此時 T 變成 &T,在這種情況下, clone 方法的簽名如下: fn clone(&&T) -> &T,接著我們現(xiàn)在對 value 進(jìn)行了引用。 編譯器發(fā)現(xiàn) &T 實(shí)現(xiàn)了 Clone 類型(所有的引用類型都可以被復(fù)制,因?yàn)槠鋵?shí)就是復(fù)制一份地址),因此可以推出 cloned 也是 &T 類型。
最終,我們復(fù)制出一份引用指針,這很合理,因?yàn)橹殿愋?T 沒有實(shí)現(xiàn) Clone,只能去復(fù)制一個指針了。
下面是一個更復(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)用,此時相當(dāng)于直接調(diào)用 foo.clone,其中 clone 的函數(shù)簽名是 fn clone(&T) -> T,由此可以看出 foo_cloned 的類型是 Container<i32>
。
然而,bar_cloned 的類型卻是 &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>
就沒有實(shí)現(xiàn) Clone 特征。
編譯器接著會去嘗試引用方法調(diào)用,此時 &Container<T>
引用實(shí)現(xiàn)了 Clone,最終可以得出 bar_cloned 的類型是 &Container<T>
。
當(dāng)然,也可以為 Container<T>
手動實(shí)現(xiàn) Clone 特征:
impl<T> Clone for Container<T> { fn clone(&self) -> Self { Self(Arc::clone(&self.0)) } }
此時,編譯器首次嘗試值方法調(diào)用即可通過,因此 bar_cloned 的類型變成 Container<T>
。
參考資料
到此這篇關(guān)于rust類型轉(zhuǎn)換的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)rust類型轉(zhuǎn)換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust字符串字面值的一些經(jīng)驗(yàn)總結(jié)
字符串有兩種表現(xiàn)形式,一種是基本類型,表示字符串的切片,以&str表示,另一種是可變的string類型,下面這篇文章主要給大家介紹了關(guān)于Rust字符串字面值的相關(guān)資料,需要的朋友可以參考下2022-04-04Rust 中的閉包之捕獲環(huán)境的匿名函數(shù)
這篇文章介紹了Rust編程語言中的閉包,包括閉包的定義、使用、捕獲環(huán)境中的變量、類型推斷與注解、與函數(shù)的比較以及實(shí)際應(yīng)用,閉包具有捕獲環(huán)境、類型推斷和高效性等特性,是Rust中一個非常強(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)庫,以此優(yōu)化代碼書寫,需要的朋友可以參考下2022-09-09