連載
» 2022年05月27日 05時00分 公開

ジェネリクスとトレイト――Rustでジェネリクス型を実装する基本からしっかり学ぶRust入門(11)

Rustについて基本からしっかり学んでいく本連載。第11回は、Rustのジェネリクスとトレイトについて。

[山内直(著)/山田祥寛(監修),WINGSプロジェクト]

この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。

「基礎からしっかり学ぶRust入門」のインデックス

連載:基礎からしっかり学ぶRust入門

 本連載のサンプルコードをGitHubで公開しています。こちらからダウンロードしてみてください。具体的な利用方法は連載第1回を参考にしてください。


 第11回は、独自のジェネリクス型を定義し、メソッドを実装してみるという過程を通じて、メソッドと切り離せない重要な概念であるトレイト(trait)を紹介します。

ジェネリクス型を定義する

 第9回第10回で、ジェネリクスとコレクションを紹介しました。ジェネリクスとは、データ型を抽象化することでコードの再利用を容易にする仕組みです。ジェネリクスを使用することで、抽象化されたデータ型に対してのみ定義や処理内容を記述すればよくなり、コードの重複を防いでメンテナンス性も向上させることができます。第9回第10回では、標準ライブラリで定義済みのコレクション(ベクター、ハッシュマップなど)を扱うことでジェネリクスについて触れてきましたが、今回は独自のジェネリクス型を定義してメソッドを実装しながら、理解を深めていきます。

構造体をジェネリクス型として定義する

 標準ライブラリが備えるコレクションであるVec<T>型やHashMap<K, V>型と同様に、独自の構造体もジェネリクス型として定義できます。struct文において、構造体の名前に型パラメーターを付記し、その型パラメーターを用いてジェネリクス型に依存するフィールドのデータ型を指定します。以下は、範囲を保持する構造体Range型をジェネリクス型として定義する例です。

struct Range<T> {       (1)
    min: T,
    max: T,
    step: T,
    current: T,
}
fn main() {
    let int_range = Range {min: 1, max: 10, step:1, current: -1};       (2)
    let float_range = Range {min: 1.0, max: 100.0, step: 0.1, current: -1.0};
    //let mixed_range = Range {min: 1.0, max: 10, step: 1, current: -1.0};      (3)
    println!("min: {}, max: {}, step: {}, current:{}", 
        int_range.min, int_range.max, int_range.step, int_range.current);
        // min: 1, max: 10, step: 1, current:-1
    println!("min: {}, max: {}, step: {}, current:{}", 
        float_range.min, float_range.max, float_range.step, float_range.current);
        // min: 1, max: 100, step: 0.1, current:-1
}
src/bin/generic_struct.rs

 (1)は、構造体Rangeをジェネリクス型として定義しています。型パラメーターにはTが渡されているので、それを用いてmin、max、step、currentの4つのフィールドを宣言しています。このように、構造体をジェネリクス型として定義するのは簡単です。HashMap<K, V>型のように、型パラメーターを複数にする場合も同様に定義できます。

 (2)では、Range型を使って実際にインスタンス変数を宣言しています。構造体のインスタンスの初期化については第7回で紹介した通りです。Rustでは、初期化に与えられたリテラルからデータ型を型推論し、その結果でTのデータ型を決定します。この例では、int_rangeはRange<i32>、float_rangeはRange<f64>となります。

 (3)は、コメントアウトされていますが、コメントを削除するとコンパイルエラーとなります。それは、初期化に用いているリテラルのデータ型が一致しないからです。なお、(1)の文で型を明示する場合は、以下のように記述します。この辺りも、通常の変数宣言のルール通りです。もちろん、型パラメーターにi32型を指定しているので、浮動小数点数などを指定するとコンパイルエラーとなります。

let int_range: Range<i32> = Range {min: 1, max: 10, step: 1, current: -1};

【補足】列挙型のジェネリクス

 列挙型(enum型)でもジェネリクスが可能です。第8回で紹介したResult<T, E>型や第9回で紹介したOption<T>型は、列挙型のジェネリクスです。例えばResult型は、以下のように定義されています。列挙型では、列挙子がそれぞれデータを持つことができましたが、その型がジェネリクスによって抽象化されているのです。

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

ジェネリクス型の構造体にメソッドを実装する

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。