TL;DR: O operador duplo-igual (==
) diz se dois objetos são “iguais” ou “equivalentes”, e esse significado tem que ser implementado pelo programador (que faz isso através do protocolo Equatable
). Por outro lado, o operador triplo-igual (===
) diz se duas variáveis fazem referência ao mesmo objeto — algo que o Swift faz sem precisar do programador.
No Swift, o operador de igualdade é diferente1 de muitas outras linguagens de programação. Em geral parece funcionar muito bem, mas quando tentamos usá-lo para comparar instâncias de nossas classes ou structs, alguma coisa estranha ocorre: o Swift simplesmente se recusa a aceitar?!?
Qual é o problema?
Para entender o problema, vamos primeiro vê-lo acontecendo. Vou começar com um exemplo simples que pode ser baixado deste link. Você pode copiar os dois trechos de código abaixo e colá-los em um playground do Xcode. Em seguida, crie dois objetos da classe Movie
como visto a seguir, ambos exatamente com os mesmos dados:
let umFilmeQualquer = Movie( originalTitle: "Duas Horas Perdidas", year: 2018, runtimeMinutes: 120, genres: [.drama]) let outroObjetoComOsMesmosDados = Movie( originalTitle: "Duas Horas Perdidas", year: 2018, runtimeMinutes: 120, genres: [.drama])
Agora vem a pergunta pra dar aquela engasgada: O que acontece quando a gente compara os dois objetos? Eles são iguais (porque contêm os mesmos dados) ou são diferentes (porque são dois objetos distintos)? Em outras palavras, o que acontece no código a seguir?
if umFilmeQualquer == outroObjetoComOsMesmosDados { print("O Swift acha que os objetos são iguais.") } else { print("O Swift acha que os objetos são diferentes.") }
A resposta é: nenhum dos dois! O Swift se recusa a executar, emitindo o erro Binary operator '==' cannot be applied to two 'Movie' operands
.
Essa não! Então não posso comparar objetos?
Calma, pode sim! Só que o Swift se recusa a responder “no chute”. O programador é que deve dizer o que são “dois objetos iguais”, dependendo da lógica desejada.
Vamos para a prática! Para ensinar ao Swift como comparar instâncias de uma classe, devemos implementar o protocolo Equatable
. Se você não sabe o que são protocolos ou se nunca implementou um, não se preocupe: na verdade é bastante simples!
Para prosseguir no nosso exemplo dos filmes, vamos considerar que dois filmes são “iguais” se o título original e o ano de produção forem o mesmo. Basta copiar e colar o seguinte código:
extension Movie: Equatable { public static func == (lhs: Movie, rhs: Movie) -> Bool { return lhs.originalTitle == rhs.originalTitle && lhs.year == rhs.year } }
(Eu implementei como uma extensão para não mexer no código original, mas poderia ter sido implementado diretamente na definição da classe.) Basicamente estamos ensinando ao Swift o que fazer para comparar dois objetos da classe Movie
: no nosso exemplo, a correspondência ocorre quando os títulos e os anos de produção são coincidentes. Não disse que era fácil?
Nota: Não importa o nome dos dois argumentos: poderia ser a
e b
, ou filme1
e filme2
, ou quaisquer outros nomes. A convenção é usar esses dois nomes estranhos: lhs
e rhs
, que significam apenas “o da esquerda” (“left-hand side”) e “o da direita” (“right-hand side”).
Comparando referências: “objetos equivalentes” vs. “o mesmo objeto”
Eu já citei em outro post que variáveis (ou constantes) que armazenam objetos na verdade guardam apenas referências a esses objetos. O Swift possui um operador especial que testa se duas variáveis estão referenciando o mesmo objeto: trata-se do triplo igual, ===
, que pode ser usado exatamente como o conhecido duplo igual:
if umFilmeQualquer === outroObjetoComOsMesmosDados { print("São duas referências para o mesmo objeto.") } else { print("São dois objetos diferentes, mesmo que tenham os mesmos dados.") } // Saída: “São dois objetos diferentes, ...” let outraReferenciaParaUmFilmeQualquer = umFilmeQualquer if umFilmeQualquer === outraReferenciaParaUmFilmeQualquer { print("São duas referências para o mesmo objeto.") } else { print("São dois objetos diferentes, mesmo que tenham os mesmos dados.") } // Saída: “São duas referências para o mesmo objeto.”
Se você considera que a comparação de dois valores da sua classe deve funcionar apenas quando são referências repetidas, basta “ensinar” isso ao Swift:
extension Movie: Equatable { public static func == (lhs: Movie, rhs: Movie) -> Bool { return lhs === rhs // Só dá verdadeiro se for exatamente o mesmo objeto } }
Se você precisa implementar o protocolo Equatable
antes de pensar na lógica mais detalhada para fazer comparações, essa pode ser uma implementação inicial.
Mas o Swift não poderia dar uma “implementação padrão” que compara todos os campos?
Espera: mas será que faz sentido? Veja só: Uma das propriedades da classe Movie
é isFavourite
, um booleano usado para marcar os nossos filmes favoritos. O que acontece se tiver dois registros com todos os dados idênticos: nome, ano de produção, duração, gênero; um marcado como sendo favorito e outro não. Será que é correto considerar que são dois filmes distintos só por causa dessa diferença? Ou seria na verdade o mesmo filme? Outro exemplo: O filme “Wall-E”, de 2008, deve ser considerado igual ou diferente do filme “WALL-E”, de 2008?
Enfim, o Swift se recusa a fornecer uma implementação automática justamente porque o programador deve tomar decisões em cima dessas sutilezas.
Testei o triplo igual com structs, mas não funciona!
Mas não vai funcionar mesmo! Afinal de contas, o triplo igual é para ver se duas referências são para o mesmo objeto — mas os structs nunca são referências, portanto o operador simplesmente não faz sentido nesse caso. Se estiver confuso com esses conceitos, dê uma olhada neste post.
Um abraço!
- A piada é infame, mas vai ficar assim mesmo! ↩︎