トレイト(trait)すごい
「コンセプトから理解するRust」を読んで衝撃を受けました...。
トレイト(trait)は今まで自分が出会ったことがない概念です。
多くの言語ではinterfaceや基底クラスを作って「あなたはこの関数orメソッドを実装してください」というルールを定めることが出来ます。pythonであれば、以下のコードのように基底クラスを作成して、継承させることで実装が出来ます。
from abc import ABCMeta, abstractmethod # 基底クラス class MonsterInterface(metaclass=ABCMeta): @abstractmethod def name(self): # nameの実装をルール化 pass class Slime(MonsterInterface): def name(self): return 'スライム' class KingSlime(MonsterInterface): def name(self): return 'キングスライム' print(Slime().name()) # スライム print(KingSlime().name()) # キングスライム
nameが実装されていないとエラーになる
class MetalSlime(MonsterInterface): pass MetalSlime() # Traceback (most recent call last): # File "Main.py", line 12, in <module> # MetalSlime() # TypeError: Can't instantiate abstract class MetalSlime with abstract methods name
このように基底クラスを継承、interfaceに対して実装するという方法が従来の方法かと思うのですが、Rustの場合はトレイト(trait)と呼ばれるものを使用します。トレイトは構造体に対して1つ1つ実装することが可能で、不要であれば実装しなくても構いません。
struct Slime; struct KingSlime; struct MetalSlime; trait Monster { fn name(&self) -> &'static str; } impl Monster for Slime { fn name(&self) -> &'static str { "スライム" } } impl Monster for KingSlime { fn name(&self) -> &'static str { "キングスライム" } } fn main() { println!("{}", Slime{}.name()); // スライム println!("{}", KingSlime{}.name()); // キングスライム MetalSlime{}; }
「ふーん、そうなんだ」という声が聞こえてきますが、面白いのはここからです。
体力(hp関数)の追加
仮にMonaster
に体力を返すhp
という関数を実装します。先ほどのpythonの例であれば基底クラスに新しくhp
という関数を作成して継承先のクラスで実装するようにルール化するか、もしくはそれぞれのクラスでhp
を適宜、追加します。
from abc import ABCMeta, abstractmethod # 基底クラス class MonsterInterface(metaclass=ABCMeta): @abstractmethod def name(self): # nameの実装をルール化 pass @abstractmethod def hp(self): pass class Slime(MonsterInterface): def name(self): return 'スライム' def hp(self): return 10 class KingSlime(MonsterInterface): def name(self): return 'キングスライム' def hp(self): return 70 slime = Slime() king_slime = KingSlime() print(f"{slime.name()}の体力: {slime.mp()}") # スライムの体力: 10 print(f"{king_slime.name()}の体力: {king_slime.mp()}") # キングスライムの体力: 70
特筆することはありません。次はRustのコードへのhp
の追加を行います。
struct Slime; struct KingSlime; struct MetalSlime; trait Monster { fn name(&self) -> &'static str; fn hp(&self) -> i32; } impl Monster for Slime { fn name(&self) -> &'static str { "スライム" } fn hp(&self) -> i32 { 10 } } impl Monster for KingSlime { fn name(&self) -> &'static str { "キングスライム" } fn hp(&self) -> i32 { 70 } } fn main() { let slime = Slime{}; let king_slime = KingSlime{}; println!("{}の体力: {}", slime.name(), slime.hp()); println!("{}の体力: {}", king_slime.name(), king_slime.hp()); MetalSlime{}; }
こちらも同じく、Monster
トレイトに対して新しくhp
を追加して、それぞれの構造体に対するimplでhp関数
を実装しました。何も問題ありません。
必殺技(special attack関数)の追加
では、次に必殺技(special attack)を実装します。なおスライムは必殺技は使えず、キングスライムは必殺技が使えるものとします。必殺技の有無はモンスターによって変わるため、別の基底クラスを用意して実装してみます。
(※通常攻撃もないのに必殺技から作るのかよというのは多めに見てください🙏)
class SpecialAttackInterface(metaclass=ABCMeta): @abstractmethod def sp_attack(self): pass class Slime(MonsterInterface): def name(self): return 'スライム' def hp(self): return 10 class KingSlime(MonsterInterface, SpecialAttackInterface): def name(self): return 'キングスライム' def hp(self): return 70 def sp_attack(self): return 100 slime = Slime() king_slime = KingSlime() print(f"{slime.name()}は必殺技が使えません") # スライムは必殺技が使えません print(f"{king_slime.name()}の必殺技: ダメージ={king_slime.sp_attack()}") # キングスライムの必殺技: ダメージ=100
pythonの場合は別の基底クラスを用意して、それをKingSlime
クラスに継承させました。継承したKingSlime
クラスのコードには当然ながらsp_attack
が追加されました。次にRustの場合ですが、新しくSpecialAttack
というトレイトを作成します。
trait SpecialAttack { fn sp_attack(&self) -> i32; } impl SpecialAttack for KingSlime { fn sp_attack(&self) -> i32 { 100 } } fn main() { let slime = Slime{}; let king_slime = KingSlime{}; println!("{}は必殺技が使えません", slime.name()); println!("{}の必殺技: ダメージ={}", king_slime.name(), king_slime.sp_attack()); MetalSlime{}; }
先程のpythonのコードとの一番の違いは既存のコードに対して全く手を入れていないという点です。
pythonでは基底クラスを作成後、継承して、継承したクラス内に新しい関数を作成しましたが、Rustでトレイトを使った場合は既存コードに手を加えることはなく、必要な構造体だけトレイトを実装すれば良いです。
既存のコードに手を加えなくても良いというのが、コード品質、安全性、ファイル分割などの視点で見れば非常に優れています。Haskellにはtypeclassという概念がありましたが「必要なら実装してね」という点で非常に似ていると感じました。
当時、Haskellを触っていた時に、なぜ感動しなかったのか分かりませんがRustのトレイトを知って衝撃を受けました。自分のおすすめは「コンセプトから理解するRust」という書籍です。文法や型とかはいいから、とりあえずRustの概念に触れたいという自分のような方にはぴったりです。