Por que generics
Logo você esbarra em tipos que deveriam funcionar para mais de um tipo de elemento. Um "par" de dois valores não se importa se os valores são inteiros, strings ou algum shape definido pelo usuário. Generics permitem que você escreva o tipo uma vez e o instancie com quaisquer tipos de elemento que o chamador precisar.
A alternativa — escrever IntPair, StringPair, BytePair e assim por diante — fica chata rápido e não compõe. Generics em Zero são a ferramenta padrão para o trabalho.
Funções genéricas
Declare parâmetros de tipo entre sinais de menor/maior entre o nome da função e a lista de parâmetros:
fun makePair<T, U>(left: T, right: U) -> Pair<T, U> {
return Pair { left: left, right: right }
}
T e U são tipos placeholder — quem chama decide o que são.
Chamadas em geral não precisam soletrá-los; o compilador infere a partir dos tipos dos argumentos:
let pair = makePair(40, 2_u8)
Aqui T é inferido como i32 (o padrão para um literal inteiro sem sufixo) e U é u8 (vindo do sufixo _u8). A ligação resultante pair tem tipo Pair<i32, u8>.
Se a inferência escolheria os tipos errados — porque os literais são ambíguos, por exemplo — você pode fixar os parâmetros no ponto de chamada:
let pair = makePair<u8, u8>(1, 2)
(Se a sintaxe de chamada com sinais de menor/maior é exatamente essa pode variar entre versões do Zero; confira a documentação atual para a grafia precisa. O comportamento "inferência primeiro" é a parte estável.)
Shapes genéricos
Shapes recebem parâmetros de tipo do mesmo jeito:
shape Pair<T, U> {
left: T,
right: U,
}
O tipo de cada campo pode mencionar os parâmetros. Instâncias fixam os parâmetros:
let intBytes: Pair<i32, u8> = Pair { left: 40, right: 2_u8 }
let words: Pair<String, String> = Pair { left: "hi", right: "there" }
Um exemplo combinando um shape genérico e uma função genérica — clique em Run para ver a inferência trabalhando:
Uma declaração de shape genérico, uma função genérica, um ponto de chamada com tipos fortes. Sem boilerplate de IntBytePair.
Type aliases
Quando o mesmo tipo parametrizado aparece muitas vezes, dê um nome com type:
type BytePair = Pair<u8, u8>
Agora BytePair é intercambiável com Pair<u8, u8> em qualquer lugar onde você possa escrever um tipo:
Um alias é só uma funcionalidade de nomenclatura — não cria um tipo distinto. Uma função que recebe um BytePair aceita tranquilamente um valor de tipo Pair<u8, u8> (e vice-versa).
Generics na biblioteca padrão
O mesmo mecanismo é a base de boa parte da biblioteca padrão. Alguns que você vai ver em código Zero real:
Maybe<T>— um valor opcional, carregando umTou nada.Span<T>— uma fatia emprestada sobre valoresT.Span<u8>é a visão canônica sobre um buffer de bytes.ref<T>emutref<T>— tipos de referência explícitos para quando você precisa compartilhar dado sem copiar.
Você não precisa aprender todos de uma vez. O ponto dos generics é que o mesmo shape funciona para qualquer tipo de elemento que você tenha à mão.
Quando generics compensam (e quando não)
Recorra a generics quando se ver escrevendo a mesma função ou shape duas vezes com tipos de elemento diferentes. Recorra a um tipo concreto quando:
- A lógica da função só faz sentido para um tipo específico (um parser para
Strings, digamos). - Você quer que o tipo apareça em mensagens de erro para facilitar a depuração.
- As características de desempenho dependem de um tamanho específico em memória.
O custo dos generics é real — binários maiores (cada instanciação gera código novo) e tempos de compilação um pouco mais lentos. Para a maior parte do código de aplicação, esse custo é desprezível, mas vale saber quando você está construindo código compacto, estilo embarcado, em que o tamanho do binário importa.
Uma nota sobre constraints
Alguns sistemas de generics permitem restringir um parâmetro de tipo ("T precisa suportar ==", "T precisa implementar Iterator"). A história de constraints do Zero pré-1.0 ainda está evoluindo — os exemplos no repo oficial usam generics na forma simples, sem bounds elaborados. Conforme a linguagem assenta, espere que sintaxe de constraint chegue numa forma pequena e regular, consistente com o resto da linguagem. Por enquanto, escreva generics que funcionam para qualquer T que você de fato passa, e deixe o compilador te dizer quando uma operação não é suportada.
A seguir: enums
Generics permitem parametrizar sobre tipos. O próximo bloco de construção fica no outro extremo do espectro — enums, o tipo enumeração simples do Zero para casos em que as variantes não carregam dado extra.
Perguntas frequentes
Como generics funcionam em Zero?
Declare parâmetros de tipo entre sinais de menor/maior depois do nome da função ou shape: fun makePair<T, U>(left: T, right: U) -> Pair<T, U> ou shape Pair<T, U> { left: T, right: U }. Quem chama ou fixa os parâmetros explicitamente (Pair<i32, u8>) ou deixa o compilador inferir a partir dos argumentos da chamada.
Shapes podem ser genéricos em Zero?
Podem. Um shape pode receber parâmetros de tipo na mesma sintaxe de sinais de menor/maior usada em funções: shape Pair<T, U> { left: T, right: U }. Cada campo pode usar os parâmetros no tipo. Instâncias são formadas escrevendo o tipo parametrizado — Pair<i32, u8>, por exemplo.
Você precisa especificar os parâmetros de tipo ao chamar uma função genérica?
Normalmente não. O compilador infere a partir dos tipos dos argumentos. Chamar makePair(40, 2_u8) é suficiente — T vira i32 e U vira u8. Você pode fixar os parâmetros explicitamente quando a inferência escolheria o tipo errado ou quando quer que o ponto de chamada documente os tipos.
O que é um type alias em Zero?
Um type alias é um nome curto para uma expressão de tipo mais longa. type BytePair = Pair<u8, u8> deixa você escrever BytePair em qualquer lugar onde escreveria Pair<u8, u8>. O alias é uma funcionalidade puramente de nomenclatura — ele não introduz um tipo novo, só uma forma mais curta de se referir a um existente.
Onde generics aparecem na biblioteca padrão do Zero?
Em todo lugar — sempre que um tipo precisa segurar ou operar sobre um tipo arbitrário de elemento. Maybe<T> para valores opcionais, Span<u8> para fatias de bytes, tipos contêineres parametrizados sobre o elemento. O mesmo mecanismo de generics atende tipos do usuário e da biblioteca padrão.