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