Swift e Objective-C

[Swift avançado #8] ~Copyable, propriedade e tipos não copiáveis

Recursos como identificadores de arquivo e locks só fazem sentido quando existe uma única instância, portanto não devem ser copiados. Veja como marcar a não copiabilidade no tipo com ~Copyable do Swift 5.9, além de borrowing, consuming e inout e seus usos.

6 min de leitura
Imagem de capa de [Swift avançado #8] ~Copyable, propriedade e tipos não copiáveis

No capítulo sobre tipos por valor, resumimos o comportamento padrão do Swift como “cópia”: atribuir copia, e passar para uma função também copia.

Este conteúdo dá continuidade ao artigo anterior Swift avançado #7.

Mas e se existirem valores que não podem ser copiados? Recursos que só fazem sentido quando há “exatamente um no mundo”, como identificadores de arquivo, locks de mutex e tokens de transferência bancária.

O tema desta edição da série avançada é propriedade (ownership). O ~Copyable do Swift 5.9 (tipos não copiáveis), borrowing e consuming abrem essa porta. SE-0390: Noncopyable Structs and Enums

Quem conhece Rust vai reconhecer a ideia. Sim: o Swift importou os conceitos centrais do Rust à sua maneira.

Mas a direção é diferente. No Rust, propriedade é o padrão e não há exceções; no Swift, cópia é o padrão e propriedade é uma ferramenta opcional.

A lacuna de um mundo em que copiar é o padrão

Todos os tipos do Swift são Copyable por padrão. Mesmo sem declarar isso, o compilador faz a adoção implícita do protocolo.

É daí que vem a semântica de cópia de atribuições e passagens vista no capítulo de tipos por valor. Para a maioria dos valores — números, strings e coordenadas — é um padrão perfeito.

O problema está nos tipos que representam recursos. Pense em um struct que envolve um descritor de arquivo.

struct FileHandle {
    let fd: Int32
    func close() { /* fd Fechar */ }
}

let a = FileHandle(fd: open("data.txt"))
let b = a          // Cópia — agora dois conhecem o mesmo fd
a.close()
b.close()          // Fechar novamente um fd já fechado — comportamento indefinido

No momento em que o identificador é copiado, fica ambíguo “quem é responsável por fechá-lo”. A cópia indevida está na raiz de bugs de recursos, como liberação dupla e uso de identificadores fechados.

A solução tradicional era usar uma classe e fechar em deinit, centralizando o gerenciamento em uma única referência.

Funciona, mas há os custos vistos no capítulo sobre ARC (Automatic Reference Counting, contagem automática de referências): heap e contagem de referências.

Além disso, o compilador ainda não conhece a regra “não pode copiar”.

~Copyable — gravando a não copiabilidade no tipo

A resposta do Swift 5.9 são os tipos não copiáveis (proposta Swift Evolution SE-0390). O ~Copyable com til é uma declaração de que “não adota Copyable”. SE-0390: Noncopyable Structs and Enums

struct FileHandle: ~Copyable {
    let fd: Int32

    consuming func close() { /* fd Fechar */ }
    deinit { /* Fechar aqui se ainda não tiver sido fechado */ }
}

let a = FileHandle(fd: open("data.txt"))
let b = a          // Não é cópia, é movimento — a propriedade passa para b
// print(a.fd)     // Erro de compilação: a já foi consumido

O comportamento muda fundamentalmente. A atribuição passa a ser um movimento (move), não uma cópia, e a variável cuja propriedade foi transferida não pode mais ser usada.

A violação gera um erro de compilação, não um crash em runtime. O compilador mantém um registro da regra de que “este valor sempre tem exatamente um proprietário”.

Assim como Optional transformou nil e Sendable transformou condições de corrida em problemas de tipo, a unicidade dos recursos também vira um problema de tipo.

Há ainda um bônus: um struct pode declarar deinit, então a limpeza determinística é executada quando o proprietário sai do escopo.

Isso permite gerenciamento de recursos no estilo RAII (Resource Acquisition Is Initialization) sem classes.

Diagrama comparando bugs de liberação dupla causados por cópia com uma estrutura que os impede por meio de movimentos
A classe de bugs chamada liberação dupla vira um erro de compilação

borrowing e consuming — três formas de passar valores para funções

Ao passar um valor não copiável para uma função, surge uma nova pergunta. Como não é possível copiá-lo, você precisa decidir se vai emprestá-lo ou transferi-lo.

Os modificadores de parâmetro expressam essa escolha.

borrowing significa empréstimo. A função apenas lê o valor, e a propriedade continua com o chamador.

O chamador pode continuar usando o valor depois que a função retorna. func checksum(of handle: borrowing FileHandle) -> Int É o lugar de operações somente de leitura como esta.

consuming significa transferência. A propriedade passa para a função, e o chamador não pode mais usar o valor. O consuming func close() acima tem exatamente esse significado.

Usar o identificador depois de chamar close vira um erro de compilação. É o momento em que a classe de bugs “usar novamente um identificador fechado” desaparece da sintaxe.

Também é uma ferramenta para modelar conceitos de domínio que devem desaparecer quando usados, como tokens de transferência bancária e tickets de uso único.

inout mantém o comportamento conhecido: emprestar e modificar. Juntos, os três completam o vocabulário de propriedade dos parâmetros: apenas ler (borrowing), levar (consuming) e modificar (inout).

Esses modificadores também podem ser usados em tipos Copyable. Nesse caso, funcionam como dicas de desempenho, não como semântica.

Eles substituem retain/release ou cópias que podem surgir da convenção padrão por empréstimos e movimentos, reduzindo o tráfego de ARC. Exigir movimento explícito com o operador consume (let b = consume a) pertence à mesma família.

Mas esta é uma área de micro-otimização que exige medição antes. O alerta sobre a conveniência do dispatch também se aplica aqui.

Na prática — onde usar e onde não usar

É importante entender exatamente qual é a posição atual desse recurso.

Ele é adequado para propriedade única de recursos. Por exemplo, wrappers de identificadores de arquivo e socket, tokens de lock, guards de transação e direitos de acesso ao hardware.

Na prática, um dos principais impulsionadores desse recurso foi o Embedded Swift. Em microcontroladores, heap e ARC são pesados, então era necessário gerenciar recursos com segurança sem classes.

Os valores fornecidos pelo Mutex da biblioteca padrão e tipos recentes como Span seguem essa linhagem.

O padrão para código de aplicativos comuns continua sendo struct Copyable. O princípio orientado a valores permanece.

Aplicar ~Copyable antecipadamente aos dados de domínio é overengineering. Ainda existe atrito com o ecossistema de genéricos: tipos não copiáveis não se misturam diretamente com genéricos e coleções existentes que assumem Copyable, e a linguagem está resolvendo isso gradualmente.

Por enquanto, use-o como ferramenta especializada apenas em tipos para os quais a resposta a “copiar seria um bug?” é sim.

Para concluir com uma comparação com Rust, Rust é uma linguagem de “propriedade por padrão” que impõe regras de propriedade a todos os valores e exige até anotações de lifetime.

O Swift mantém o mundo em que copiar é o padrão e leva apenas os tipos necessários para o mundo da propriedade: “propriedade opcional”.

É um exemplo clássico de Progressive Disclosure. O código de quem não conhece propriedade nem contém esse conceito; a próxima etapa se abre apenas para quem precisa dela.

Diagrama comparando borrowing, consuming e inout a três guichês
Apenas ler (borrowing), levar (consuming), modificar (inout)

Resumo

  • Todos os tipos são implicitamente Copyable, e a cópia indevida de tipos de recursos está na raiz de bugs de liberação dupla e semelhantes.
  • ~Copyable (SE-0390) proíbe cópias, transforma atribuições em movimentos e faz do uso de uma variável consumida um erro de compilação. Também permite deinit em structs para limpeza determinística.
  • Vocabulário de propriedade dos parâmetros: borrowing (apenas ler, empréstimo), consuming (levar, transferência) e inout (modificar). Métodos consuming registram na sintaxe o significado de “desaparece quando usado”.
  • Use-o para propriedade única de recursos — identificadores, locks, tokens e sistemas embarcados — enquanto structs Copyable continuam sendo o padrão para dados comuns. Diferentemente da propriedade universal do Rust, o Swift usa propriedade opcional.

O próximo artigo é o último tema da série avançada e de toda a série Swift: a história por trás de “o tamanho do app caiu de repente no Swift 5”, a estabilidade do ABI (Application Binary Interface). SE-0390: Noncopyable Structs and Enums

Fontes e critérios de verificação

  • SE-0390: Noncopyable Structs and Enums — Swift Evolution · texto original de padrões e especificações · verificado em 2026-08-17 · base: ~Copyable, consuming, borrowing e regras de tipos não copiáveis do Swift 5.9

Continue lendo