Объединённые типы
Тип переменной или выражения может состоять из нескольких типов. Это называется объединённым типом. Например, при присваивании одной и той же переменной в разных ветвях if:
if 1 + 2 == 3
a = 1
else
a = "hello"
end
a # : Int32 | String
В конце if a будет иметь тип Int32 | String, что читается как «объединение Int32 и String». Этот объединённый тип создаётся компилятором автоматически. Во время выполнения a, конечно, будет иметь только один тип. Это можно увидеть, вызвав метод class:
# The runtime type
a.class # => Int32
Тип во время компиляции можно увидеть с помощью typeof:
# The compile-time type
typeof(a) # => Int32 | String
Объединение может состоять из произвольного количества типов. При вызове метода для выражения с объединённым типом все типы в объединении должны поддерживать этот метод, иначе возникнет ошибка во время компиляции. Тип вызова метода представляет собой объединение типов возвращаемых значений этих методов.
# to_s is defined for Int32 and String, it returns String
a.to_s # => String
a + 1 # Error, because String#+(Int32) isn't defined
При необходимости переменную можно определить как объединённый тип во время компиляции
# set the compile-time type
a = 0.as(Int32 | Nil | String)
typeof(a) # => Int32 | Nil | String
Правила объединённых типов
В общем случае при объединении типов T1 и T2 результатом становится объединение T1 | T2. Однако в некоторых случаях результирующий тип отличается.
Объединение классов и структур в одной иерархии
Если T1 и T2 находятся в одной иерархии, а их ближайший общий предок Parent не является Reference, Struct, Int, Float или Value, результирующим типом будет Parent+. Это называется виртуальным типом и, по сути, означает, что компилятор будет воспринимать тип как Parent или любой из его подтипов.
Например:
class Foo
end
class Bar < Foo
end
class Baz < Foo
end
bar = Bar.new
baz = Baz.new
# Here foo's type will be Bar | Baz,
# but because both Bar and Baz inherit from Foo,
# the resulting type is Foo+
foo = rand < 0.5 ? bar : baz
typeof(foo) # => Foo+
Объединение кортежей одинакового размера
Объединение двух кортежей одинакового размера приводит к типу кортежа, в котором на каждой позиции находится объединение типов.
Например:
t1 = {1, "hi"} # Tuple(Int32, String)
t2 = {true, nil} # Tuple(Bool, Nil)
t3 = rand < 0.5 ? t1 : t2
typeof(t3) # Tuple(Int32 | Bool, String | Nil)
Объединение именованных кортежей с одинаковыми ключами
Объединение двух именованных кортежей с одинаковыми ключами (независимо от их порядка) приводит к типу именованного кортежа, в котором для каждого ключа используется объединение типов. Порядок ключей будет таким же, как в кортеже слева.
Например:
t1 = {x: 1, y: "hi"} # Tuple(x: Int32, y: String)
t2 = {y: true, x: nil} # Tuple(y: Bool, x: Nil)
t3 = rand < 0.5 ? t1 : t2
typeof(t3) # NamedTuple(x: Int32 | Nil, y: String | Bool)
To the extent possible under law, the persons who contributed to this workhave waived
all copyright and related or neighboring rights to this workby associating CC0 with it.
https://crystal-lang.org/reference/1.2/syntax_and_semantics/union_types.html