トレイト

トレイトはある型が提供しなければならない機能をRustのコンパイラに伝える言語機能です。

メソッド構文で関数を呼び出すのに用いていた、 impl キーワードを思い出して下さい。

fn main() { struct Circle { x: f64, y: f64, radius: f64, } impl Circle { fn area(&self) -> f64 { std::f64::consts::PI * (self.radius * self.radius) } } }
struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

始めにトレイトをメソッドのシグネチャと共に定義し、続いてある型のためにトレイトを実装するという流れを除けばトレイトはメソッド構文に似ています。 この例では、 CircleHasArea トレイトを実装しています。

fn main() { struct Circle { x: f64, y: f64, radius: f64, } trait HasArea { fn area(&self) -> f64; } impl HasArea for Circle { fn area(&self) -> f64 { std::f64::consts::PI * (self.radius * self.radius) } } }
struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

trait HasArea {
    fn area(&self) -> f64;
}

impl HasArea for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

このように、 trait ブロックは impl ブロックにとても似ているように見えますが、関数本体を定義せず、型シグネチャだけを定義しています。トレイトを impl するときは、ただ impl Item とするのではなく、 impl Trait for Item と記述します。

ジェネリック関数におけるトレイト境界

トレイトはある型の振る舞いを確約できるため有用です。ジェネリック関数は制約、あるいは 境界 が許容する型のみを受け取るためにトレイトを利用できます。以下の関数を考えて下さい、これはコンパイルできません。

fn main() { fn print_area<T>(shape: T) { println!("This shape has an area of {}", shape.area()); } }
fn print_area<T>(shape: T) {
    println!("This shape has an area of {}", shape.area());
}

Rustは以下のエラーを吐きます。

error: no method named `area` found for type `T` in the current scope

T はあらゆる型になれるため、 area メソッドが実装されているか確認できません。ですがジェネリックな T にはトレイト境界を追加でき、境界が実装を保証してくれます。

fn main() { trait HasArea { fn area(&self) -> f64; } fn print_area<T: HasArea>(shape: T) { println!("This shape has an area of {}", shape.area()); } }
fn print_area<T: HasArea>(shape: T) {
    println!("This shape has an area of {}", shape.area());
}

<T: HasArea> 構文は「 HasArea トレイトを実装するあらゆる型」という意味です。トレイトは関数の型シグネチャを定義しているため、 HasArea を実装するあらゆる型が .area() メソッドを持っていることを確認できます。

トレイトの動作を確認するために拡張した例が以下になります。

trait HasArea { fn area(&self) -> f64; } struct Circle { x: f64, y: f64, radius: f64, } impl HasArea for Circle { fn area(&self) -> f64 { std::f64::consts::PI * (self.radius * self.radius) } } struct Square { x: f64, y: f64, side: f64, } impl HasArea for Square { fn area(&self) -> f64 { self.side * self.side } } fn print_area<T: HasArea>(shape: T) { println!("This shape has an area of {}", shape.area()); } fn main() { let c = Circle { x: 0.0f64, y: 0.0f64, radius: 1.0f64, }; let s = Square { x: 0.0f64, y: 0.0f64, side: 1.0f64, }; print_area(c); print_area(s); }
trait HasArea {
    fn area(&self) -> f64;
}

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

impl HasArea for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

struct Square {
    x: f64,
    y: f64,
    side: f64,
}

