Rust 中的類型轉換


1. as 運算符

as 運算符有點像 C 中的強制類型轉換,區別在於,它只能用於原始類型(i32i64f32
f64u8u32char 等類型),並且它是安全的

在 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;
  • 為什么它是安全的?

    嘗試以下代碼:

    let b = 1i32 as char;
    

    編譯器錯誤:

    error[E0604]: only `u8` can be cast as `char`, not `i32`
    --> src\main.rs:2:13
        |
    2   |     let b = 1i32 as char;
        |             ^^^^^^^^^^^^
    

    可見在不相關的類型之間,Rust 會拒絕轉換,這也避免了運行時錯誤。

2. Trait From<T>Into<T>

上文說到,as 運算符之能在原始類型之間進行轉換,那么對於 Struct 和 Enum 這樣的類型該如何進行轉換呢? 這就是我們這節的內容 From<T>Into<T>

先來看一看這兩個 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塊必須與類型本身存在於同一個模塊中。

    來自 F001 https://zhuanlan.zhihu.com/p/21568827

    顯然, 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 方法進行轉換。

3. 解引用強制多態

這次先看一個例子:

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 類型。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM