コンテンツにスキップ

as

as 疑似メソッドはある式の型に対して制約を与えます。例をあげます。

if some_condition
  a = 1
else
  a = "hello"
end

# a : Int32 | String

上記のコードでは、aInt32 | String のユニオン型となります。何らかの理由で if のあとで aInt32 であるとしたい場合は、このようにすることでコンパイラに強制することができます。

a_as_int = a.as(Int32)
a_as_int.abs # コンパイラが a_as_int が Int32 であると把握しているので動作します

as 疑似メソッドは実行時にチェックを行うため、もし aInt32 ではないときには例外が発生します。

また、式に与える引数はです。

ある型を別の型に強制することは不可能で、コンパイルエラーが発生します。

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) }