Ícone do site Aprenda Golang

Como comparar valores em Go

Comparar valores é uma das operações mais comuns em qualquer linguagem de programação, e em Go não é diferente. Porém, a simplicidade da linguagem esconde algumas nuances importantes sobre como certos tipos podem ou não ser comparados diretamente.

Neste post, exploraremos os operadores de comparação em Go, suas limitações e alternativas.

Operadores de Comparação

De modo geral, Go oferece dois operadores para comparação direta de valores:

Esses operadores funcionam para os tipos chamados de “comparáveis”, como por exemplo o boolean, int, float, tipos complexos, string, channels, arrays, structs e ponteiros.

Para os tipos numéricos (int, float e tipos complexos) também podemos utilizar >, <, <= e >=.

Tipos comparáveis

Os tipos “comparáveis” se referem a tipos de dados que podem ser comparados diretamente, através dos operadores citados acima. No exemplo abaixo, fazemos duas pequenas comparações com strings.

package main

import "fmt"

func main() {
    str1 := "golang"
    str2 := "golang"
    str3 := "go"

    fmt.Println(str1 == str2) // true
    fmt.Println(str1 == str3) // false
}

Embora fazer essas comparações com tipos básicos não seja nenhuma novidade, dizer que uma struct é um tipo comparável pode soar meio estranho. Isso acontece pois muitas vezes acabamos olhando para structs como as famosas classes da orientação a objetos.

No entanto, vale lembrar, como explicamos no post “As diferenças entre structs e classes”, structs não são classes.

No exemplo abaixo, que é praticamente uma cópia anterior, podemos ver quão simples é comprar duas structs.

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func main() {
    p1 := Person{Name: "Alice", Age: 30}
    p2 := Person{Name: "Alice", Age: 30}
    p3 := Person{Name: "Bob", Age: 25}

    fmt.Println(p1 == p2) // true
    fmt.Println(p1 == p3) // false
}

Porém, contudo, todavia… É CLARO que não pode ser tão fácil assim. Na verdade, structs só são comparáveis quando todos seus atributos são de tipos comparáveis.

Em outras palavras, se uma struct contiver algum campo que não seja comparável (como slices ou maps), o compilador emitirá um erro.

panic: runtime error: comparing uncomparable type main.X

Structs não comparáveis

Ok, mas o que eu faço para comparar duas structs que contém atributos não comparáveis (slices e maps)?

Existem basicamente duas opções para se fazer isso.

A primeira seria implementar um método “Compare”, recebendo a struct a ser comparada como parâmetro. Em sua implementação, comparar cada um dos atributos manualmente, como mostro no próximo exemplo.

package main

import "fmt"

type Data struct {
    Key    string
    Values []int
}

func (d *Data) Compare(b *Data) bool {
    if d.Key != b.Key {
         return false
    }
		
    if len(d.Values) != len(b.Values) {
         return false
    }
		
    for k, v := range d.Values {
        if v != b.Values[k] {
            return false
        }
    }
		
    return true
}

func main() {
    d1 := Data{Key: "A", Values: []int{1, 2, 3}}
    d2 := Data{Key: "A", Values: []int{1, 2, 3}}

    fmt.Println(d1.Compare(d2))
}

Embora seja uma abordagem bem verbosa, ela com certeza da conta do recado.

A segunda opção, MUITO menos verbosa, é utilizar a função DeepEqual do package reflect. Porém, assim como qualquer função/método que venha dessa package, embora facilite muito as coisas, existe um lado negativo.

Mas antes de falar desse lado, vamos ver como utilizar essa função.

Reflect.DeepEqual

A função reflect.DeepEqual , como dito anteriormente, é uma alternativa para comparar valores que não podem ser comparados diretamente, como por exemplo slices e maps.

No exemplo abaixo, reescrevemos o código anterior substituindo a implementação customizada do método “Compare”, pela utilização da função DeepEqual.

package main

import (
    "fmt"
    "reflect"
)

type Data struct {
    Key    string
    Values []int
}

func main() {
    d1 := Data{Key: "A", Values: []int{1, 2, 3}}
    d2 := Data{Key: "A", Values: []int{1, 2, 3}}
    d3 := Data{Key: "A", Values: []int{2, 1, 3}}

    fmt.Println(reflect.DeepEqual(d1, d2)) // true
    fmt.Println(reflect.DeepEqual(d1, d3)) // false
}

Embora poderosa, antes de utilizar reflect.DeepEqual, é importante considerar dois pontos:

Performance

Em alguns benchmarks, reflect.DeepEqual se mostra, em média, 100x mais lenta do que quando utilizamos operadores básicos.

Se performance for um fator crítico, evite reflect.DeepEqual em caminhos de execução sensíveis. Em vez disso, implemente comparações manuais otimizadas para seu caso de uso específico.

Conclusão

Comparar valores em Go é simples, mas exige atenção a detalhes importantes, especialmente quando lidamos com tipos compostos como structs e slices.

Enquanto os operadores == e != são rápidos e intuitivos, eles não cobrem todos os casos. Para situações mais complexas, reflect.DeepEqual pode ser útil, mas com custos de performance.

Saber quando usar cada abordagem é essencial para escrever código eficiente e confiável. Agora que você entende as nuances das comparações em Go, pode aplicá-las com confiança em seus projetos.

Até a próxima!


Faça parte da comunidade!

Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.

Livros Recomendados

Abaixo listei alguns dos melhores livros que já li sobre GO.

Sair da versão mobile