Na primeira vez que você encontra os colchetes angulares <T> em código Swift, para por um instante. Há uma letra maiúscula ao lado do nome da função e, ao abrir a documentação, uma assinatura como func map<T>(_ transform: (Element) -> T) -> [T] está esperando. Genéricos são a porta de entrada para o Swift intermediário. Quase tudo na biblioteca padrão (incluindo Array, Dictionary e Optional) é feito com genéricos; só depois de superar essa barreira você começa a entender o código da biblioteca.
Esta é a segunda parte da série intermediária. Vamos resumir quais problemas os genéricos resolvem, quando usar restrições e cláusulas where e como fica o desempenho.
O problema resolvido pelos genéricos — rejeitando a escolha entre duplicação e segurança de tipos
Em um mundo sem genéricos, basta tentar criar uma função que troque dois valores para o problema aparecer.
Uma versão para Int não funciona com String. Copiar o código para cada tipo aumenta a duplicação. A alternativa parece ser uma caixa que aceite qualquer tipo: Any, em Swift. Só que Any apaga as informações de tipo. Cada extração exige conversão, e o erro de inserir um Int e extrair um String passa pela compilação para virar um crash em tempo de execução.
Em resumo, só restavam Any, sem duplicação mas arriscado, ou cópias por tipo, seguras mas duplicadas. Os genéricos rejeitam essa escolha.
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
<T> declara um espaço para preencher o tipo depois. No momento da chamada, T é definido como um tipo concreto, e o compilador verifica tudo usando esse tipo. Uma chamada incompatível como swapValues(&intA, &strB) gera erro de compilação. O código é único, mas a verificação de tipos ocorre por tipo: essa é a essência dos genéricos.
Isso também vale para tipos. Ao declarar struct Stack<Element>, Stack<Int> e Stack<String> se tornam tipos diferentes, e o compilador impede inserir uma string em uma pilha de Int. Array tem exatamente essa estrutura; Optional, como vimos na parte sobre optionals, também era um enum genérico chamado enum Optional<Wrapped>. Na prática, você já usava genéricos todos os dias.
Restrições — de «qualquer tipo» para «um tipo com estas capacidades»
Por padrão, o espaço T aceita qualquer tipo. Mas isso também significa não poder fazer nada: como o compilador não sabe nada sobre T, não é possível comparar, imprimir ou somar.
func largest<T>(_ items: [T]) -> T? {
items.max(by: <) // erro de compilação — Tnão há garantia de que seja comparável
}
É aqui que entram as restrições. Ao escrever <T: Comparable>, você exige que T seja um tipo que adote Comparable; a partir daí, pode usar < com T.
func largest<T: Comparable>(_ items: [T]) -> T? {
items.max()
}
Restrição não é perda, mas uma troca. Você reduz os tipos aceitos e aumenta o que pode fazer com eles. A ideia de combinar capacidades, vista na parte sobre protocolos, encontra os genéricos justamente aqui, porque as restrições usam protocolos.
Quando as condições ficam complexas, usamos uma cláusula where. O significado é o mesmo; where se torna obrigatório para impor condições aos tipos associados dos parâmetros de tipo.
// Elementcomparação apenas entre coleções cujo Equatableseja igual
func allEqual<C: Collection>(_ items: C) -> Bool where C.Element: Equatable {
guard let first = items.first else { return true }
return items.allSatisfy { $0 == first }
}
C.Element indica o tipo de elemento contido por uma coleção: o tipo associado (associated type). Esse será o tema de uma próxima parte; por enquanto, basta entender que where é o lugar onde essa condição é imposta.
Na prática, aplique apenas as restrições necessárias. Se Comparable basta, exigir Hashable também reduz os tipos que podem ser usados. A lista exata deve refletir as capacidades realmente usadas pelo corpo da função.
Desempenho — o compilador elimina o custo da abstração
À pergunta «genéricos não são lentos?», Swift responde com uma otimização marcante: especialização.
Em princípio, uma função genérica não sabe qual tipo receberá, então precisa carregar informações de tipo em tempo de execução e operar indiretamente. Mas, se o compilador consegue ver o ponto de chamada, ele gera uma versão específica para swapValues<Int>. O código de máquina é igual ao de uma versão para Int escrita à mão. Usar a abstração genérica não exige pagar custo em tempo de execução: o compilador a remove e produz código concreto. É um exemplo clássico de abstração sem custo, como vimos na primeira parte sobre filosofia.
A otimização funciona bem dentro do mesmo módulo, mas sofre limitações ao cruzar fronteiras de módulo (isso depende da forma de distribuição da biblioteca). A conclusão prática é não evitar genéricos por medo de desempenho; gargalos devem ser encontrados por medição. Já tratamos otimização prematura em outro texto.
Surge uma pergunta natural: «Se eu receber um tipo de protocolo (como items: [Comparable]), qual é a diferença para um genérico?». Esse é o tema da próxima parte. Genéricos oferecem polimorfismo estático, com o tipo definido em tempo de compilação; tipos de protocolo (existential) oferecem polimorfismo dinâmico, misturando tipos em tempo de execução. Também veremos por que Swift explicita essa diferença com some e any.
Quando criar genéricos — critérios práticos
Ler genéricos e projetá-los são habilidades diferentes; por isso, vamos resumir os critérios para criá-los.
é um sinal quando a mesma lógica aparece pela segunda vez, mudando apenas o tipo. Começar com genéricos pensando «um dia outro tipo vai aparecer» geralmente é overengineering. Siga YAGNI (You Aren’t Gonna Need It — não crie até precisar): comece com um tipo concreto e generalize quando surgir duplicação real.
** combina melhor com estruturas e algoritmos do que com conceitos de domínio.** Caches, respostas paginadas e pilhas, independentes do conteúdo, são o habitat natural dos genéricos. Como APIResponse<User> e Cache<ImageKey, UIImage>. Já transformar à força uma lógica de domínio específico, como pagamento de pedidos, só complica a assinatura.
** é sinal de recuo quando a complexidade da assinatura supera o benefício para quem a usa.** Se houver três ou quatro parâmetros de tipo e três linhas de where, vale perguntar se os colegas conseguem ler a chamada. O princípio de Progressive Disclosure também se aplica: a declaração deve absorver a complexidade, que não pode vazar para o uso.
Resumo
- Genéricos rejeitam a escolha entre reutilização sem duplicação e segurança de tipos. O código é único, mas a verificação de tipos ocorre por tipo.
<T>declara um espaço para o tipo; restrições (T: Comparablee cláusulas where) estreitam esse espaço em troca de ampliar as operações disponíveis.- Graças à especialização, genéricos geralmente têm o mesmo desempenho de código concreto escrito à mão.
- Critérios: generalize quando surgir a segunda duplicação, use em estruturas e algoritmos e pare se a complexidade da assinatura superar o benefício.
A próxima parte aborda a sintaxe irmã dos genéricos e uma das maiores fontes de confusão do Swift atual: some e any. Vamos explorar o que é some View e o custo de um existential.

![Imagem de capa de [Swift intermediário #2] Elimine duplicação e riscos com genéricos](/assets/images/posts/7d69dc2a-8c58-4422-956e-940173ebaf99/1.jpg)