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 Resumo tem o método resumir.
  • Noticia retorna "titulo por autor".
  • Tweet trunca o texto em 20 caracteres e adiciona ... somente se passar de 20.
  • mostrar é genérica com T: Resumo e 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

  1. A trait Resumo declara resumir(&self) -> String, o contrato que todo tipo resumível deve cumprir.
  2. Para Noticia, resumir usa format! para juntar título e autor com a palavra "por".
  3. Para Tweet, contamos os caracteres com chars().count() em vez de len(), pois len() conta bytes.
  4. Se passar de 20 caracteres, pegamos os 20 primeiros com chars().take(20).collect() e acrescentamos ....
  5. Caso contrário, clonamos o texto original com self.texto.clone() para devolver uma String própria.
  6. mostrar<T: Resumo> recebe uma referência genérica e chama resumir(), funcionando para qualquer tipo que implemente a trait.
  7. No main criamos uma Noticia e um Tweet e passamos ambos para mostrar.

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() > 20 e &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 é String e não pode ser movido de uma referência &self; use .clone().
  • Declarar fn mostrar<T: Resumo>(item: T) e chamar mostrar(&t): o tipo seria &Tweet, que não implementa Resumo; use item: &T ou item: T com mostrar(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...