Pular para o conteúdo

Erro E0369 do Rust: binary operation cannot be applied to type

O E0369 quer dizer que você usou um operador como ==, < ou + num tipo que ainda não sabe fazer essa operação. Em Rust, os operadores não vêm prontos nas suas structs e enums. Cada um vem de uma trait, e o seu tipo só ganha o operador quando você deriva ou implementa essa trait. A correção costuma ser uma única linha #[derive(...)], e o compilador ainda diz qual. Para os outros erros desta série, veja Erros do compilador Rust.

error[E0369]: binary operation `==` cannot be applied to type `Point`
--> src/main.rs:9:10
|
9 | if a == b { // ❌ Point não sabe se comparar
| - ^^ - Point
| |
| Point
|
note: an implementation of `PartialEq` might be missing for `Point`
--> src/main.rs:1:1
|
1 | struct Point {
| ^^^^^^^^^^^^ must implement `PartialEq`
help: consider annotating `Point` with `#[derive(PartialEq)]`
|
1 + #[derive(PartialEq)]
2 | struct Point {
|

Em português: “a operação binária == não pode ser aplicada ao tipo Point”. A nota diz que talvez falte uma implementação de PartialEq para Point, e a ajuda sugere colocar #[derive(PartialEq)] na struct.

Este é o programa que gera o erro:

struct Point {
x: i32,
y: i32,
}
fn main() {
let a = Point { x: 1, y: 2 };
let b = Point { x: 1, y: 2 };
if a == b { // ❌ Point não sabe se comparar
println!("Same point!");
}
}

A primeira linha muda conforme o operador, mas o erro é o mesmo:

  • binary operation `<` cannot be applied to type `Version`: falta PartialOrd (vale também para >, <= e >=).
  • cannot add `Point` to `Point`: falta Add. Você também vai ver “cannot subtract” (não dá para subtrair), “cannot multiply” (não dá para multiplicar) e assim por diante.
  • cannot add `&str` to `&str`: você tentou juntar dois slices de string com +.

Os rótulos embaixo do operador mostram o tipo de cada lado, aqui Point e Point. Depois, a note: aponta para a sua struct e diz que ela precisa implementar PartialEq (must implement PartialEq). Em outras palavras: “você me pediu para comparar dois Point, mas ninguém me disse o que igual significa para um Point.”

Pense num juiz de competição que recebe dois objetos e ouve a pergunta “qual é maior?”. Para números, a resposta é óbvia. Para duas mochilas, o juiz precisa de uma regra primeiro: pelo peso? pelo volume? O Rust é esse juiz cuidadoso.

Todo operador é uma chamada de método disfarçada. a == b chama PartialEq::eq, a < b chama PartialOrd::partial_cmp e a + b chama Add::add. Números, String e muitos tipos da biblioteca padrão já implementam essas traits. A sua struct nova começa sem nenhuma.

O Rust não chuta, porque às vezes o chute estaria errado. Dois User são iguais quando os IDs batem, ou só quando todos os campos batem? Point + Point deve somar as coordenadas, ou somar pontos nem faz sentido? Quem decide é você, derivando a trait (o padrão campo por campo) ou escrevendo o seu próprio impl. As traits são explicadas na página Traits comuns.

Escolha esta para == e != quando “igual” significa “todos os campos são iguais”. Na maioria das vezes é a correção certa, e é exatamente o que o rustc sugere. Acrescentar Debug também deixa você imprimir a struct:

#[derive(Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let a = Point { x: 1, y: 2 };
let b = Point { x: 1, y: 2 };
let c = Point { x: 5, y: 0 };
if a == b {
println!("Mesmo ponto!");
}
println!("{:?} != {:?}: {}", a, c, a != c);
}

Saída:

Mesmo ponto!
Point { x: 1, y: 2 } != Point { x: 5, y: 0 }: true
▶ Experimente você mesmo

Todos os campos também precisam ser comparáveis. Se o tipo de algum campo não tiver PartialEq, derive a trait nesse tipo também. Enums funcionam do mesmo jeito: #[derive(PartialEq)] em enum Status faz status == Status::Banned compilar.

Escolha esta quando o seu tipo tem uma ordem natural. PartialOrd exige PartialEq, então o rustc sugere #[derive(PartialEq, PartialOrd)]. A versão derivada compara os campos de cima para baixo: primeiro major e, só se forem iguais, minor. Por isso, a ordem dos campos na struct importa:

#[derive(Debug, PartialEq, PartialOrd)]
struct Version {
major: u32,
minor: u32,
}
fn main() {
let installed = Version { major: 1, minor: 4 };
let required = Version { major: 1, minor: 7 };
if installed < required {
println!("Atualize a partir de {:?}", installed);
}
let next = Version { major: 2, minor: 0 };
println!("2.0 é mais nova que 1.7: {}", next > required);
}

Saída:

Atualize a partir de Version { major: 1, minor: 4 }
2.0 é mais nova que 1.7: true
▶ Experimente você mesmo

Se quiser usar .sort() num Vec do seu tipo, você também vai precisar de Eq e Ord. Veja Ordenação e busca.

Escolha esta quando somar dois valores do seu tipo tem um significado claro, como somar coordenadas ou dinheiro. Não existe derive para operadores matemáticos, então você escreve um impl curto. Output é o tipo que o + devolve:

use std::ops::Add;
#[derive(Debug, Clone, Copy, PartialEq)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point { x: self.x + other.x, y: self.y + other.y }
}
}
fn main() {
let start = Point { x: 1, y: 2 };
let step = Point { x: 3, y: 4 };
let end = start + step;
println!("{:?}", end);
println!("{}", end == Point { x: 4, y: 6 });
}

