as¶
as
疑似メソッドはある式の型に対して制約を与えます。例をあげます。
if some_condition
a = 1
else
a = "hello"
end
# a : Int32 | String
上記のコードでは、a
は Int32 | String
のユニオン型となります。何らかの理由で if
のあとで a
が Int32
であるとしたい場合は、このようにすることでコンパイラに強制することができます。
a_as_int = a.as(Int32)
a_as_int.abs # コンパイラが a_as_int が Int32 であると把握しているので動作します
as
疑似メソッドは実行時にチェックを行うため、もし a
が Int32
ではないときには例外が発生します。
また、式に与える引数は型です。
ある型を別の型に強制することは不可能で、コンパイルエラーが発生します。
1.as(String) # コンパイル時のエラー
Note
as
を使っても、関連のない型に変換することはできません。その点では他の言語の cast
とは異なります。整数や浮動小数点数、そして文字にはこれらの変換のためのメソッドが提供されています。代替になるものとしては、以下で説明するポインタキャストを利用することができます。
ポインタ型同士の変換¶
as
疑似メソッドはポインタ型同士のキャストも可能です。
ptr = Pointer(Int32).malloc(1)
ptr.as(Int8*) # :: Pointer(Int8)
このとき、ランタイムのチェックは行われません。ポインタは安全でない (unsafe) ため、通常、この型キャストは C バインディングやローレベルなコードにおいてのみ利用します。
ポインタ型と他の型の変換¶
ポインタ型と Reference 型を相互に変換することも可能です。
array = [1, 2, 3]
# object_id はメモリ上のオブジェクトのアドレスを返すため、
# そのアドレスからポインタを作ることができる
ptr = Pointer(Void).new(array.object_id)
# ポインタをその型にキャストすると、
# 同一の値が得られる
array2 = ptr.as(Array(Int32))
array2.same?(array) # => true
この場合も、ポインタが絡む処理になるためランタイムのチェックは行われません。このキャストが必要になるケースは前述のものよりも稀です。ただ、これによって (String などの) コアとなる型を Crystal 自身で実装することが可能になっており、また、Reference 型を void ポインタにキャストすることで C の関数に渡すこともできます。
大きな型へのキャストの利用方法¶
as
疑似メソッドは、ある式をより「大きな」型へキャストするために使えます。例をあげます。
a = 1
b = a.as(Int32 | Float64)
b # :: Int32 | Float64
上記では一体何が嬉しいのかわからないかもしれません。では、以下のように配列の要素を map する場合ではどうでしょう?
ary = [1, 2, 3]
# Int32 | Float64 型の配列 1, 2, 3 にしたい
ary2 = ary.map { |x| x.as(Int32 | Float64) }
ary2 # :: Array(Int32 | Float64)
ary2 << 1.5 # OK
Array#map
メソッドはブロックの返す値の型を配列のジェネリック型とします。as
疑似メソッドがなければ、推論された型は Int32
なので、それに対して Float64
は追加されません。
コンパイラがブロックの型を推論できないときに使う¶
コンパイラがブロックの型を推論できない場合があります。これは互いに依存した再帰的な呼び出しで起こります。これらすべての場合で、as
は型を知らせるために使えます。
some_call { |v| v.method.as(ExpectedType) }