Ao aprender o tratamento de erros no Swift, é comum ter a impressão de que há ferramentas demais: throws e do-catch, o ponto de interrogação e o ponto de exclamação ligados a try, além do tipo Result. Algumas APIs lançam erros, outras retornam Result, e certos códigos simplesmente engolem tudo com try?. Qual é o padrão?
Na verdade, essas ferramentas não competem entre si; elas dividem responsabilidades. O padrão é throws, as variações de try formam um espectro de quanto você se importa com os erros, e Result complementa o modelo quando o erro precisa ser transportado como valor. Na quinta parte da série Fundamentos de Swift, este artigo desenha esse mapa.
A estrutura que fecha primeiro os caminhos de falha na validação de entrada se conecta a Como escolher entre Swift guard e saída antecipada anterior.
Fundamentos — Erros são tipos, e lançar erros é um contrato
O tratamento de erros do Swift começa com duas declarações. Defina o erro como um tipo que adota o protocolo Error e adicione throws à assinatura das funções que podem falhar.
enum PaymentError: Error {
case insufficientBalance(needed: Int)
case cardExpired
case network(underlying: Error)
}
func pay(amount: Int) throws -> Receipt {
guard balance >= amount else {
throw PaymentError.insufficientBalance(needed: amount - balance)
}
// ...
}
Não é por acaso que enum é usado com frequência para tipos de erro. Como vimos no artigo sobre opcionais, enum expressa um conjunto de casos, e os motivos de falha são exatamente isso. Valores associados também podem carregar informações de contexto, como um valor insuficiente.
Mais importante: throws está na assinatura. O sistema de tipos registra que essa função pode falhar. Por isso, o chamador precisa usar try, e omiti-lo gera um erro de compilação. Diferentemente de linguagens em que não se sabe onde uma exceção pode ocorrer, o código Swift marca com try todos os pontos que podem falhar. Assim como os opcionais elevam a “ausência de valor” ao tipo, throws eleva a “possibilidade de falha” à assinatura. O princípio de segurança em primeiro lugar da parte 1 se repete aqui.
A forma básica no lado receptor é do-catch.
do {
let receipt = try pay(amount: 50_000)
show(receipt)
} catch PaymentError.insufficientBalance(let needed) {
showTopUp(needed: needed)
} catch {
showError(error) // todo o restante, error variável fornecida automaticamente
}
catch usa pattern matching. Você pode capturar apenas casos específicos, extrair valores associados e receber o restante no último catch, de forma parecida com switch. É um design que funciona em conjunto com o fato de os erros serem enums.
As três faces de try — um espectro de atenção aos erros
try tem três formas, e cada uma declara como você quer lidar com os erros.
try — Vou tratar ou propagar. É a forma padrão. Capture com do-catch ou declare sua própria função como throws e deixe o erro subir. A propagação de erros acontece automaticamente, sem código explícito, e essa é uma das vantagens ocultas do tratamento de erros do Swift. As funções das camadas intermediárias só precisam de throws para fazer o encanamento de graça; o tratamento pode acontecer uma única vez em uma camada externa, mais próxima da UI.
try? — Se falhar, tudo bem; só preciso que não haja valor. Ele converte o erro em um opcional: um valor em caso de sucesso e nil em caso de falha, descartando as informações do erro. É adequado para cenários de “se não der, tudo bem”, como ler um cache. O perigo é usar try? por hábito. Quando o motivo da falha importa, try? faz as pistas de depuração desaparecerem silenciosamente. Use-o somente quando puder responder sim à pergunta “Ninguém precisa saber por que isso falhou?”
try! — A falha é um bug do programador. Em caso de falha, ocorre um crash imediato. Pela mesma lógica do forced unwrapping !, ele só é permitido quando falhar significa que o código está errado, como ao carregar um recurso incluído no bundle do app. O critério do artigo sobre opcionais se aplica integralmente: se nil — aqui, um erro — for um cenário normal, nunca use.
Result — Transportando erros como valores
Se throws é o padrão, quando usar Result? Result é um enum que contém sucesso ou falha.
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
A diferença decisiva em relação a throws está no tempo e no local. throws força o tratamento — ou a propagação — imediatamente na chamada, enquanto Result é um valor comum que pode ser armazenado, colocado em um array e processado depois. Por isso, há aproximadamente três situações adequadas para Result.
Primeiro, APIs assíncronas baseadas em completion handler. Como vimos no artigo sobre closures, o completion handler é executado depois que a função retorna, então não pode transmitir um erro com throws. completion: (Result<Data, NetworkError>) -> Void era o padrão para esse caso. Segundo, quando é preciso reunir resultados. Para executar 10 tarefas e contabilizar 7 sucessos e 3 falhas, os erros precisam ser valores. Terceiro, quando você quer especificar o tipo de falha. Failure de Result é um tipo concreto, então a assinatura mostra que tipo de erro pode chegar.
Ainda assim, é importante conhecer a direção. Desde que async/await se tornou padrão, a transmissão de erros assíncronos passou a ser responsabilidade de async throws, e o primeiro uso de Result está diminuindo em código novo. O terceiro uso também está sendo absorvido pelo typed throws do Swift 6 (throws(PaymentError), SE-0413). Portanto, o critério prático atual é: use throws por padrão; use Result em situações específicas nas quais seja necessário armazenar, reunir ou transmitir o erro como valor.
A propósito, a conversão entre os dois cabe em uma linha. Envolva com Result { try pay(amount: 100) } e extraia com try result.get(). Como é possível transitar livremente entre eles nas fronteiras, não há motivo para insistir em apenas um.
Sensibilidade de design — Bons erros consideram quem os recebe
Vamos sair um pouco da sintaxe. A qualidade do código de tratamento de erros é determinada, em grande parte, pelo design do lado que lança o erro.
Divida os erros em unidades que permitam comportamentos diferentes por parte do chamador. O critério para dividir os casos é: “O receptor trata estes dois de forma diferente?” Saldo insuficiente e cartão expirado devem ser casos distintos, pois a orientação ao usuário muda. Já timeout de TCP e falha de DNS podem ser agrupados em network se o app fizer exatamente o mesmo retry. Dividir em dez erros casos com o mesmo tratamento só aumenta a quantidade de catch.
O critério para lançar um erro ou retornar um opcional é o mesmo. Se “não existir” for um resultado cotidiano e esperado, como em uma busca em dicionário, use um opcional; se algo deu errado e o motivo for necessário, use throws. Quando existe apenas um motivo óbvio para a falha, muitas vezes um opcional é suficiente.
Projete as mensagens exibidas ao usuário junto com o tipo de erro. Ao adotar LocalizedError, o próprio erro pode carregar uma mensagem para exibição; esse tema é detalhado em outro artigo.
Resultado confirmado por execução direta
Executei no Apple Swift 6.3.3 uma função throwing que converte uma string em inteiro. Ao receber a falha com try?, restou apenas nil; ao capturar o sucesso com Result, consegui reconectá-lo depois a um fluxo throwing usando get().
error=try?-nil:true,result:42
Por causa dessa diferença, não uso try? quando o motivo da falha pode alterar logs, retries ou orientações ao usuário. Já em fronteiras como consultas ao cache, nas quais falha e ausência são tratadas da mesma forma, converter para nil expressa melhor a intenção. Escolho Result apenas para armazenar resultados que não precisam de tratamento imediato ou para reunir resultados de várias tarefas.
Resumo
- A estrutura do tratamento de erros no Swift é o protocolo Error — geralmente um enum —, uma assinatura throws e pattern matching com do-catch. A possibilidade de falha fica registrada no sistema de tipos, e todos os pontos de falha são marcados com try.
- As três variações de try declaram uma atitude em relação aos erros: try para tratar ou propagar, try? quando o motivo não importa e try! quando falhar significa um bug.
- A propagação de erros é automática. As camadas intermediárias apenas adicionam throws, e o tratamento acontece uma vez na camada externa.
- Result é a ferramenta para armazenar ou reunir erros como valores. A transmissão assíncrona está migrando para async throws, e a especificação explícita do tipo está migrando para typed throws.
- O ponto central do design é dividir os casos de erro em unidades nas quais o chamador se comporte de maneira diferente.
Com isso, a trilogia de fluxo de controle da série Fundamentos de Swift — opcionais, guard e tratamento de erros — está completa. O próximo artigo aborda um fundamento de outro tipo: por que a String do Swift é especialmente difícil em comparação com outras linguagens, começando por explicar por que “quantos caracteres há em coreano?” não é uma pergunta simples.
Continue lendo
- Padrão de bridge no Swift: separando abstrações para evitar a explosão de subclasses (compilação completa de exemplos)
- [Fundamentos de Swift #3] Guia completo das propriedades do Swift: 4 critérios para escolher entre stored, computed, lazy e didSet
- [Fundamentos de Swift #6] Por que Swift String não pode virar text[0]? Guia completo de grapheme clusters (Grapheme Cluster)
Fontes e verificação
- The Swift Programming Language: Error HandlingSwift.org · Documentação oficial · Consultado 26 de agosto de 2026Evidência: Como tratar Error, throw, throws, do-catch e try·try?·try!
- Swift ResultApple Developer Documentation · Documentação oficial · Consultado 26 de agosto de 2026Evidência: APIs de conversão entre as representações success·failure de Result e expressões throwing

![Imagem de capa de [Fundamentos de Swift #5] Como escolher entre throws, try e Result](/assets/images/posts/79164702-137a-40ab-b9c5-28127ec6c2df/1.jpg)