Swift e Objective-C

[Fundamentos de Swift #5] Como escolher entre throws, try e Result

As ferramentas de tratamento de erros do Swift têm papéis diferentes conforme o momento em que a falha é tratada e as informações que precisam ser preservadas. Este guia organiza as escolhas entre throws, do-catch, try?, try! e Result junto com os resultados da execução.

8 min de leitura
Imagem de capa de [Fundamentos de Swift #5] Como escolher entre throws, try e Result

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.

As três variações de try declaram uma atitude em relação aos erros
As três variações de try declaram uma atitude em relação aos erros

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.

throws é uma bola que você precisa pegar imediatamente; Result é um valor dentro de uma caixa
throws é uma bola que você precisa pegar imediatamente; Result é um valor dentro de uma caixa

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

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