1. 程式人生 > 實用技巧 >[翻譯] Rust 的 Api 設計 (AsRef, Into, Cow)

[翻譯] Rust 的 Api 設計 (AsRef, Into, Cow)

原文地址

函式引數過載

Rust 不支援函式引數過載, 但是我們可以使用內建的 trait 來實現類似的功能. 它們就是 AsRefInto.

AsRef (和 AsMut)

AsRef 允許呼叫函式時提供的引數型別不一樣 - 只要函式接受的是一個引用型別, 而傳入的型別能夠轉成改引用型別. AsMut 實現相同功能只不過是對 mut 引用.

一個例子:

fn func1(p1: PathBuf);
fn func2(p1: &Path);

fn func3<S: AsRef<Path>>(p1: S) {
      let p1 = p1.as_ref();
      ...
}
  • func1 可以傳遞型別 PathBuf 的引數
  • func2 可以傳遞型別為 Path 或者 &PathBuf
  • func3 可以傳遞型別為 Path 或者 &PathBuf, 或者任意可以 borrow 一個 &Path 的型別, 比如說, String

func3 函式在使用時更靈活.

考慮如下 func4:

fn func4<S: AsRef<Path>>(p1: S, p2: S);

該函式定義限制了 p1p2 必須為相同型別, 如下更改可以使其更靈活:

fn func4<N: AsRef<Path>, M: AsRef<Path>>(p1: N, p2: M);

AsRef

一個常見的用例是將值轉換成(靜態)字串, 比如 enum 值. 我們可以直接使用 AsRef 來實現:

impl AsRef<str> for XmlDoc {
      fn as_ref(&self) -> &str {
            XmlDoc::Unknown => "Unknown",
            XmlDoc::None => "None",
            XmlDoc::Debug => "Debug",
            XmlDoc::Release => "Release",
            XmlDoc::Both => "Both",
      }
}

AsRef 反模式

AsRef 適用於只需要傳參的引用時使用. 如果你發現引數所有權時, 比如

let x = param.as_ref().to_owned();

那麼你應該使用 Into triat.

Into

Into 類似於 AsRef, 可以讓我們在呼叫函式時傳參更靈活. 不同的是使用 Into 時需要引數的所有權. 我們經常在建構函式中使用:

fn new<S: Into<String>>(model: S) -> Self;

為自定義型別實現 Into

不要為自定義的型別實現 Into. 轉而使用 From 實現來, 這樣我們就可以使用標準庫中 Into 的預設實現了.
如果你的建構函式只有一個型別為 T的引數, 那麼我強烈建議你不要建構函式使用 From<T> 代替.

impl<P> From<P> for SolutionDirectory
where P: Into<PathBuf>
{
    fn from(sln_directory: P) -> Self {
        SolutionDirectory {
            directory: sln_directory.into(),
            solutions: vec![]
        }
    }
}

let s: SolutionDirectory = "somepath".into();

Tips

  • 標準庫 std::fs 中的函式, 如果接收引數型別為 AsRef<Path>, 那麼可以傳參型別 PathBuf, Path, String, &str, OsString, OsStr 以及其他.
  • Into<String> 可以接收常用字串型別以及 Box<str>, Rc<String>, Cow<str> 等.
  • 上述 trait 不能產生錯誤或異常. 如果需要可以參考 TryFromTryInto.

Cow 作為返回值

Cow 型別可以延遲記憶體分配. 該型別經常出現在返回值位置(該型別實現了 Deref, 因此輸入引數不需要使用該型別, 直接用 &T 就可以了).

use std::borrow::Cow;

/// 返回 Cow::Borrowed 型別, 靜態生命週期
fn func5() -> Cow<'static, str> {
    "".into()
}

/// 返回 Cow::Owned, 靜態生命週期
fn func6() -> Cow<'static, str> {
    let s = "s".to_owned();
    s.into()
}

/// 返回 Cow::Borrowed, 並且生命週期和 'data' 相同.
fn func7(data: &str) -> Cow<str> {
    Cow::Borrowed(&data[0..1])
}

// Ditto.
fn func8(data: &str) -> Cow<str> {
    data[0..1].into()
}

fn main() {
    match func8("hello") {
        Cow::Borrowed(_) => println!("It's borrowed"),
        Cow::Owned(_) => println!("Owned!"),
    }
}

參考文獻