Médio Rust
Trait Resumo genérica
Enunciado
Crie uma trait Resumo com o método obrigatório resumir(&self) -> String. Implemente-a para Noticia (campos titulo: String e autor: String), retornando "<titulo> por <autor>", e para Tweet (campo usuario: String e texto: String), retornando no máximo 20 caracteres do texto seguidos de "..." se o texto for maior que 20. Escreva uma função genérica mostrar<T: Resumo>(item: &T) que imprime o resumo.
Requisitos
- A trait
Resumotem o métodoresumir. Noticiaretorna"titulo por autor".Tweettrunca o texto em 20 caracteres e adiciona...somente se passar de 20.mostraré genérica comT: Resumoe imprime o resultado.
Código inicial
trait Resumo {
fn resumir(&self) -> String;
}
struct Noticia {
titulo: String,
autor: String,
}
struct Tweet {
usuario: String,
texto: String,
}
// TODO: impl Resumo para Noticia e Tweet
// TODO: fn mostrar<T: Resumo>(item: &T)
fn main() {
let n = Noticia { titulo: String::from("Rust 2024"), autor: String::from("Ana") };
let t = Tweet { usuario: String::from("bob"), texto: String::from("Aprendendo traits em Rust hoje!") };
mostrar(&n);
mostrar(&t);
}
Saída esperada
Rust 2024 por Ana
Aprendendo traits e...
Ver dica
Para truncar com segurança use self.texto.chars().take(20).collect::<String>(); comparar chars().count() evita cortar no meio de um caractere UTF-8.
Mostrar solução
trait Resumo {
fn resumir(&self) -> String;
}
struct Noticia {
titulo: String,
autor: String,
}
struct Tweet {
usuario: String,
texto: String,
}
impl Resumo for Noticia {
fn resumir(&self) -> String {
format!("{} por {}", self.titulo, self.autor)
}
}
impl Resumo for Tweet {
fn resumir(&self) -> String {
// conta caracteres, não bytes, para não quebrar UTF-8
if self.texto.chars().count() > 20 {
let inicio: String = self.texto.chars().take(20).collect();
format!("{}...", inicio)
} else {
self.texto.clone()
}
}
}
// função genérica: aceita qualquer tipo que implemente Resumo
fn mostrar<T: Resumo>(item: &T) {
println!("{}", item.resumir());
}
fn main() {
let n = Noticia { titulo: String::from("Rust 2024"), autor: String::from("Ana") };
let t = Tweet { usuario: String::from("bob"), texto: String::from("Aprendendo traits em Rust hoje!") };
mostrar(&n);
mostrar(&t);
}
Passo a passo
- A trait
Resumodeclararesumir(&self) -> String, o contrato que todo tipo resumível deve cumprir. - Para
Noticia,resumirusaformat!para juntar título e autor com a palavra "por". - Para
Tweet, contamos os caracteres comchars().count()em vez delen(), poislen()conta bytes. - Se passar de 20 caracteres, pegamos os 20 primeiros com
chars().take(20).collect()e acrescentamos.... - Caso contrário, clonamos o texto original com
self.texto.clone()para devolver umaStringprópria. mostrar<T: Resumo>recebe uma referência genérica e chamaresumir(), funcionando para qualquer tipo que implemente a trait.- No
maincriamos umaNoticiae umTweete passamos ambos paramostrar.
Por que funciona
A função mostrar não conhece Noticia nem Tweet: ela só exige que o tipo implemente Resumo. Isso é polimorfismo via trait. Cada tipo decide como resumir, e o código genérico chama o método sem se preocupar com os detalhes. O truncamento usa chars() para respeitar caracteres Unicode, evitando pânico ou texto corrompido.
Erros comuns
- Usar
self.texto.len() > 20e&self.texto[..20]: com acentos (como "aprendendo") o índice pode cair no meio de um caractere UTF-8 e o programa entra em pânico. - Esquecer de clonar no
else:self.textoéStringe não pode ser movido de uma referência&self; use.clone(). - Declarar
fn mostrar<T: Resumo>(item: T)e chamarmostrar(&t): o tipo seria&Tweet, que não implementaResumo; useitem: &Touitem: Tcommostrar(t).
Outra forma de resolver
Podemos usar impl Trait no parâmetro, que é açúcar sintático para o mesmo genérico:
fn mostrar(item: &impl Resumo) {
println!("{}", item.resumir());
}
É preferível quando há apenas um parâmetro e não precisamos nomear o tipo genérico.
Saída esperada
Rust 2024 por Ana
Aprendendo traits e...