【Ruby】Struct(構造体クラス)を理解する


# Struct(構造体クラス)とは？

Rubyにおける`Struct(構造体クラス)`は簡易的なクラスのようなもので、下記のようなケースで使用されます。

- まとまったデータを扱いたいが、クラスを作るまでもない場合。
- クラス内で特定のデータのまとまりを表現する場合。


# 通常のクラスとの違い

主に下記のような違いがあります。

- 継承、メンバの追加・削除ができない。
- 明示的にアクセスメソッドを定義しなくても、構造体クラス外でメンバの参照・更新が可能。

# Struct(構造体クラス)の定義方法

## 定義方法(1)

第一引数には`String`で`構造体クラス名`を渡します。(クラスと同じで大文字から始まる必要があります。)
第二引数以降には`Symbol`でメンバを渡します。

__第一引数を渡した場合__

```ruby
Struct.new("User", :name, :age)

p Struct::User.new("Taro", 36) #=> #<struct Struct::User name="Taro", age=36>
```

__第一引数を省略した場合__

下記のように、第一引数を省略して定義する事も可能です。

```ruby
User = Struct.new(:name, :age)

p User.new("Taro", 36) #=> #<struct User name="Taro", age=36>
```

## 定義方法(2)

Struct(構造体クラス)は下記のように定義する事も可能です。

```ruby
class User < Struct.new(:name, :age)
end

p User.new("Taro", 36) #=> #<struct User name="Taro", age=36>
```

## Struct(構造体クラス)インスタンスの作成

既に上記で確認できますが、クラスのように`new`を使用する事で、`Struct(構造体クラス)`のインスタンスを作成する事が可能です。

```ruby
p User.new("Taro", 36) #=> #<struct User name="Taro", age=36>
```

__指定数以上の引数を渡した場合__

指定された数以上の引数を与えるとエラーになります。


```ruby
User.new("Taro", 36, 4) #=> struct size differs
```

__指定数に満たない数の引数を渡した場合__

逆に、指定数に満たない数の引数を渡した場合は、値が渡されなかったメンバに`nil`が入り、インスタンスが作成されます。

```ruby
User = Struct.new(:name, :age)

User.new("Taro") #=> <struct User name="Taro", age=nil>
```

# メンバの参照・更新方法

`Struct(構造体クラス)`の定義時に指定したメンバは下記のように簡単に参照・更新する事が可能です。

```ruby
user1 = User.new("Taro", 40) 

# 参照
p user1.name #=> "Taro"

# 更新
user1.name = "Jiro"
p user1.name #=> "Jiro"
```

# Struct(構造体クラス)にメソッドを定義する

クラスのように、`do~end`のブロック内にインスタンスから呼べるメソッドを定義する事が可能です。

```ruby
User = Struct.new("User", :name, :age) do
  def intro
    "私の名前は#{name}"
  end
  
  def say(word)
    word
  end
end

user1 = User.new("Taro",92)
p user1.intro #=> 私の名前はTaro
p user1.say("こんにちは") #=> こんにちは
```

また、通常のクラスのように、下記のようにする事でクラスから直接呼ぶメソッドの定義をする事も可能です。

```ruby
User = Struct.new("User", :name, :age) do
  class << self
    def hoge
      "Hoge"
    end
  end
  
  def self.huga
    "Huga"
  end
end

p User.hoge #=> "Hoge"
p User.huga #=> "Huga"
```

# Struct(構造体クラス)インスタンスの比較

`Struct(構造体クラス)`の特徴の1つにインスタンスのメンバの値が同じであれば`==`で比較した際に`true`を返す事が挙げられます。

```ruby
User = Struct.new(:name, :age)

user1 = User.new("Taro", 45)
user2 = User.new("Taro", 45)
user3 = User.new("Jiro", 90)

# メンバの値が同じなのでtrue
p user1 == user2 #=> true

# メンバの値が異なるのでfalse
p user1 == user3 #=> false
```

念の為ですが、クラスの場合は下記のようになります。

```ruby
class User
  def initialize(name, age)
    @name = name
    @age = age
  end 
end

user1 = User.new("Taro", 45)
user2 = User.new("Taro", 45)

# 異なるオブジェクトのためfalse
p user1 == user2 #=> false
```

# Struct(構造体クラス)の実例

最後に、使い方のイメージが付くよう`Struct(構造体クラス)`の実例を紹介します。
冒頭で挙げた通り、`Struct(構造体クラス)`は下記のように、クラス内で特定のデータのまとまりを表現する場合に使われます。

```ruby
class User
  Address = Struct.new(:city, :prefecture, :country)

  def initialize(name, args)
    @name = name
    @address = Address.new(args[:city], args[:prefecture], args[:country])
  end

end

user1 = User.new("Taro", { city: "Minato", prefecture: "Tokyo", country: "Japan"})

p user1 #=> <User:0x0000558567cc3408 @name="Taro", @address=#<struct User::Address city="Minato", prefecture="Tokyo", country="Japan">>
```

# 参考
- [Class: Struct (Ruby 2.1.2)](https://ruby-doc.org/core-2.1.2/Struct.html)
- [How to Use Struct & OpenStruct in Ruby (Complete Tutorial)](https://www.rubyguides.com/2017/06/ruby-struct-and-openstruct/)
- [The simple but powerful Ruby Struct \| Leigh Halliday](https://www.leighhalliday.com/ruby-struct)
- [RubyのStructクラスが便利 - kurobaraのブログ](https://moonstruckdrops.github.io/blog/2013/07/02/ruby-struct/)
- [suzupy — RubyでのStructの使い方](https://suzupy.tumblr.com/post/144393392355/ruby%E3%81%A7%E3%81%AEstruct%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9)