#Crystal : initialize 中の self 利用（追記あり）

# Crystal : `initialize` 中の `self` 利用

<i class="fa fa-lightbulb-o"></i> <a href="#-追記">追記</a>あり

## <i class="fa fa-check-square-o"></i> メモ

動的型付け言語のRubyでその恩恵を目一杯受けていたコードを，Crystalなど静的型付け言語へ移植しようとすると，思わぬ小石に躓くことが多い。

今日の小石は `inialize` 中の `self` 。

親子関係のあるオブジェクト間で相互参照したいようなとき，基本的に「実際に使用される瞬間の状態がすべて」な Ruby だと

```ruby:parent_child.rb
class Parent
  attr_reader :name
  attr_reader :child

  def initialize(_name)
    @name = _name
    @child = Child.new(self)
  end
end

class Child
  attr_reader :parent

  def initialize(_parent)
    @parent = _parent
  end
end

the_parent = Parent.new("John")
the_child = the_parent.child

puts the_child.parent.name
```

みたいに書いても，（相互参照自体の是非はひとまず置くとして）なんとなく動いてしまう。

```
$ ruby parent_child.rb
John
```

同じようなことを Crystal でやろうとして，

```ruby:parent_child.cr
class Parent
  getter :name
  getter :child

  def initialize(@name)
    @child = Child.new(self)
  end
end

class Child
  getter :parent

  def initialize(@parent)
  end
end

the_parent = Parent.new("John")
the_child = the_parent.child

puts the_child.parent.name
```

とか書いてみると，

```
$ crystal 
Error in line 25: undefined method 'parent' for Nil
...

```

と怒られる。
「なぜ **Nil** ？」と思って `the_child.class` を見ても **Child** クラスだと返ってくるので余計混乱したりして。

```ruby
puts the_child.class # -> "Child"
```

エラーメッセージを最後まで読んでいくと 

```
Error: 'self' was used before initializing instance variable '@child', rendering it nilable
```

と書かれている通り，原因は `Parent#initialize` 内で `@child` に値が代入される前に `self` を使っちゃったせいで，`@child` が nilable だ（値として `nil` も取りうる）と判断されてしまったせいらしい。（Crystal のエラーメッセージには，どの変数が `nil` だったのか，とか `initialize` のどの辺りで `self` が使われているのか，とかまで詳しく出力してくれるので，ちゃんと読めば問題箇所の特定は比較的楽）

正確ではないかもしれないけれど，自分の理解としては，

1. `Child.new` に渡された時点では，`self` は `@child` が未定義（`nil`）な状態の **Parent** オブジェクトである。
2. もし万が一，`Child#initialize` の中で，`@parent.child` を参照されたりするとその時点では未定義な状態の `@child` が参照されうる。
3. その結果，（実際に参照されるタイミングの状態とは無関係に）`@child` の型は **Child?**（**Child | Nil**）だとCrystalは理解する。
    （上でも書いたとおり `the_child.class` は **Child** を返すんだけど，同じような動きをすると思ってた `typeof(the_child)` はちゃんと **Child?** を返してくる）
4. nilableな型をはじめとした，複数のクラスからなるunion型をもつオブジェクトは，その型に含まれるすべてのクラス（この場合は **Child** と **Nil**）が共通して持つメソッドしか利用できない。
6. 当然ながら，`Nil#parent` は定義されていない。

みたいな流れなのかなと。

なお，`Parent#initialize` 内で `self` を使う前に値が代入されている `@name` は，未定義な状態を参照されるタイミングがないので，`Parent#name` の方はなんの問題もなく **String** として扱える。

```ruby
puts the_parent.name.length # -> "4"
```

ちなみに，他のオブジェクトに渡さなくても，`self` の中身が評価されるタイミングがあるとダメらしく，こんなのも×。

```ruby
class Foo
  getter :foo

  def initialize()
    self.to_s
    @foo = "FOO!"
  end
end

puts Foo.new.foo.size # -> Error
```

```ruby
class Bar
  getter :bar

  def initialize()
    buz()
    @bar = "BAR!"
  end

  def buz()
    self.to_s
  end
end

puts Bar.new.bar.size # -> Error
```

## <i class="fa fa-question-circle"></i> 回避策

`initialize` の中での `self` の利用をできるだけ遅らせて，その前に必要なインスタンス変数の初期化をすべて終えてしまうというのが一つ目の回避策。ただし，上の例のようにインスタンス変数の初期化に `self` が出てくる場合には使えない。

二つ目の回避策は，nialbleな型のオブジェクトであっても，状況的に `nil` でないことを確定させてやれば，もう一方の（本来の）クラスのオブジェクトとして扱うことができる，というもの。

```ruby
if the_child
  # the_child が nil だとココには来ない
  puts the_child.parent.name # -> "John"
end
```

とはいえ，使う頃には `@child` には確実に値が入っている事がわかっているのに，いちいち返り値をチェクして使うのも煩わしいので，`Parent#child` の返り値が **Child** になっていてくれたほうが嬉しい。

というわけで第三の方法。
要は，「`@child` が nilable であっても `Parent#child` が **Child** オブジェクト以外返さないようにすれば良い」というわけで，「もし `@child` が　`nil` だったら例外で処理を止める」というかなり乱暴な getter メソッドを自分で定義してひとまず回避。

```ruby
class Parent
  def child
    @child || raise Exception.new
  end
end

puts the_child.parent.name # -> "John"
```

### <i class="fa fa-lightbulb-o"></i> 追記

開発者の方からコメントいただきました。

`@child` が `nil` にはなり得ない事が自明である場合に，`getter! child` とすることでまさに第三手法の動きをする getter メソッドが定義されるとのことです。

## <i class="fa fa-exclamation-circle"></i> そもそも論
`initialize` 中で `self` を使わなくても良いような設計にすべきだよね。うん，分かってる。