Rustについて基本からしっかり学んでいく本連載。第11回は、Rustのジェネリクスとトレイトについて。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
第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 }
(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};
Copyright © ITmedia, Inc. All Rights Reserved.