as
運算符as
運算符有點像 C 中的強制類型轉換,區別在於,它只能用於原始類型(i32
、i64
、f32
、
f64
、 u8
、 u32
、 char
等類型),而且它是安全的。安全
例app
在 Rust 中,不一樣的數值類型是不能進行隱式轉換的,好比:函數
let b: i64 = 1i32;
會出現編譯錯誤,提示沒法進行類型轉換。設計
error[E0308]: mismatched types --> src\main.rs:2:18 | 2 | let b: i64 = 1i32; | ^^^^ expected i64, found i32 help: change the type of the numeric literal from `i32` to `i64`
這時可使用as 進行轉換。指針
let b: i64 = 1i32 as i64;
爲何它是安全的?code
嘗試如下代碼:開發
let b = 1i32 as char;
編譯器錯誤:get
error[E0604]: only `u8` can be cast as `char`, not `i32` --> src\main.rs:2:13 | 2 | let b = 1i32 as char; | ^^^^^^^^^^^^
可見在不相關的類型之間,Rust 會拒絕轉換,這也避免了運行時錯誤。編譯器
From<T>
和 Into<T>
上文說到,as
運算符之能在原始類型之間進行轉換,那麼對於 Struct 和 Enum 這樣的類型該如何進行轉換呢? 這就是咱們這節的內容 From<T>
和 Into<T>
。string
先來看一看這兩個 Trait 的結構。
pub trait From<T> { fn from(T) -> Self; } pub trait Into<T> { fn into(self) -> T; }
很簡單,From<T>
有一個 from
方法,Into<T>
有一個 into
方法。
通常來講,咱們應該儘可能優先選擇實現 From<T>
而不是 Into<T>
,由於當你爲 U
實現 From<T>
,這意味着你同時也爲 T
隱式實現了 Into<U>
。
來看個例子
fn main() { println!("Hello, world!"); let b: Complex = 1.into(); println!("{:?}", b); } #[derive(Debug)] struct Complex { re: i32, im: i32 } impl From<i32> for Complex{ fn from(re: i32) -> Self { Complex{ re, im:0 } } }
當我爲 Complex
實現 From<i32>
後,我也能夠在 i32
上使用 into
方法,轉換到 Complex
。
原始類型實現了與 as
轉換相對應的 From<T>
與 Into<T>
。
當你爲 U
實現 From<T>
以後,你要確保這個轉換必定能成功,如如有失敗的可能,你應該選擇爲 U
實現 TryFrom<T>
。
何時該使用 Into<T>
Into<T>
被設計出來,總有該用到的地方。那何時該使用呢?
先複習一下 Rust 中的 孤兒原則
在聲明trait和impl trait的時候,Rust規定了一個Orphan Rule(孤兒規則):impl塊要麼與trait的聲明在同一個的crate中,要麼與類型的聲明在同一個crate中。
也就是說,不能在一個crate中,針對一個外部的類型,實現一個外部的trait。
由於在其它的crate中,一個類型沒有實現一個trait,極可能是有意的設計。
若是咱們在使用其它的crate的時候,強行把它們「拉郎配」,是會製造出bug的。
好比說,咱們寫了一個程序,引用了外部庫lib1和lib2,lib1中聲明瞭一個trait T,lib2中聲明瞭一個struct S ,咱們不能在本身的程序中針對S實現T。
這也意味着,上游開發者在給別人寫庫的時候,尤爲要注意。
一些比較常見的標準庫中的 trait,好比 Display Debug ToString Default 等,應該儘量地提供好。
不然,使用這個庫的下游開發者,是沒辦法幫咱們把這些 trait 實現的。
同理,若是是匿名impl,那麼這個impl塊必須與類型自己存在於同一個模塊中。
顯然, From<T>
不屬於當前 crate ,當你要實現當前 crate 中的類型 T
轉換到其餘 crate 中的類型 U
時,若是選擇爲 U
實現 From<T>
,因爲孤兒原則,編譯器會阻止你這麼作。這時咱們就能夠選擇爲 T
實現 Into<U>
。
注意,和 From<T>
不一樣,實現 Into<U>
以後並不會隱式實現 From<T>
,這點需特別注意。
From<T>
的妙用
回憶一下 Rust 的 ?
操做符,它被用於 返回值爲 Result<T,E>
或者 Option<T>
的函數。回想一下,它是如何處理 Err(E)
的。
fn apply() -> Result<i32,i32> { Err(1) } fn main() -> Result<(),i64> { let a = apply()?; Ok(()) }
上面的例子是能夠經過編譯的,既然 Rust 中的數值類型是不能隱式轉換的,那麼,當返回 Err(i32)
時是如何轉換到 Err(i64)
的呢?這實際上是一個 Rust 的語法糖。展開後的代碼相似於下面:
fn apply() -> Result<i32,i32> { Err(1) } fn main() -> Result<(),i64> { let a = match apply() { Ok(v) => v, Err(e) => return Err(i64::from(e)), }; Ok(()) }
也就是說,Rust 會自動調用目標類 from
方法進行轉換。
此次先看一個例子:
fn print(message: &str) { println!("{}",message); } fn main() { let message: String = "message".to_string(); print(&message); }
print
的形參是 &str
類型,然而在 main
中,我傳遞倒是一個 &String
類型的實參。明顯,這兩個類型不相同!!Rust 爲何會經過這樣的代碼呢?
沒錯,這就是 Rust 的 解引用強制多態。
首先,須要瞭解一個 Deref
Trait 。
#[lang = "deref"] pub trait Deref { type Target: ?Sized; #[must_use] fn deref(&self) -> &Self::Target; }
deref
方法返回一個 &Target
類型的引用。
回憶一下 Rust 中的解引用語法,當 ref
是一個引用或智能指針時,咱們可使用 *ref
的方式解引用。這是相似一個語法糖,對於 *ref
這種寫法,寫全應該時 *(ref.deref())
。
回想 Box<T>
的使用,Box<T>
實現了 Deref
,它的 deref
方法返回 &T
的引用,而後使用解引用運算符 *
,咱們順利拿到一個 T
類型的數據。也就是,你能夠經過實現 Deref
以重載解引用運算符。
Deref
和這節的內容有什麼關係呢?
當 T
實現了 Deref<Target=U>
時,對於須要 &U
的地方,你能夠提供一個 &T
類型的數據,Rust會爲你自動調用 deref
方法,而這個過程能夠重複屢次。
好比,我自定義類型 P
實現了 Deref<Target=String>
,那麼能夠把 &P
類型變量傳遞給一個 &str
類型變量。&P -> &String -> &str
,僞代碼: &P.deref().deref()
。
回到這節開頭的例子,print(&message)
至關於 print((&message).deref())
,正好是一個 &str
類型。