as
as
是一個虛擬方法(Pseudo-method),能夠限制一個表達式的型別。例如:
if some_condition
a = 1
else
a = "hello"
end
# a : Int32 | String
上述的程式碼中,a
的型別是一個 Int32 | String
的型別集合。
假定 a
在執行 if
之後是一個 Int32
,那麼我們可以強制編譯器將其視為一個 Int32
:
a_as_int = a.as(Int32)
a_as_int.abs # 可行,編譯器知道 a_as_int 是 Int32
as
會在執行時期檢查 —— 如果 a
並不是一個 Int32
,那麼將會喚起一個例外。
這個表達式的參數為一型別。
另外,不能夠將任一種型別限制為另一種型別,這將在編譯時期發出錯誤:
1.as(String) # Compile-time error
注意: 我們不能使用 as
將一個型別轉換為另一個不相關的型別 —— as
並不像其他語言裡的 cast
(轉型)。而在整數、浮點數與字元中有另外提供轉型的方法,或著我們也能照下方說明的指標來轉型。
指標之間的轉型
as
也允許指標之間的型別轉換:
ptr = Pointer(Int32).malloc(1)
ptr.as(Int8*) # :: Pointer(Int8)
在上方的例子中,並沒有進行檢查 —— 指標非常的不安全,而這類型的轉型通常只在 C 語言繫結(Binding)或是更底層的程式碼中才需要。
指標型別與其他型別之間的轉換
我們也可以在指標型別與參考型別之間進行轉換,如:
array = [1, 2, 3]
# object_id 回傳一個物件在記憶體中的位址,
# 我們可以建立一個指向該位址的指標
ptr = Pointer(Void).new(array.object_id)
# 再來我們就可以把指標轉型為原來的型態,
# 那麼我們應該要得到相同的值
array2 = ptr.as(Array(Int32))
array2.same?(array) #=> true
再次強調,由於指標的關係,上述例子中並不會進行檢查。
這種轉型其實比前一種例子更罕見,但這讓一些 Crystal 裡的核心型別(如字串)得以實作,而且也允許透過轉換為 void 指標後傳遞一個參考型別給 C 函數。
轉型為更大的型別
as
也可以被用於轉換為更大的型別,例如:
a = 1
b = a.as(Int32 | Float64)
b # :: Int32 | Float64
上面的範例看起來可能不是很實用,但卻在對應陣列中的元素時非常好用:
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
元素進去。
當編譯器無法推斷區塊的型別時
Sometimes the compiler can't infer the type of a block. This can happen in recursive calls that depend on each other. In those cases you can use as
to let it know the type:
some_call { |v| v.method.as(ExpectedType) }