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.
A mensagem de erro
Seção intitulada “A mensagem de erro”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`: faltaPartialOrd(vale também para>,<=e>=).cannot add `Point` to `Point`: faltaAdd. 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+.
O que significa
Seção intitulada “O que significa”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.
Por que o Rust faz isso
Seção intitulada “Por que o Rust faz isso”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.
Como corrigir
Seção intitulada “Como corrigir”Correção 1: #[derive(PartialEq)] para == e !=
Seção intitulada “Correção 1: #[derive(PartialEq)] para == e !=”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 }: trueTodos 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.
Correção 2: #[derive(PartialOrd)] para < e >
Seção intitulada “Correção 2: #[derive(PartialOrd)] para < e >”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: trueSe quiser usar .sort() num Vec do seu tipo, você também vai precisar de Eq e Ord. Veja Ordenação e busca.
Correção 3: impl std::ops::Add para +
Seção intitulada “Correção 3: impl std::ops::Add para +”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 }trueSub, 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.
Correção 4: Juntar strings com + ou format!
Seção intitulada “Correção 4: Juntar strings com + ou format!”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á, FerrisOi, caranguejoFerris continua aquiO + 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.
Onde você costuma ver esse erro
Seção intitulada “Onde você costuma ver esse erro”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").
Erros relacionados
Seção intitulada “Erros relacionados”- E0277: trait bound not satisfied: o operador existe para o tipo da esquerda, mas não com esse tipo da direita (
i32 + f64,String == char). - E0308: mismatched types: o que você recebe com
String + Stringou ao comparar duas structs diferentes. - E0599: no method found: chamar
.sort()ou.contains()pode falhar do mesmo jeito quando faltaOrdouPartialEqno seu tipo. E0368: binary assignment operation += cannot be applied: a versão deste erro para o+=. ImplementeAddAssign, destd::ops.- Todos os erros do compilador: a lista completa de páginas de erro.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”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
Para saber mais
Seção intitulada “Para saber mais”- E0369 — Rust error codes index (em inglês): a explicação oficial deste erro.
- Derivable Traits — The Rust Book, Appendix C (em inglês): o que derivar
PartialEqePartialOrdoferece, incluindo a ordem campo por campo. - Storing UTF-8 Encoded Text with Strings — The Rust Book (em inglês): por que o
+com strings pede umaStringà esquerda e um&strà direita, e quando usarformat!. - Operator Overloading — Rust by Example (em inglês): um exemplo curto para rodar, implementando
Addnos seus próprios tipos. std::ops::Add— documentação da biblioteca padrão (em inglês): a traitAdd, o tipoOutpute como somar valores de tipos diferentes.