Pular para o conteúdo

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 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) instead

Em 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);
}

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.

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.

O E0277 tem muitas caras. Encontre abaixo a mensagem que combina com a sua.

{:?} 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ça
struct 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)
▶ Experimente você mesmo

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: true
Tarefas de prioridade baixa: 1
▶ Experimente você mesmo

Um 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.5
▶ Experimente você mesmo

f64::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.

error[E0277]: `?` couldn't convert the error to `ConfigError`
|
8 | fn parse_port(text: &str) -> Result<u16, ConfigError> {
| ------------------------ expected `ConfigError` because of this
9 | 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` trait

Em 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 }))
▶ Experimente você mesmo

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ê: debug
A main ainda tem: debug
▶ Experimente você mesmo

O mesmo vale para RefCell: entre threads, use Arc<Mutex<T>> em vez de Rc<RefCell<T>>. Veja Estado compartilhado e Concorrência.

  • 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 in announce” (exigido por uma restrição em announce). Correção: implemente a trait que a restrição pede.
  • Testes. assert_eq!(a, b) exige PartialEq e Debug (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 type Vec<i32> cannot be built from an iterator over elements of type &String” (um Vec<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.

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