學習Rust泛型與特性

安全劍客發表於2020-12-28
Rust 語言是一種高效、可靠的通用高階語言。其高效不僅限於開發效率,它的執行效率也是令人稱讚的,是一種少有的兼顧開發效率和執行效率的語言。

學習Rust泛型與特性學習Rust泛型與特性
泛型是一個程式語言不可或缺的機制。
C++ 語言中用"模板"來實現泛型,而 C 語言中沒有泛型的機制,這也導致 C 語言難以構建型別複雜的工程。
泛型機制是程式語言用於表達型別抽象的機制,一般用於功能確定、資料型別待定的類,如連結串列、對映表等。

在函式中定義泛型

這是一個對整型數字選擇排序的方法:
例項

fn max(array: &[i32]) -> i32 {
    let mut max_index = 0;
    let mut i = 1;
    while i < array.len() {
        if array[i] > array[max_index] {
            max_index = i;
        }
        i += 1;
    }
    array[max_index]
}
fn main() {
    let a = [2, 4, 6, 3, 1];
    println!("max = {}", max(&a));
}

執行結果:

max = 6

這是一個簡單的取最大值程式,可以用於處理 i32 數字型別的資料,但無法用於 f64 型別的資料。透過使用泛型我們可以使這個函式可以利用到各個型別中去。但實際上並不是所有的資料型別都可以比大小,所以接下來一段程式碼並不是用來執行的,而是用來描述一下函式泛型的語法格式:

例項

fn max(array: &[T]) -> T {
    let mut max_index = 0;
    let mut i = 1;
    while i < array.len() {
        if array[i] > array[max_index] {
            max_index = i;
        }
        i += 1;
    }
    array[max_index]
}
結構體與列舉類中的泛型

在之前我們學習的 Option 和 Result 列舉類就是泛型的。

Rust 中的結構體和列舉類都可以實現泛型機制。

struct Point{
    x: T,
    y: T
}

這是一個點座標結構體,T 表示描述點座標的數字型別。我們可以這樣使用:

let p1 = Point {x: 1, y: 2};
let p2 = Point {x: 1.0, y: 2.0};

使用時並沒有宣告型別,這裡使用的是自動型別機制,但不允許出現型別不匹配的情況如下:

let p = Point {x: 1, y: 2.0};

x 與 1 繫結時就已經將 T 設定為 i32,所以不允許再出現 f64 的型別。如果我們想讓 x 與 y 用不同的資料型別表示,可以使用兩個泛型識別符號:

struct Point{
    x: T1,
    y: T2
}

在列舉類中表示泛型的方法諸如 Option 和 Result:

enum Option{
    Some(T),
    None,
}
enum Result{
    Ok(T),
    Err(E),
}

結構體與列舉類都可以定義方法,那麼方法也應該實現泛型的機制,否則泛型的類將無法被有效的方法操作。

例項

struct Point{
    x: T,
    y: T,
}
implPoint{
    fn x(&self) -> &T {
        &self.x
    }
}
fn main() {
    let p = Point { x: 1, y: 2 };
    println!("p.x = {}", p.x());
}

執行結果:

p.x = 1

注意,impl 關鍵字的後方必須有 ,因為它後面的 T 是以之為榜樣的。但我們也可以為其中的一種泛型新增方法:

impl Point{
    fn x(&self) -> f64 {
        self.x
    }
}

impl 塊本身的泛型並沒有阻礙其內部方法具有泛型的能力:

