Rust 技巧:ok_or_else

考慮下面的代碼:

use std::collections::HashMap;

fn lookup(map: &HashMap<String, i32>, key: &str) -> Result<i32, String> {
    map.get(key).copied().ok_or(format!("key {} not found", key))
}

fn main() {
    let mut map = HashMap::new();
    map.insert("x".to_string(), 1);
    assert_eq!(lookup(&map, "x"), Ok(1));
    assert_eq!(lookup(&map, "y"), Err("key y not found".to_string()));
}

你能發現代碼中的任何問題嗎?如果找不到問題,讓我們來看一些提示:比起即刻求值,更喜歡惰性求值。

在大多數編程語言中,函數的參數在傳遞給函數之前都會被求值。這意味着,無論 Option 是 Some 還是 None,都會對 Option::ok_or() 的參數求值。在我們的例子中,它是 format!(…) 宏,這是相當昂貴的,因爲它需要動態地爲字符串分配內存。

換句話說,我們直接調用 format!(…) 宏,而不考慮 Option 的值,這是即刻求值。當 Option 爲 Some 時,這是浪費和不必要的,使 lookup() 函數效率低下。爲了提高效率,我們可以使用 if let Some()…else 語句,或者使用 Result::ok_or_else() 方法。

fn lookup(map: &HashMap<String, i32>, key: &str) -> Result<i32, String> {
    // map.get(key).copied().ok_or(format!("key {} not found", key))
    map.get(key).copied().ok_or_else(|| format!("key {} not found", key))
}

Result::ok_or() 和 Result::ok_or_else() 方法的區別在於,前者是即刻求值,而後者是惰性求值。爲了實現延遲求值,它必須接受函數或閉包作爲參數,而不是一個值。

Rust 中還有很多這樣的變體,比如 Result::or() / Result::or_else() 或者 Option::or()  / Option::or_else()。通常,這些惰性版本通常都會在後面添加一個_else 後綴。更重要的是,如果參數是一個值,則必須立即的求值,而如果它是一個函數,則可以惰性的求值。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/TJfbqeTAXH29S899FnTrmA