Saída:

Point { x: 4, y: 6 }
true
▶ Experimente você mesmo

Sub, Mul, Div e Neg funcionam do mesmo jeito, todos em std::ops. Derivar Copy aqui faz start e step continuarem utilizáveis depois do +. Mais em Traits comuns e Traits avançados.

Escolha esta quando o erro diz cannot add `&str` to `&str` (não dá para somar &str com &str). O + com strings exige uma String com dono do lado esquerdo e um &str do lado direito. A ajuda do rustc diz isso do jeito dela, “create an owned String from a string reference” (crie uma String com dono a partir de uma referência de string), ou seja, first.to_owned() + second. Com duas String, empreste a da direita: s1 + &s2. E o format! sempre funciona e não move nada:

fn main() {
let first = String::from("Olá, ");
let second = String::from("Ferris");
let shout = format!("{first}{second}!"); // empresta as duas
let joined = first + &second; // first é movida para dentro de joined
let short = "Oi, ".to_owned() + "caranguejo"; // &str à esquerda: crie uma String antes
println!("{shout}");
println!("{joined}");
println!("{short}");
println!("{second} continua aqui");
}

Saída:

Olá, Ferris!
Olá, Ferris
Oi, caranguejo
Ferris continua aqui
▶ Experimente você mesmo

O + toma posse da String da esquerda e acrescenta o texto nela, e é por isso que first não pode ser usada depois dessa linha. Se isso confundir, use format! e pronto. Veja Strings.

Comparações em código genérico. A clássica função largest falha com binary operation `>` cannot be applied to type `&T`, porque T pode ser qualquer tipo, até um que não tem ordem:

fn largest<T>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest { // ❌ T talvez não seja comparável
largest = item;
}
}
largest
}

Correção: acrescente uma restrição de trait (trait bound), exatamente como o rustc sugere: fn largest<T: PartialOrd>(list: &[T]) -> &T. Veja Genéricos.

Conferindo o valor de um enum. if status == Status::Banned falha até o enum ter #[derive(PartialEq)]. Se você não pode mudar o enum (ele vem de outro crate), use matches!(status, Status::Banned) ou um match.

Juntando duas String. first + second com duas String não é E0369: é E0308 (“expected &str, found String”) com help: consider borrowing here (considere emprestar aqui), first + &second. Mesma família, mesma correção da Correção 4.

Comparando dois tipos diferentes. count == limit com um i32 e um f64 dá E0308, e name == 'F' com uma String e um char dá E0277 (“can’t compare String with char”). Uma regra prática: você recebe o E0369 quando o tipo da esquerda não tem nenhuma versão do operador, e o E0308 ou o E0277 quando tem uma, mas não para aquele tipo da direita. Correção: converta um dos lados (count as f64 == limit, name == "F").

Teste o que aprendeu

1. Você escreve if order_a == order_b para uma struct Order e recebe o E0369. Qual costuma ser a correção?

2. Com #[derive(PartialOrd)] em struct Version { major: u32, minor: u32 }, como duas versões são comparadas?

3. first e second são &str. Qual expressão junta as duas sem erro?

Pontuação: 0 / 3