implPoint{
    fn mixup(self, other: Point) -> Point{
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

方法 mixup 將一個 Point 點的 x 與 Point 點的 y 融合成一個型別為 Point 的新點。

特性

特性(trait)概念接近於 Java 中的介面(Interface),但兩者不完全相同。特性與介面相同的地方在於它們都是一種行為規範,可以用於標識哪些類有哪些方法。

特性在 Rust 中用 trait 表示:

trait Descriptive {
    fn describe(&self) -> String;
}

Descriptive 規定了實現者必須有是 describe(&self) -> String 方法。

我們用它實現一個結構體:

例項

struct Person {
    name: String,
    age: u8
}
impl Descriptive for Person {
    fn describe(&self) -> String {
        format!("{} {}", self.name, self.age)
    }
}

格式是:

impl <特性名> for <所實現的型別名>

Rust 同一個類可以實現多個特性,每個 impl 塊只能實現一個。

預設特性

這是特性與介面的不同點:介面只能規範方法而不能定義方法,但特性可以定義方法作為預設方法,因為是"預設",所以物件既可以重新定義方法,也可以不重新定義方法使用預設的方法:

例項

trait Descriptive {
    fn describe(&self) -> String {
        String::from("[Object]")
    }
}
struct Person {
    name: String,
    age: u8
}
impl Descriptive for Person {
    fn describe(&self) -> String {
        format!("{} {}", self.name, self.age)
    }
}
fn main() {
    let cali = Person {
        name: String::from("Cali"),
        age: 24
    };
    println!("{}", cali.describe());
}

執行結果:

Cali 24

如果我們將 impl Descriptive for Person 塊中的內容去掉,那麼執行結果就是:

[Object]
特性做引數

很多情況下我們需要傳遞一個函式做引數,例如回撥函式、設定按鈕事件等。在 Java 中函式必須以介面實現的類例項來傳遞,在 Rust 中可以透過傳遞特性引數來實現:

fn output(object: impl Descriptive) {
    println!("{}", object.describe());
}

任何實現了 Descriptive 特性的物件都可以作為這個函式的引數,這個函式沒必要了解傳入物件有沒有其他屬性或方法,只需要瞭解它一定有 Descriptive 特性規範的方法就可以了。當然,此函式內也無法使用其他的屬性與方法。

特性引數還可以用這種等效語法實現:

fn output(object: T) {
    println!("{}", object.describe());
}

這是一種風格類似泛型的語法糖,這種語法糖在有多個引數型別均是特性的情況下十分實用:

fn output_two(arg1: T, arg2: T) {
    println!("{}", arg1.describe());
    println!("{}", arg2.describe());
}

特性作型別表示時如果涉及多個特性,可以用 + 符號表示,例如:

fn notify(item: impl Summary + Display)
fn notify(item: T)

注意:僅用於表示型別的時候,並不意味著可以在 impl 塊中使用。

複雜的實現關係可以使用 where 關鍵字簡化,例如:

fn some_function(t: T, u: U)

可以簡化成:

fn some_function(t: T, u: U) -> i32
    where T: Display + Clone,
          U: Clone + Debug

在瞭解這個語法之後,泛型章節中的"取最大值"案例就可以真正實現了:

例項

trait Comparable {
    fn compare(&self, object: &Self) -> i8;
}
fn max(array: &[T]) -> &T {
    let mut max_index = 0;
    let mut i = 1;
    while i < array.len() {
        if array[i].compare(&array[max_index]) > 0 {
            max_index = i;
        }
        i += 1;
    }
    &array[max_index]
}
impl Comparable for f64 {
    fn compare(&self, object: &f64) -> i8 {
        if &self > &object { 1 }
        else if &self == &object { 0 }
        else { -1 }
    }
}
fn main() {
    let arr = [1.0, 3.0, 5.0, 4.0, 2.0];
    println!("maximum of arr is {}", max(&arr));
}

執行結果:

maximum of arr is 5

Tip: 由於需要宣告 compare 函式的第二引數必須與實現該特性的型別相同,所以 Self (注意大小寫)關鍵字就代表了當前型別(不是例項)本身。

特性做返回值

特性做返回值格式如下:

例項

fn person() -> impl Descriptive {
    Person {
        name: String::from("Cali"),
        age: 24
    }
}

但是有一點,特性做返回值只接受實現了該特性的物件做返回值且在同一個函式中所有可能的返回值型別必須完全一樣。比如結構體 A 與結構體 B 都實現了特性 Trait,下面這個函式就是錯誤的:

例項

fn some_function(bool bl) -> impl Descriptive {
    if bl {
        return A {};
    } else {
        return B {};
    }
}
有條件實現方法

impl 功能十分強大,我們可以用它實現類的方法。但對於泛型類來說,有時我們需要區分一下它所屬的泛型已經實現的方法來決定它接下來該實現的方法:

struct A{}
implA{
    fn d(&self) {}
}

這段程式碼宣告瞭 A 型別必須在 T 已經實現 B 和 C 特性的前提下才能有效實現此 impl 塊。

原文地址:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559985/viewspace-2745775/,如需轉載,請註明出處,否則將追究法律責任。

相關文章