Erro E0277 do Rust: the trait bound is not satisfied (trait não implementado)
O E0277 quer dizer: você usou um tipo num lugar que exige uma habilidade que esse tipo não tem. Imprimir, comparar, somar, converter, mandar para outra thread: cada uma dessas coisas exige uma trait, e o seu tipo não tem essa trait. É um dos erros mais comuns do Rust, a mensagem geralmente diz a correção exata e, na maioria das vezes, a correção é uma linha só. Para os outros erros desta série, veja Erros do compilador Rust.
A mensagem de erro
Seção intitulada “A mensagem de erro”A versão que a maioria das pessoas encontra primeiro é imprimir a própria struct com {}:
error[E0277]: `Player` doesn't implement `std::fmt::Display` --> src/main.rs:8:20 |8 | println!("{}", p); | -- ^ `Player` cannot be formatted with the default formatter | | | required by this formatting parameter |help: the trait `std::fmt::Display` is not implemented for `Player` = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) insteadEm português: “Player não implementa std::fmt::Display”. O compilador diz que Player não pode ser formatado com o formatador padrão ({}) e sugere usar {:?} (ou {:#?} para uma impressão mais bonita) no lugar.
O programa que causa o erro:
struct Player { name: String, score: u32,}
fn main() { let p = Player { name: String::from("Ana"), score: 42 }; println!("{}", p);}O que significa
Seção intitulada “O que significa”Uma trait é uma promessa de que um tipo sabe fazer alguma coisa. Display é a promessa “eu sei me imprimir de um jeito bonito”. PartialEq é “eu posso ser comparado com ==”. Send é “posso ser levado com segurança para outra thread”.
Código como println!, .sort(), + e thread::spawn pede uma dessas promessas. O compilador aponta para o valor que você passou (p na linha 8) e diz: esse tipo nunca fez essa promessa, então não posso usá-lo aqui.
Pense numa tomada. Ela só aceita plugues de um formato. Seu aparelho pode funcionar perfeitamente, mas se o plugue não encaixa, não entra. O E0277 é o compilador dizendo “plugue errado”. Você corrige dando ao tipo o plugue certo: normalmente com #[derive(...)], às vezes com um bloco impl curto.
Por que o Rust faz isso
Seção intitulada “Por que o Rust faz isso”O Rust nunca chuta. Para o seu tipo Player, não existe um único jeito “certo” de imprimir, comparar ou ordenar. Dois jogadores são iguais quando os nomes batem, ou só quando todos os campos batem? O Rust faz você dizer, em vez de fazer escondido algo que você não queria.
A verificação acontece em tempo de compilação. Em muitas linguagens, imprimir um objeto que você nunca ensinou a se imprimir dá algo inútil como <Player object at 0x7f...>, e mandar um valor que não é seguro entre threads para outra thread dá uma falha rara em produção. Em Rust, as duas coisas são erros de compilação que você corrige uma vez só.
Você pode ler mais sobre como as traits funcionam na página Traits, e sobre as do dia a dia (Debug, Display, PartialEq, Ord, Hash) em Traits comuns.
Como corrigir
Seção intitulada “Como corrigir”O E0277 tem muitas caras. Encontre abaixo a mensagem que combina com a sua.
Correção 1: Imprimir uma struct com {} ou {:?}
Seção intitulada “Correção 1: Imprimir uma struct com {} ou {:?}”{:?} exige Debug. Você quase nunca escreve essa trait à mão: basta colocar #[derive(Debug)]. Use para depuração e logs. A versão do erro com {:?} é assim:
error[E0277]: `Player` doesn't implement `Debug` = help: the trait `Debug` is not implemented for `Player` = note: add `#[derive(Debug)]` to `Player` or manually `impl Debug for Player`A nota sugere acrescentar #[derive(Debug)] a Player ou escrever impl Debug for Player à mão.
{} exige Display. O Rust não consegue derivar essa, porque só você sabe como deve ser a versão “bonita”. Escreva um impl pequeno com write!. Use para textos que seus usuários vão ver.
use std::fmt;
#[derive(Debug)] // ganha o {:?} de graçastruct Player { name: String, score: u32,}
// O Display você escreve: ele decide o que o {} imprime.impl fmt::Display for Player { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} ({} pontos)", self.name, self.score) }}
fn main() { let p = Player { name: String::from("Ana"), score: 42 }; println!("{:?}", p); println!("{}", p);}Saída:
Player { name: "Ana", score: 42 }Ana (42 pontos)Correção 2: Comparar, ordenar ou usar como chave de HashMap
Seção intitulada “Correção 2: Comparar, ordenar ou usar como chave de HashMap”.contains() e == exigem PartialEq. .sort() exige Ord. Chaves de HashSet e HashMap exigem Eq e Hash. As mensagens são assim:
error[E0277]: can't compare `Point` with `Point` = help: the trait `PartialEq` is not implemented for `Point`
error[E0277]: the trait bound `Priority: Ord` is not satisfied = help: the trait `Ord` is not implemented for `Priority`
error[E0277]: the trait bound `Point: Hash` is not satisfied = help: the trait `Hash` is not implemented for `Point`Ou seja: “não dá para comparar Point com Point” e “a exigência Priority: Ord (ou Point: Hash) não foi satisfeita”. A correção é derivar essas traits. Uma depende da outra, então o conjunto completo de costume é PartialEq, Eq, Hash, PartialOrd, Ord:
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]enum Priority { Low, Medium, High,}
fn main() { let mut tasks = vec![Priority::High, Priority::Low, Priority::Medium]; tasks.sort(); // exige Ord println!("{:?}", tasks); println!("Tem prioridade alta: {}", tasks.contains(&Priority::High)); // exige PartialEq
let mut counts: HashMap<Priority, u32> = HashMap::new(); // a chave exige Eq + Hash for task in &tasks { *counts.entry(*task).or_insert(0) += 1; } println!("Tarefas de prioridade baixa: {}", counts[&Priority::Low]);}Saída:
[Low, Medium, High]Tem prioridade alta: trueTarefas de prioridade baixa: 1Um Ord derivado ordena as variantes de um enum na ordem em que você as escreveu, e as structs campo por campo, de cima para baixo. Um porém, sendo sincero: um tipo com um campo f64 não pode derivar Eq, Hash nem Ord, porque floats têm NaN, que não é igual a si mesmo. Para ordenar floats, veja Ordenação e busca.
Correção 3: Misturar tipos numéricos (cannot add f64 to i32)
Seção intitulada “Correção 3: Misturar tipos numéricos (cannot add f64 to i32)”error[E0277]: cannot add `f64` to `i32` --> src/main.rs:4:23 |4 | let total = items + price; | ^ no implementation for `i32 + f64` | = help: the trait `Add<f64>` is not implemented for `i32`Em português: “não dá para somar f64 a i32”, porque não existe implementação de i32 + f64. O Rust nunca converte números por você, nem mesmo de i32 para f64. Os dois lados de +, -, * e / precisam ser do mesmo tipo. Converta um dos lados você mesmo com as:
fn main() { let items: i32 = 3; let price: f64 = 2.5; let total = items as f64 * price; // converte primeiro, depois multiplica println!("Total: {total}");}Saída:
Total: 7.5f64::from(items) também funciona e nunca perde dados. No sentido contrário (de f64 para i32), as casas decimais são cortadas, então pense no arredondamento antes. As páginas Conversão de tipos e Conversões explicam os detalhes.
Correção 4: ? couldn’t convert the error
Seção intitulada “Correção 4: ? couldn’t convert the error”error[E0277]: `?` couldn't convert the error to `ConfigError` |8 | fn parse_port(text: &str) -> Result<u16, ConfigError> { | ------------------------ expected `ConfigError` because of this9 | let port = text.parse::<u16>()?; | --------------^ the trait `From<ParseIntError>` is not implemented for `ConfigError` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` traitEm português: “o ? não conseguiu converter o erro para ConfigError”. A nota explica que o ? faz uma conversão implícita do valor de erro usando a trait From. Quando o ? encontra um erro, ele o retorna da sua função. Se o tipo do erro for diferente do que a sua função promete, o ? tenta convertê-lo com From. Sua tarefa é escrever esse From:
use std::num::ParseIntError;
#[derive(Debug)]enum ConfigError { BadPort(ParseIntError),}
// Ensina o `?` a transformar um ParseIntError num ConfigError.impl From<ParseIntError> for ConfigError { fn from(err: ParseIntError) -> Self { ConfigError::BadPort(err) }}
fn parse_port(text: &str) -> Result<u16, ConfigError> { let port = text.parse::<u16>()?; Ok(port)}
fn main() { println!("{:?}", parse_port("8080")); println!("{:?}", parse_port("oitenta"));}Saída:
Ok(8080)Err(BadPort(ParseIntError { kind: InvalidDigit }))Outras boas opções: converter só uma chamada com .map_err(ConfigError::BadPort)?, usar #[from] do crate thiserror ou retornar Result<T, Box<dyn std::error::Error>> em programas pequenos. Veja Erros personalizados e anyhow e thiserror.
Correção 5: Rc cannot be sent between threads safely
Seção intitulada “Correção 5: Rc cannot be sent between threads safely”error[E0277]: `Rc<String>` cannot be sent between threads safely --> src/main.rs:6:32 |6 | let handle = thread::spawn(move || { | ------------- required by a bound introduced by this call ... = help: within `{closure@src/main.rs:6:32: 6:39}`, the trait `Send` is not implemented for `Rc<String>`Em português: “Rc<String> não pode ser enviado entre threads com segurança”, porque Rc<String> não implementa Send. O Rc conta seus donos com um número comum, e duas threads poderiam alterar esse número ao mesmo tempo e corrompê-lo. Por isso, Rc não é Send. Use Arc (a versão “atômica”) no lugar. Funciona do mesmo jeito e é seguro entre threads:
use std::sync::Arc;use std::thread;
fn main() { let config = Arc::new(String::from("debug")); let for_thread = Arc::clone(&config); // um segundo dono, para a thread
let handle = thread::spawn(move || { println!("A thread vê: {for_thread}"); }); handle.join().unwrap();
println!("A main ainda tem: {config}");}Saída:
A thread vê: debugA main ainda tem: debugO mesmo vale para RefCell: entre threads, use Arc<Mutex<T>> em vez de Rc<RefCell<T>>. Veja Estado compartilhado e Concorrência.
Onde você costuma ver esse erro
Seção intitulada “Onde você costuma ver esse erro”- Funções genéricas.
fn announce<T: Display>(item: T)chamada com a sua struct dá`Song` doesn't implement `std::fmt::Display`com “required by a bound inannounce” (exigido por uma restrição emannounce). Correção: implemente a trait que a restrição pede. - Testes.
assert_eq!(a, b)exigePartialEqeDebug(para imprimir os valores quando o teste falha). Correção:#[derive(Debug, PartialEq)]. .collect()para o tipo errado.let nums: Vec<i32> = words.iter().collect();dá “a value of typeVec<i32>cannot be built from an iterator over elements of type&String” (umVec<i32>não pode ser montado a partir de um iterador de&String). Correção: converta cada item antes, por exemplo com.map(|w| w.parse().unwrap()). Veja Iteradores.
Erros relacionados
Seção intitulada “Erros relacionados”- E0599: no method found: chamar
.insert()numHashMapcuja chave não éHashàs vezes aparece como E0599 (“trait bounds were not satisfied”) em vez disso. Mesma correção: derive as traits. - E0308: mismatched types: você passou um tipo onde se esperava outro, como um
i32onde vai umf64. - E0382: borrow of moved value: costuma aparecer logo depois que você acrescenta
CloneouCopypensando em traits. E0369: binary operation == cannot be applied: a versão do operador==para a falta dePartialEq. Mesma correção.- Todos os erros do compilador explicados
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. Você recebe "Order doesn't implement Debug" ao usar {:?}. Qual é a correção mais rápida?
2. Por que let total = 3_i32 + 2.5_f64; falha?
3. Seu código com threads falha com "Rc<String> cannot be sent between threads safely". O que você deve usar?
Pontuação: 0 / 3
Para saber mais
Seção intitulada “Para saber mais”- E0277 — Rust error codes index (em inglês): a explicação oficial deste erro.
- Traits: Defining Shared Behavior — The Rust Book (em inglês): o que são traits e como funcionam as restrições de trait (trait bounds).
- Derivable Traits — The Rust Book, Appendix C (em inglês): o que
Debug,PartialEq,Eq,Hash,PartialOrdeOrdoferecem quando derivados. - Recoverable Errors with Result — The Rust Book (em inglês): como o
?funciona, incluindo a conversão comFrom. Send— documentação da biblioteca padrão (em inglês): quais tipos podem passar entre threads e por que oRcnão pode.