English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

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

これはシンプルな最大値取得プログラムで、iの処理に使用できます。32 数字のデータ型ですが、fに使用することはできません。64 データの種類です。泛型を使用することで、この関数が様々なデータ型に利用できるようになりますが、実際にはすべてのデータ型が比較できるわけではありません。したがって、次の一段のコードは実行するためではなく、関数の泛型の文法形式を説明するためです:

fn max<T>(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]
}

構造体とエnumクラスの汎型

先ほど学んだ Option と Result エnumクラスは汎型です。

Rust では構造体とエnumクラスは汎型メカニズムを実現できます。

struct Point<T> {
    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 が i に設定されています32、そのため f は再び出現することはできません64 の型です。x と y を異なるデータ型で表現したい場合、2つの汎型識別子を使用できます:

struct Point<T1,2> {
    x: T1,
    y: T2
}

エnumクラス内で汎型を表す方法として Option と Result があります:

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

構造体とエnumクラスはメソッドを定義できるので、メソッドも汎型のメカニズムを実現する必要があります。さもなければ、汎型のクラスは効果的にメソッドで操作できません。

struct Point<T> {
    x: T,
    y: T,
}
impl<T> Point<T> {
    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> が必要であり、それが後の T の基準となります。しかし、その中のいずれかの汎型にメソッドを追加することもできます:

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

impl 块自体の汎型は、内部メソッドが汎型を持つ能力に影響を与えません:

impl<T, U> Point<T, U> {
    fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
        Point {
            x: このオブジェクトの x,
            y: その他の y,
        }
    }
}

method mixupは、Point<T, U>のxとPoint<V, W>のyを融合し、新しいPoint<T, W>の型の新しい点を生成します。

特性

特性(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ブロックは1つの特性のみを実装できます。

デフォルトの特性

これは特性とインターフェースの違いです:インターフェースは方法を規定できますが、方法を定義することはできません。しかし、特性はデフォルトのメソッドとして方法を定義できます。"デフォルト"であるため、オブジェクトはメソッドを再定義することも、デフォルトのメソッドを使用することもできます:

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<T: Descriptive>(object: T) {
    println!("{}", object.describe());
}

这是一种风格类似泛型的语法糖,这种语法糖在有多个参数类型均是特性的情况下十分实用:

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

特性作类型表示时如果涉及多个特性,可以用 + 符号表示,例如:

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

注意:仅用于表示类型的时候,并不意味着可以在 impl 块中使用。

复杂的实现关系可以使用 where 关键字简化,例如:

fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U)

可以简化成:

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

在了解这个语法之后,泛型章节中的"取最大值"案例就可以真正实现了:

trait Comparable {
    fn compare(&self, object: &Self) -> i8;
}
fn max<T: Comparable>(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!("arr の最大値は {}", max(&arr));
}

実行結果:

arr の最大値は 5

ヒント: 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<T> {}
impl<T: B + C> A<T> {
    fn d(&self) {}
}

このコードは、A<T> 型が T が B と C トレイトを実装している場合にのみこの impl ブロックを有効に実装できることを宣言しています。