Learning Rust - Generics

Syntax and Semantics

今回はRustのジェネリクスのはなしです。 ジェネリクスがでてくると、佳境に入った気がしますね。

私はC++でテンプレートに慣れているので、 Rustのジェネリクスもスムーズに学べました。

Generics

Rustの標準ライブラリには、たとえばOption<T>という型があります。 ある正常な値をもっているか、あるいは不正かの情報をもち、 Option<T>をつかうとエラー処理に統一感がでます。

enum Option<T> {
    Some(T),
    None,
}

let x: Option<i32> = Some(5);

<T>がジェネリクスを表しています。 このenumはいろんな型Tのバージョンをつくることができるというわけです。 正常な値をあらわすSomeに、おなじT型の値をいれます。

標準ライブラリに、他にはResult<T, E>という型もあります。 これはOption<T>に使う場面が似ていますが、 エラーだった場合の情報を増やすために、エラーを表すstructが、 単なるunit-like structではなく、tuple structになっています。

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

このように、二つ以上のパラメータをもったジェネリクス型もつくれます。

Generic functions

Enumだけでなく、ジェネリックな関数もつくれます。 Syntaxは以下です。

fn takes_anything<T>(x: T) {
    // do something with x
}

もちろん複数の引数をとったり、パラメータが複数になったりもできます。

fn takes_two_of_the_same_things<T>(x: T, y: T) {
    // ...
}

fn takes_two_things<T, U>(x: T, y: U) {
    // ...
}

ジェネリックな関数を呼ぶときは、型は推論されるようです。

fn main() {
    trait Printable {
        fn print(&self);
    }

    impl Printable for i32 {
        fn print(&self) {
            println!("{}", self);
        }
    }

    impl Printable for f64 {
        fn print(&self) {
            println!("{}", self);
        }
    }

    fn takes_anything<T: Printable>(x: T) {
        x.print();
    }

    takes_anything(1);
    takes_anything(1.0);
}

ただし、実際によぶときは多くの場合、 上のコードのようにtraitの定義が必要でしょう。 Traitについては次回に紹介します。

Generic structs

さらにstructもジェネリクスが可能です。

struct Point<T> {
    x: T,
    y: T,
}

let int_origin = Point { x: 0, y: 0 };
let float_origin = Point { x: 0.0, y: 0.0 };

これもパラメータは推論されるようですね。

メソッドを定義するときは、impl<T>と書きます。

impl<T> Point<T> {
    fn swap(&mut self) {
        std::mem::swap(&mut self.x, &mut self.y);
    }
}

ジェネリクスは強力で、なくてはならない機能ですが、 一方であらゆる型を受け入れたりすると、すぐに「爆発」してしまいます。 C++で、とんでもなく長くわかりにくい、 テンプレートのエラーメッセージに遭遇したかたも多いでしょう。

これを防ぐため、ジェネリクスが受け取れる型になんらかの条件を設定するのが、 次回扱うtraitです。