↓ メインコンテンツへスキップ

Rust のトレイト

基本概要
#

  • 型に対して実装すべきメソッドを定義したのがトレイト

    • Java で言うところのインタフェース
     1// デカルト座標
     2#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
     3pub struct CartesianCoord {
     4    pub x: f64,
     5    pub y: f64,
     6}
     7
     8// 極座標
     9pub struct PolarCoord {
    10    pub r: f64,
    11    pub theta: f64,
    12}
    13
    14// 座標
    15pub trait Coordinates {
    16    // 関数の本体は書かない
    17    fn to_cartesian(self) -> CartesianCoord;
    18    fn from_cartesian(cart: CartesianCoord) -> Self;
    19}
    20
    21// デカルト座標系はそのまま
    22impl Coordinates for CartesianCoord {
    23    fn to_cartesian(self) -> CartesianCoord {
    24        self
    25    }
    26    fn from_cartesian(cart: CartesianCoord) -> Self {
    27        cart
    28    }
    29}
    30
    31// 極座標系は変換が必要
    32impl Coordinates for PolarCoord {
    33    fn to_cartesian(self) -> CartesianCoord {
    34        CartesianCoord {
    35            x: self.r * self.theta.cos(),
    36            y: self.r * self.theta.sin(),
    37        }
    38    }
    39    fn from_cartesian(cart: CartesianCoord) -> Self {
    40        PolarCoord {
    41            r: (cart.x * cart.x + cart.y * cart.y).sqrt(),
    42            theta: (cart.y / cart.x).atan(),
    43        }
    44    }
    45}
    46
    47// タプルにもトレイトを実装できる
    48impl Coordinates for (f64, f64) {
    49    fn to_cartesian(self) -> CartesianCoord {
    50        CartesianCoord {
    51            x: self.0,
    52            y: self.1,
    53        }
    54    }
    55    fn from_cartesian(cart: CartesianCoord) -> Self {
    56        (cart.x, cart.y)
    57    }
    58}
  • ジェネリクスで受け取る型に境界(「トレイトを実装している型」というような境界)をトレイト境界という

  • 下の例だと、パラメータ P の後にトレイト名を付けることで、to_cartesian() を実装している型しか受け取れない、と強制できる

    1fn print_point<P: Coordinates>(point: P) {
    2    let p = point.to_cartesian();
    3    println!("({}, {})", p.x, p.y)
    4}
  • 関数の型の後に where<トレイト境界> という書き方も OK

    1fn print_point<P>(point: P)
    2where
    3    P: Coordinates,
    4{
    5    let p = point.to_cartesian();
    6    println!("({}, {})", p.x, p.y)
    7}
  • impl Trait 構文で書く方法もある

    • 関数の引数の型の位置に impl トレイト名 を書くことで、トレイト境界を指定する。
    • この構文を使うと、型パラメータや具体的な型名に言及せずにトレイト境界を書くことができる
    1fn print_point(point: impl Coordinates) {
    2    let p = point.to_cartesian();
    3    println!("({}, {})", p.x, p.y)
    4}
  • トレイトを継承することも可能

  • トレイトのメソッドにはデフォルト実装をもたせることができる

  • トレイトで定義した関数は、トレイトとそれを実装する型が可視であれば、他のモジュールからアクセスできる

    • 関数ごとに pub を付ける必要はない
  • トレイト実装のルール:あるトレイト Trait をある型 Type に実装するためには、トレイト Trait または型 Type の少なくともどちらか一方の定義のあるクレートで実装しなければならない

    型:自クレート型:他クレート
    トレイト:自クレート○○
    トレイト:他クレート○×
  • いくつかの標準ライブラリのトレイトは #[derive(XXX)] アトリビュートを使うことで型定義時に自動で実装できる

トレイトのジェネリクス
#

  • trait トレイト名<型パラメータ> で宣言できる

    1trait Init<T> {
    2    fn init(t: T) -> Self;
    3}
  • 実装も今まで通り

    • 型パラメータを導入する箇所は、トレイト名より前であることに注意
    1impl<T> Init<T> for Box<T> {
    2    // 内部では`T`でパラメータの型を参照する
    3    fn init(t: T) -> Self {
    4        Box::new(t)
    5    }
    6}

トレイトを実装する際に、列挙されてないメソッドも定義する
#

  • クラスベースの言語から来た身としては、変に悩んでしまったので…
  • rust-jp.rs の 日本語コミュニティ(Slack)の方々に回答いただいた。

やりたいこと:Java での実装例
#

  • タイトルだけでは何がしたいのかさっぱりわからないと思うので、実現したいことを Java で書くと以下のような感じ:

     1import java.util.*;
     2
     3interface Service {
     4    int ID = 123;
     5    void displayId();
     6}
     7
     8class Module implements Service {
     9    public void displayId() {
    10        System.out.println(getId());
    11    }
    12
    13    private int getId() {
    14        return this.ID;
    15    }
    16}
    17
    18public class Main {
    19    public static void main(String[] args) {
    20        Module module = new Module();
    21        module.displayId();
    22    }
    23}
  • 要は、以下の2点:

    • インタフェース(Rust ではトレイト)で public なメソッドを1つ列挙しておきたい
    • 実装クラス(Rust では impl)の中で、 private なメソッドを定義して利用したい

Rust で実現:失敗例
#

  • これを Rust で実現しようとこのように書くとコンパイルエラーが出て怒られる:

     1trait Service {
     2    fn display(&self);
     3}
     4
     5struct Module {
     6    id: u64,
     7}
     8
     9impl Service for Module {
    10    fn display(&self) {
    11        print!("{}", get_id());
    12    }
    13
    14    fn get_id(&self) -> u64 {
    15        self.id
    16    }
    17}
    18
    19fn main() {
    20    let module = Module{
    21        id: 12,
    22    };
    23    module.display();
    24}
  • エラーメッセージは、error[E0407]: method get_id is not a member of trait Service と error[E0425]: cannot find function get_id in this scope というもの。

    • get_id というメソッドは Service トレイトで列挙していないので、書けないよ と怒られてしまう。

Rust で実現:成功例
#

  • ということで、以下のようにするとコンパイラに怒られずに済む:

     1trait Service {
     2    fn display(&self);
     3}
     4
     5struct Module {
     6    id: u64,
     7}
     8
     9impl Service for Module {
    10    fn display(&self) {
    11        print!("{}", self.get_id());
    12    }
    13}
    14
    15impl Module {
    16    fn get_id(&self) -> u64 {
    17        self.id
    18    }
    19}
    20
    21fn main() {
    22    let module = Module{
    23        id: 12,
    24    };
    25    module.display();
    26}
  • 直したのは、 Service トレイトの実装 Module をトレイトの実装部分( impl Service for Module の箇所)と Module 独自のメソッドを定義する部分( impl Module の箇所)に分けて書く、という点。

ここに掲載する記事はまだありません。