ごらくらいふ

プログラミングしたりゲームしたり

Rustの話: traitの定義部分では、フィールドに干渉できない

前提

rustc 1.22.1

本題

traitを書く時、こういう書き方はできない。

trait Horizontal {
    // x:i64 というフィールドの存在を前提とする
    // x をカウントアップして、新しい値を返す
    fn count_up (&mut self) -> i64 {
        self.x += 1;
        self.x
    }
}

struct Point {
    x: i64, // コンパイラにフィールドの追加を強制される
}
impl Horizontal for Point {}

前提としてtraitのデフォルト実装はフィールドに干渉できない。

Traitを実装したStructに特定のフィールドを強制したい場合、 例えば、getter/setterの定義を用意することで実現できる。

trait Horizontal {
    fn get_x (&self) -> i64;
    fn set_x (&mut self, val: i64);
    
    fn count_up (&mut self) {
        let next = self.get_x() + 1;
        self.set_x( next );
        self.get_x()
    }
}

struct Point {
    x: i64, // getter/setterのためにフィールドを用意する
}
impl Horizontal for Point {
    fn get_x (&self) -> i64 {
        self.x
    }
    fn set_x (&mut self, val: i64) {
        self.x = val;
    }
}

余談: getter/setterのマクロを書いてみた

traitで使うaccessorと、実装時に使うaccessor_impl

macro_rules! accessor {
    ((get=$getter:ident) : $type:ty ) => {
        fn $getter(&self) -> $type ;
    };
    ((set=$setter:ident) : $type:ty ) => {
        fn $setter(&mut self, value:$type) ;
    };
    ((get=$getter:ident, set=$setter:ident) : $type:ty ) => {
        accessor!((get=$getter): $type);
        accessor!((set=$setter): $type);
    };
}
macro_rules! accessor_impl {
    ((get=$getter:ident) $name:ident : $type:ty ) => {
        fn $getter(&self) -> $type {
            self.$name
        }
    };
    ((set=$setter:ident) $name:ident : $type:ty ) => {
        fn $setter(&mut self, value: $type) {
            self.$name = value;
        }
    };
    ((get=$getter:ident, set=$setter:ident) $name:ident : $type:ty ) => {
        accessor_impl!((get=$getter) $name:$type);
        accessor_impl!((set=$setter) $name:$type);
    };
}

このマクロを使うとこんな感じになる。フィールドが増えないと効き目が薄い。

trait Horizontal {
    accessor!((get = get_x, set = set_x): i64);
    fn count_up(&mut self) -> i64 {
        let next = self.get_x() + 1;
        self.set_x(next);
        self.get_x()
    }
}
struct Point {
    x: i64,
}

impl Horizontal for Point {
    accessor_impl!((get = get_x, set = set_x) x: i64);
}