impl HasArea for Square {
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

fn print_area<T: HasArea>(shape: T) {
    println!("This shape has an area of {}", shape.area());
}

fn main() {
    let c = Circle {
        x: 0.0f64,
        y: 0.0f64,
        radius: 1.0f64,
    };

    let s = Square {
        x: 0.0f64,
        y: 0.0f64,
        side: 1.0f64,
    };

    print_area(c);
    print_area(s);
}

このプログラムの出力は、

This shape has an area of 3.141593
This shape has an area of 1

見ての通り、上記の print_area はジェネリックですが、適切な型が渡されることを保証しています。もし不適切な型を渡すと、

fn main() { print_area(5); }
print_area(5);

コンパイル時エラーが発生します。

error: the trait `HasArea` is not implemented for the type `_` [E0277]

ジェネリック構造体におけるトレイト境界

ジェネリック構造体もトレイト境界による恩恵を受けることができます。型パラメータを宣言する際に境界を追加するだけで良いのです。以下が新しい型 Rectangle<T> とそのメソッド is_square() です。

struct Rectangle<T> { x: T, y: T, width: T, height: T, } impl<T: PartialEq> Rectangle<T> { fn is_square(&self) -> bool { self.width == self.height } } fn main() { let mut r = Rectangle { x: 0, y: 0, width: 47, height: 47, }; assert!(r.is_square()); r.height = 42; assert!(!r.is_square()); }
struct Rectangle<T> {
    x: T,
    y: T,
    width: T,
    height: T,
}

impl<T: PartialEq> Rectangle<T> {
    fn is_square(&self) -> bool {
        self.width == self.height
    }
}

fn main() {
    let mut r = Rectangle {
        x: 0,
        y: 0,
        width: 47,
        height: 47,
    };

    assert!(r.is_square());

    r.height = 42;
    assert!(!r.is_square());
}

is_square() は両辺が等しいかチェックする必要があるため、両辺の型は core::cmp::PartialEq トレイトを実装しなければなりません。

fn main() { impl<T: PartialEq> Rectangle<T> { ... } }
impl<T: PartialEq> Rectangle<T> { ... }

これで、長方形を等値性の比較できる任意の型として定義できました。

上記の例では任意の精度の数値を受け入れる Rectangle 構造体を新たに定義しました-実は、等値性を比較できるほぼ全ての型に対して利用可能なオブジェクトです。同じことを SquareCircle のような HasArea を実装する構造体に対してできるでしょうか?可能では有りますが乗算が必要になるため、それをするには オペレータトレイト についてより詳しく知らなければなりません。

トレイト実装のルール

ここまでで、構造体へトレイトの実装を追加することだけを説明してきましたが、あらゆる型についてトレイトを実装することもできます。技術的には、 i32HasArea を実装することも できなくはない です。

fn main() { trait HasArea { fn area(&self) -> f64; } impl HasArea for i32 { fn area(&self) -> f64 { println!("this is silly"); *self as f64 } } 5.area(); }
trait HasArea {
    fn area(&self) -> f64;
}

impl HasArea for i32 {
    fn area(&self) -> f64 {
        println!("this is silly");

        *self as f64
    }
}

5.area();

しかし例え可能であったとしても、そのようなプリミティブ型のメソッドを実装するのは適切でない手法だと考えられています。

ここまでくると世紀末感が漂いますが、手に負えなくなることを防ぐためにトレイトの実装周りには2つの制限が設けられています。第1に、あなたのスコープ内で定義されていないトレイトは適用されません。例えば、標準ライブラリは File にI/O機能を追加するための Write トレイトを提供しています。デフォルトでは、 FileWrites で定義されるメソッド群を持っていません。

fn main() { let mut f = std::fs::File::open("foo.txt").expect("Couldn’t open foo.txt"); // let buf = b"whatever"; // byte string literal. buf: &[u8; 8] let buf = b"whatever"; // buf: &[u8; 8] はバイト文字列リテラルです。 let result = f.write(buf); // result.unwrap(); // ignore the error // result.unwrap(); // エラーを無視します。 }
let mut f = std::fs::File::open("foo.txt").expect("Couldn’t open foo.txt");
let buf = b"whatever"; // buf: &[u8; 8] はバイト文字列リテラルです。
let result = f.write(buf);

エラーは以下のようになります。

error: type `std::fs::File` does not implement any method in scope named `write`
let result = f.write(buf);
               ^~~~~~~~~~

始めに Write トレイトを use する必要があります。

fn main() { use std::io::Write; let mut f = std::fs::File::open("foo.txt").expect("Couldn’t open foo.txt"); let buf = b"whatever"; let result = f.write(buf); // result.unwrap(); // ignore the error // result.unwrap(); // エラーを無視します。 }
use std::io::Write;

let mut f = std::fs::File::open("foo.txt").expect("Couldn’t open foo.txt");
let buf = b"whatever";
let result = f.write(buf);

これはエラー無しでコンパイルされます。

これは、例え誰かが i32 へメソッドを追加するような望ましくない何かを行ったとしても、あなたがトレイトを use しない限り、影響はないことを意味します。

トレイトの実装における制限はもう1つあります。トレイト、またはあなたが書いている impl の対象となる型は、あなた自身によって実装されなければなりません。 HasArea は私たちが記述したコードであるため、 i32 のための HasArea を実装することができます。しかし、 i32 のためにRustによって提供されている ToString トレイトを実装したいとしても、トレイトと型が共に私たちの記述したコードでないため、それはできません。

トレイトに関して最後に1つ。トレイト境界が設定されたジェネリック関数は「単相化」(monomorphization)(mono: 単一の、morph: 相)されるため、静的ディスパッチが行われます。一体どういう意味でしょうか?詳細については、 トレイトオブジェクト の章をチェックしてください。

複数のトレイト境界

トレイトによってジェネリックな型パラメータに境界が与えられることを見てきました。

fn main() { fn foo<T: Clone>(x: T) { x.clone(); } }
fn foo<T: Clone>(x: T) {
    x.clone();
}

1つ以上の境界を与えたい場合、 + を使えます。

fn main() { use std::fmt::Debug; fn foo<T: Clone + Debug>(x: T) { x.clone(); println!("{:?}", x); } }
use std::fmt::Debug;

fn foo<T: Clone + Debug>(x: T) {
    x.clone();
    println!("{:?}", x);
}

この T 型は CloneDebug 両方が必要です。

Where 節

ジェネリック型もトレイト境界の数も少ない関数を書いているうちは悪く無いのですが、数が増えるとこの構文ではいよいよ不便になってきます。

fn main() { use std::fmt::Debug; fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) { x.clone(); y.clone(); println!("{:?}", y); } }
use std::fmt::Debug;

fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
    x.clone();
    y.clone();
    println!("{:?}", y);
}

関数名は左端にあり、引数リストは右端にあります。境界を記述する部分が邪魔になっているのです。

Rustは「 where 節」と呼ばれる解決策を持っています。

use std::fmt::Debug; fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) { x.clone(); y.clone(); println!("{:?}", y); } fn bar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug { x.clone(); y.clone(); println!("{:?}", y); } fn main() { foo("Hello", "world"); bar("Hello", "world"); }
use std::fmt::Debug;

fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
    x.clone();
    y.clone();
    println!("{:?}", y);
}

fn bar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug {
    x.clone();
    y.clone();
    println!("{:?}", y);
}

fn main() {
    foo("Hello", "world");
    bar("Hello", "world");
}

foo() は先程見せたままの構文で、 bar()where 節を用いています。型パラメータを定義する際に境界の設定をせず、引数リストの後ろに where を追加するだけで良いのです。長いリストであれば、空白を加えることもできます。

fn main() { use std::fmt::Debug; fn bar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug { x.clone(); y.clone(); println!("{:?}", y); } }
use std::fmt::Debug;

fn bar<T, K>(x: T, y: K)
    where T: Clone,
          K: Clone + Debug {

    x.clone();
    y.clone();
    println!("{:?}", y);
}

この柔軟性により複雑な状況であっても可読性を改善できます。

また、where は基本の構文よりも強力です。例えば、

fn main() { trait ConvertTo<Output> { fn convert(&self) -> Output; } impl ConvertTo<i64> for i32 { fn convert(&self) -> i64 { *self as i64 } } // can be called with T == i32 // T == i32の時に呼び出せる fn normal<T: ConvertTo<i64>>(x: &T) -> i64 { x.convert() } // can be called with T == i64 // T == i64の時に呼び出せる fn inverse<T>() -> T // this is using ConvertTo as if it were "ConvertTo<i64>" // これは「ConvertTo<i64>」であるかのようにConvertToを用いている where i32: ConvertTo<T> { 42.convert() } }
trait ConvertTo<Output> {
    fn convert(&self) -> Output;
}

impl ConvertTo<i64> for i32 {
    fn convert(&self) -> i64 { *self as i64 }
}

// T == i32の時に呼び出せる
fn normal<T: ConvertTo<i64>>(x: &T) -> i64 {
    x.convert()
}

// T == i64の時に呼び出せる
fn inverse<T>() -> T
        // これは「ConvertTo<i64>」であるかのようにConvertToを用いている
        where i32: ConvertTo<T> {
    42.convert()
}

ここでは where 節の追加機能を披露しています。この節は左辺に型パラメータ T だけでなく具体的な型(このケースでは i32 )を指定できます。この例だと、 i32ConvertTo<T> を実装していなければなりません。(それは明らかですから)ここの where 節は i32 が何であるか定義しているというよりも、 T に対して制約を設定しているといえるでしょう。

デフォルトメソッド

典型的な実装者がどうメソッドを定義するか既に分かっているならば、トレイトの定義にデフォルトメソッドを加えることができます。例えば、以下の is_invalid()is_valid() の反対として定義されます。

fn main() { trait Foo { fn is_valid(&self) -> bool; fn is_invalid(&self) -> bool { !self.is_valid() } } }
trait Foo {
    fn is_valid(&self) -> bool;

    fn is_invalid(&self) -> bool { !self.is_valid() }
}

Foo トレイトの実装者は is_valid() を実装する必要がありますが、デフォルトの動作が加えられている is_invalid() には必要ありません。

fn main() { trait Foo { fn is_valid(&self) -> bool; fn is_invalid(&self) -> bool { !self.is_valid() } } struct UseDefault; impl Foo for UseDefault { fn is_valid(&self) -> bool { println!("Called UseDefault.is_valid."); true } } struct OverrideDefault; impl Foo for OverrideDefault { fn is_valid(&self) -> bool { println!("Called OverrideDefault.is_valid."); true } fn is_invalid(&self) -> bool { println!("Called OverrideDefault.is_invalid!"); // true // overrides the expected value of is_invalid() true // 予期されるis_invalid()の値をオーバーライドする } } let default = UseDefault; // assert!(!default.is_invalid()); // prints "Called UseDefault.is_valid." assert!(!default.is_invalid()); // 「Called UseDefault.is_valid.」を表示 let over = OverrideDefault; // assert!(over.is_invalid()); // prints "Called OverrideDefault.is_invalid!" assert!(over.is_invalid()); // 「Called OverrideDefault.is_invalid!」を表示 }
struct UseDefault;

impl Foo for UseDefault {
    fn is_valid(&self) -> bool {
        println!("Called UseDefault.is_valid.");
        true
    }
}

struct OverrideDefault;

impl Foo for OverrideDefault {
    fn is_valid(&self) -> bool {
        println!("Called OverrideDefault.is_valid.");
        true
    }

    fn is_invalid(&self) -> bool {
        println!("Called OverrideDefault.is_invalid!");
        true // 予期されるis_invalid()の値をオーバーライドする
    }
}

let default = UseDefault;
assert!(!default.is_invalid()); // 「Called UseDefault.is_valid.」を表示

let over = OverrideDefault;
assert!(over.is_invalid()); // 「Called OverrideDefault.is_invalid!」を表示

継承

時々、1つのトレイトの実装に他のトレイトの実装が必要になります。

fn main() { trait Foo { fn foo(&self); } trait FooBar : Foo { fn foobar(&self); } }
trait Foo {
    fn foo(&self);
}

trait FooBar : Foo {
    fn foobar(&self);
}

FooBar の実装者は Foo も実装しなければなりません。以下のようになります。

fn main() { trait Foo { fn foo(&self); } trait FooBar : Foo { fn foobar(&self); } struct Baz; impl Foo for Baz { fn foo(&self) { println!("foo"); } } impl FooBar for Baz { fn foobar(&self) { println!("foobar"); } } }
struct Baz;

impl Foo for Baz {
    fn foo(&self) { println!("foo"); }
}

impl FooBar for Baz {
    fn foobar(&self) { println!("foobar"); }
}

Foo の実装を忘れると、Rustは以下のように伝えるでしょう。

error: the trait `main::Foo` is not implemented for the type `main::Baz` [E0277]

Derive

繰り返しDebugDefault のようなトレイトを実装するのは非常にうんざりさせられます。そのような理由から、Rustは自動的にトレイトを実装するための アトリビュート を提供しています。

#[derive(Debug)] struct Foo; fn main() { println!("{:?}", Foo); }
#[derive(Debug)]
struct Foo;

fn main() {
    println!("{:?}", Foo);
}

ただし、deriveは以下の特定のトレイトに制限されています。