Rust 泛型之 Trait

假設你買了一個網絡攝像頭,通過 USB 接口連接到你的電腦。現在,假設你又買了一個外接硬盤,也是通過 USB 接口連接到你的電腦上。這就是一個泛型在現實物理世界的應用,USB 接口就是一個泛型接口。需要連接到電腦的設備,只需要遵循通用的 USB 規範就可以了。

在代碼中,一個函數針對特定的類型執行特定的任務,一個泛型函數可以在一些類型上執行特定的任務。我們來看個例子:

fn add(x: i64, y: i64) -> i64 {
    x + y
}

add 函數只能對 i64 類型的值相加,我們把 add 函數改成泛型函數:

fn add<T>(x: T, y: T) -> T {
    x + y
}

這段代碼是無效的,編譯器不知道怎麼對泛型類型 T 進行相加,這就需要對泛型 T 做泛型約束:

use std::ops::Add;
fn add<T: Add<Output = T>>(x: T, y: T) -> T {
    x + y
}

在這裏,只要實現了 Add Trait 的類型,就都可以相加。

泛型 (Generic)

泛型編程的目的是提高代碼的可重用性和通過對 function、struct、trait 中的類型延遲定義來減少 bug。在實踐中,這意味着一個算法可以用於多種不同的類型,前提是它們滿足約束條件。

在 Rust 中,function、trait 和數據類型都是可以泛型的:

use std::fmt::Display;
fn generic_display<T: Display>(item: T) {
    println!("{}", item);
}
struct Point<T> {
    x: T,
    y: T,
}
struct Point2<T>(T, T);
enum Option<T> {
    Some(T),
    None,
}
fn main() {
    let a = "42";
    let b = 42;
    generic_display(a);
    generic_display(b);
    let (x, y) = (4, 2);
    let point = Point {x, y};
    // generic_display(point); // Point沒有實現Disply
}

泛型在 Rust 中應用廣泛,如果沒有它們,就不可能有 Vec、HashMap 或 BTreeSet 這樣的泛型集合:

use std::{vec, collections::HashMap};
#[derive(Debug)]
struct Contact {
    name: String,
    email: String,
}
fn main() {
    let imported_contacts = vec![
        Contact {
            name: "John".to_string(),
            email: "john@smith.com".to_string(),
        },
        Contact {
            name: "steve".to_string(),
            email: "steve@jobs.com".to_string(),
        },
        Contact {
            name: "John".to_string(),
            email: "john@smith.com".to_string(),
        },
    ];
    let unique_contacts: HashMap<String, Contact> = imported_contacts
                .into_iter()
                .map(|contact| (contact.email.clone(), contact))
                .collect();
    println!("{:?}", unique_contacts);
}

感謝泛型的強大,我們使用了標準庫的 HashMap,快速的去除了重複的數據。

特徵 (Trait)

Rust 中的 trait 等同於其他語言的 interface。

pub trait Dog {
    fn bark(&self) -> String;
}
pub struct Labrador {}
impl Dog for Labrador {
    fn bark(&self) -> String {
        "wouf".to_string()
    }
}
pub struct Husky {}
impl Dog for Husky {
    fn bark(&self) -> String {
        "Wuuuuuu".to_string()
    }
}
fn main() {
    let labrador = Labrador {};
    println!("{}", labrador.bark());
    let husky = Husky {};
    println!("{}", husky.bark());
}

這裏定義了一個狗的 trait,所有實現了 Dog trait 的類型都被認爲是一種狗。這裏體現了程序員可以通過 trait 共享行爲,即多個類型共享相同的行爲。

默認實現

可以爲 trait 提供默認的方法實現:

pub trait Hello {
    fn hello(&self) -> String {
        String::from("World")
    }
}
pub struct Sylvain {}
impl Hello for Sylvain {
    fn hello(&self) -> String {
        String::from("Sylvain")
    }
}
pub struct Anonymous {}
impl Hello for Anonymous {}
fn main() {
    let sylvain = Sylvain{};
    let anonymous = Anonymous{};
    println!("Sylvain: {}", sylvain.hello());
    println!("Anonymous: {}", anonymous.hello());
}

組合 Trait

多個 trait 可以組合在一起形成更高級的約束:

pub trait Module {
    fn name(&self) -> String;
    fn description(&self) -> String;
}
pub trait SubdomainModule {
    fn enumerate(&self, domain: &str) -> Result<Vec<String>, Error>;
}
fn enumerate_subdomains<M: Module + SubdomainModule>(module: M, target: &str) -> Vec<String> {
    // ...
}

異步 Trait

到目前爲止,異步 trait 還沒有得到 Rust 的原生支持,我們可以使用 async-trait 庫來實現異步 trait。

#[async_trait]
pub trait HttpModule: Module {
    async fn scan(
        &self,
        http_client: &Client,
        endpoint: &str,
    ) -> Result<Option<HttpFinding>, Error>;
}

泛型 Trait

trait 也支持泛型參數

use std::fmt::Display;
trait Printer<S: Display> {
    fn print(&self, to_print: S) {
        println!("{}", to_print);
    }
}
struct ActualPrinter {}
impl<S:Display, T> Printer<S> for T {}
fn main() {
    let s = "Hello";
    let n = 42;
    let ap = ActualPrinter {};
    ap.print(s);
    ap.print(n);
    n.print(s);
    s.print(n);
}

本文翻譯自:

https://kerkour.com/rust-generics-traits

coding 到燈火闌珊 專注於技術分享,包括 Rust、Golang、分佈式架構、雲原生等。

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