從錯誤處理看 Rust 的語言和 Go 語言的設計

王舉發表於2019-06-19

golang 對比

先用go語言做一個對比,比如我們要將兩個字串轉換為數字後返回結果,如果使用golang,則實現方式如下:

func multiply(first_number string, second_number string) (result int, err error) {
    var (
        first  int
        second int
    )
    first, err = strconv.Atoi(first_number)
    if err != nil {
        return
    }

    second, err = strconv.Atoi(second_number)
    if err != nil {
        return
    }
    result = first * second
    return
}

可以看到,這也是go語言最常用的錯誤處理機制,如果捕獲到錯誤,則將錯誤返回,否則返回正常的結果。這樣處理錯誤的結果就是:每一個錯誤都要進行人肉處理,怎麼很多的冗餘性程式碼,如果程式碼量很大的話,會寫很多冗餘的程式碼,很不優雅。

rust 實現

基礎版本

現在,使用Rust進行實現同樣的邏輯:

fn multiply(first_number_str: &str, second_number_str: &str) -> i32 {
    let first_number = first_number_str.parse::<i32>().unwrap();
    let second_number = second_number_str.parse::<i32>().unwrap();
    first_number * second_number
}

以上程式碼相比golang的程式碼,並沒有多少現金的地方。因為程式在解析到程式異常的時候,直接終止程式。這樣在專案開發中是很不友好的,但是,這個只是最粗糙的實現方式,我們使用match進行優化它:

match

use std::num::ParseIntError;
type AliasResult<T> = Result<T, ParseIntError>;

#[allow(dead_code)]
fn multiply1(first_number_str: &str, second_number_str: &str) -> AliasResult<i32> {
    match first_number_str.parse::<i32>() {
        Err(e) => Err(e),
        Ok(first_number) => match second_number_str.parse::<i32>() {
            Err(e) => Err(e),
            Ok(second_number) => Ok(first_number * second_number)
        }
    }
}

這個和golang相比,實現不相上下。都是檢測到異常就返回異常,如果止步於此,那麼,rust只能和golang打成平手。但是,rust還提供的函式式的結果處理機制:

使用 map 和 and_then 實現

#[allow(dead_code)]
fn multiply2(first_number_str: &str, second_number_str: &str) -> AliasResult<i32> {
    first_number_str.parse::<i32>().and_then(|first_number| {
        second_number_str.parse::<i32>().map(|second_number| first_number * second_number)
    })
}

可以看到,通過運算元的方式,rust可以通過兩行程式碼就可以處理想要的結果,這裡已經是相對比較好的實現了。但是,相比之前的程式碼,不過不了解函數語言程式設計,可能相對難懂。那麼我們有沒有折中的實現呢

通用折中的實現

#[allow(dead_code)]
fn multiply3(first_number_str: &str, second_number_str: &str) -> AliasResult<i32> {
    let first_number = match first_number_str.parse::<i32>() {
        Ok(first_number) => first_number,
        Err(e) => return Err(e)
    };

    let second_number = match second_number_str.parse::<i32>() {
        Ok(second_number) => second_number,
        Err(e) => return Err(e)
    };

    Ok(first_number * second_number)
}

以上實現,相對來說清晰易讀,但是,有沒有一種方式,能將重複的錯誤處理簡化掉呢,答案是有:

#[allow(dead_code)]
fn multiply4(first_number_str: &str, second_number_str: &str) -> AliasResult<i32> {
    let first_number = first_number_str.parse::<i32>()?;
    let second_number = second_number_str.parse::<i32>()?;

    Ok(first_number * second_number)
}

以上實現,相比之前第一個最粗糙的實現,只是多了兩個?.但是,他依然做了最安全的錯誤處理,同時,相比於golang,程式碼量成倍的減少,並且程式碼的易讀性也相當的高,同時,相比程式碼的執行效率,rust也是完勝golang的。所以,擁抱rust吧~~

相關文章