🏗️

構造体とimpl — Rubyのclassが2つに分かれる

structでデータ、implでメソッド、traitで多態性

Rubyではクラスがデータ(インスタンス変数)とメソッドを一体にまとめる。Rustはこれを分離した。

# Ruby — データとメソッドが一体
class User
  attr_reader :name, :email
  def initialize(name, email)
    @name = name
    @email = email
  end
  def display
    "#{name} <#{email}>"
  end
end

// Rust — データ
struct User {
    name: String,
    email: String,
}

// Rust — メソッド(別ブロック)
impl User {
    fn new(name: String, email: String) -> Self {
        User { name, email }
    }
    fn display(&self) -> String {
        format!("{} <{}>", self.name, self.email)
    }
}

&self — Rubyの暗黙的selfが明示的に

Rubyではインスタンスメソッド内でselfは自動的に使える。nameだけでself.nameになる。Rustでは第1引数として&selfを明示する必要がある。

  • &self — 読み取り専用(Rubyの通常メソッド)

  • &mut self — 値を変更可能(Rubyの!メソッドに近い)

  • self — 所有権を取る(呼び出し後に元のデータは使用不可)

impl User {
    fn name(&self) -> &str { &self.name }           // 読み取り
    fn set_name(&mut self, name: String) { self.name = name; } // 書き込み
    fn into_name(self) -> String { self.name }        // 所有権移動
}

関連関数 — Rubyのクラスメソッド

&selfを受け取らない関数はRubyのクラスメソッドに相当する。

# Ruby
User.create(name: "sehwa")  # クラスメソッド

// Rust
User::new(String::from("sehwa"), String::from("s@e.com"));  // ::で呼び出し

.ではなく::で呼ぶ。newはキーワードではなくただの慣例。

trait — Rubyのmodule

Rubyでincludeでモジュールを混ぜるように、Rustではtraitをimplする。

# Ruby
module Printable
  def print_info
    puts to_s
  end
end
class User
  include Printable
end

// Rust
trait Printable {
    fn print_info(&self);
}

impl Printable for User {
    fn print_info(&self) {
        println!("{}", self.display());
    }
}

核心的な違い:Rubyのmoduleは実装を含むことができ多重includeも自由。Rustのtraitはインターフェースに近く、実装はimpl Trait for Typeで行う。ダイアモンド継承問題がない。

derive — 自動実装

RubyでComparableをincludeして<=>を定義すると残りの比較演算子が自動で生まれる。Rustのderiveは似たようなもの。

#[derive(Debug, Clone, PartialEq)]
struct User {
    name: String,
    email: String,
}

Debugputs user.inspectのようなデバッグ出力、Clone.dupPartialEq==比較。アノテーション1行で自動実装される。

継承がない

Rustに継承はない。これがRuby開発者にとって最大のパラダイム転換。コード再利用はコンポジション(構造体の中に別の構造体を入れる)かtraitで解決する。

キーポイント

1

structでデータ定義、implでメソッド定義 — 別ブロック

2

&self = 読み取り、&mut self = 書き込み、self = 所有権移動

3

traitはRubyのmodule includeに対応 — 継承の代わりに使用

4

deriveでDebug、Clone、PartialEq等を自動実装

メリット

  • データと振る舞いが分離されて構造が明確
  • traitにダイアモンド継承問題がない

デメリット

  • Rubyのオープンクラス(既存クラスにメソッド追加)ができない
  • 継承なしでのコード再利用という考え方の転換が必要

ユースケース

ActiveRecordモデルのようにデータ + ビジネスロジックをまとめる時 RubyのComparable/Enumerableをtraitで実装する時