Trilhas
/
Crystal
Crystal
/
Programa
/
Tipo união
Ti

Tipo união em Crystal

1 exercício

Sobre Tipo união

O Crystal permite que uma variável tenha múltiplos tipos. Isso é chamado de união de tipos. No Crystal, é comum o compilador inferir uma união de tipos.

Note

Uma união de tipos ainda é um único tipo em tempo de execução, mesmo que seja composta de vários tipos. Isso significa que, se uma união de tipos for formada por String e Int32, ela não será os dois tipos ao mesmo tempo. Em vez disso, ela será ou uma String ou um Int32.

Uma união de tipos é declarada separando os tipos com um pipe (|). Elas costumam vir entre parênteses, mas isso não é obrigatório. A união de tipos mais comum é (T | Nil), em que T é um tipo, e pode aparecer em métodos que podem retornar Nil. Isso também é chamado de uma variável nilable.

foo : String = "Hello"
foo = nil # Error: type must be String, not (String | Nil)

foo : (String | Nil) = "Hello"
foo = nil

Ela não se limita a apenas dois tipos, mas pode ter quantos você quiser.

foo : (String | Int32 | Nil | Float64) = "Hello"
foo = 1
foo = nil
foo = 1.0

typeof vs Object#class

Há duas formas de obter o tipo de uma variável. Seja usando typeof, seja usando Object#class. A diferença é que typeof retorna o tipo de uma variável em tempo de compilação, enquanto Object#class retorna o tipo em tempo de execução. Isso significa que, se você quiser verificar se uma variável é uma união de tipos, por exemplo, Object#class não conseguirá te dizer isso, pois só retorna o tipo em tempo de execução, que é um único tipo.

foo = 0 == 0 ? "a" : 1
typeof(foo) # => (String | Int32)
foo.class # => String

Operações com uniões de tipos

Como uma união de tipos é um único tipo em tempo de execução, todas as operações padrão funcionam nela. Mas, ao compilar o código, o compilador precisa saber de qual tipo se trata. Por isso, o código precisa ser estruturado de forma que só possa ser de um dos tipos quando você quiser usar as operações específicas de cada tipo.

foo : (String | Int32) = "Hello"
foo.downcase # Error: undefined method 'downcase' for (String | Int32)

O Crystal tem um método específico para uniões de tipos: o método is_a?, que recebe um tipo como argumento e retorna um Boolean. O método nil? é um atalho para is_a?(Nil). Colocar o método is_a? em uma expressão de controle dirá ao compilador qual é o tipo e, assim, garantirá que ele seja esse tipo. E, para uma cláusula else, ficará garantido que não é esse tipo.

foo : (String | Int32) = "Hello"
if foo.is_a?(String)
  typeof(foo)  # => String
  foo.downcase # => "hello"
end

Esse is_a? não se limita a receber um único tipo como argumento, ele também pode receber uma união de tipos. Ele também pode ser combinado com && para vários tipos.

Note

O método is_a?, quando usado em conjunto com uma expressão de controle, não pode ser uma variável de instância nem uma variável de classe. Nesse caso, elas precisam primeiro ser atribuídas a uma variável local.

as

Uma forma de transformar uma união de tipos em um único tipo é fazer com que um bloco só possa ser executado se o tipo for um tipo específico. Outra abordagem é usar o método as. Isso transforma uma união de tipos em um único tipo fazendo uma verificação em tempo de execução. Uma exceção será lançada se o tipo não for o tipo esperado.

foo : String | Int32 = "Hello"
foo.as(String).downcase # => "hello"

foo.as(Int32) # Error: can't cast String to Int32
Caution

Essa abordagem serve apenas para quando você tem certeza de que o tipo é o esperado, ou para quando você quer lançar uma exceção caso não seja.

Usar essa abordagem com uma configuração inadequada pode levar a um comportamento inesperado.

as?

as? funciona de forma muito parecida com as, mas retorna nil em vez de lançar uma exceção se o tipo não for o esperado. Isso significa que ele retorna uma união de tipos do tipo esperado e Nil.

foo : (String | Int32) = "Hello"
foo.as?(String).downcase # => "hello"

foo.as?(Int32) # => nil

Atalho para nilable

Nilable significa que uma variável pode ser um tipo ou Nil. Isso pode ser escrito como (T | Nil). Mas, como tipos nilable são relativamente comuns, existe um atalho para isso: T?.

# This:
foo : (String | Nil) = "Hello"
foo = nil

# Is the same as:
foo : String? = "Hello"
foo = nil

Inferindo uniões de tipos

O compilador infere uma união de tipos quando não tem certeza de qual tipo se trata. Por exemplo, no código a seguir o compilador não saberá qual é o tipo de foo, já que ele pode ser uma String ou um Int32.

if true
  foo = 1
else
  foo = "Hello"
end

typeof(foo) # => (Int32 | String)

Essa inferência acontece automaticamente, e há outros cenários em que, por exemplo, o compilador vai inferir que um método retorna uma união de tipos.

character = "Hello world"[10]?
typeof(character) # => (Char | Nil)
Editar via GitHub O link abre em uma nova janela ou aba

Aprenda Tipo união

A prática está bloqueada

Desbloqueie mais 1 exercício para praticar Tipo união