Desafio Rust

Trait Pilha com implementações diferentes

Enunciado

Defina uma trait Pilha com os métodos obrigatórios push(&mut self, valor: i32), pop(&mut self) -> Option<i32> e o método padrão vazia(&self) -> bool que retorna true quando tamanho() for 0. Inclua também o método obrigatório tamanho(&self) -> usize. Implemente Pilha para uma struct PilhaVec que usa um Vec<i32> internamente e para uma struct PilhaLimitada que usa um Vec<i32> e recusa push quando já tem 3 elementos. No main, empilhe 1, 2, 3, 4 na PilhaLimitada, imprima o tamanho e desempilhe tudo imprimindo cada valor.

Requisitos

  • A trait Pilha tem push, pop, tamanho obrigatórios e vazia com corpo padrão.
  • PilhaVec implementa todos os métodos usando Vec<i32>.
  • PilhaLimitada ignora push quando tamanho() >= 3.
  • main mostra que a limitada guarda apenas 3 elementos e imprime os pops até esvaziar.

Código inicial

trait Pilha {
    fn push(&mut self, valor: i32);
    fn pop(&mut self) -> Option<i32>;
    fn tamanho(&self) -> usize;

    // método padrão
    fn vazia(&self) -> bool {
        self.tamanho() == 0
    }
}

struct PilhaVec {
    itens: Vec<i32>,
}

struct PilhaLimitada {
    itens: Vec<i32>,
    limite: usize,
}

// TODO: impl Pilha para PilhaVec e PilhaLimitada

fn main() {
    // TODO: criar PilhaLimitada com limite 3, empilhar 1..=4 e desempilhar
}

Saída esperada

tamanho: 3
desempilhado: 3
desempilhado: 2
desempilhado: 1
vazia: true
Ver dica

Vec::pop já retorna Option<i32>, então o pop da trait pode apenas delegar. No push da limitada, compare self.itens.len() < self.limite antes de inserir.

Mostrar solução
trait Pilha {
    fn push(&mut self, valor: i32);
    fn pop(&mut self) -> Option<i32>;
    fn tamanho(&self) -> usize;

    // método padrão: depende apenas de tamanho()
    fn vazia(&self) -> bool {
        self.tamanho() == 0
    }
}

struct PilhaVec {
    itens: Vec<i32>,
}

struct PilhaLimitada {
    itens: Vec<i32>,
    limite: usize,
}

impl Pilha for PilhaVec {
    fn push(&mut self, valor: i32) {
        self.itens.push(valor);
    }

    // Vec::pop já devolve Option<i32>: basta repassar
    fn pop(&mut self) -> Option<i32> {
        self.itens.pop()
    }

    fn tamanho(&self) -> usize {
        self.itens.len()
    }
}

impl Pilha for PilhaLimitada {
    fn push(&mut self, valor: i32) {
        // só empilha se ainda houver espaço
        if self.itens.len() < self.limite {
            self.itens.push(valor);
        }
    }

    fn pop(&mut self) -> Option<i32> {
        self.itens.pop()
    }

    fn tamanho(&self) -> usize {
        self.itens.len()
    }
}

fn main() {
    let mut p = PilhaLimitada { itens: Vec::new(), limite: 3 };

    // 4 é recusado porque a pilha já está cheia
    for v in 1..=4 {
        p.push(v);
    }
    println!("tamanho: {}", p.tamanho());

    // desempilha até esvaziar
    while let Some(v) = p.pop() {
        println!("desempilhado: {}", v);
    }
    println!("vazia: {}", p.vazia());
}

Passo a passo

  1. A trait Pilha declara push, pop e tamanho sem corpo (obrigatórios) e vazia com corpo padrão que usa self.tamanho().
  2. PilhaVec guarda um Vec<i32>; seu push chama self.itens.push(valor).
  3. O pop de PilhaVec simplesmente devolve self.itens.pop(), que já é Option<i32>.
  4. tamanho retorna self.itens.len() para as duas structs.
  5. PilhaLimitada tem também um campo limite; seu push só insere quando self.itens.len() < self.limite.
  6. No main, o laço for v in 1..=4 tenta empilhar 1, 2, 3 e 4; o 4 é descartado silenciosamente.
  7. println!("tamanho: {}", p.tamanho()) mostra 3.
  8. O laço while let Some(v) = p.pop() desempilha na ordem inversa (3, 2, 1) e para quando pop devolve None.
  9. Por fim, p.vazia() usa o método padrão e imprime true.

Por que funciona

As duas implementações compartilham o mesmo contrato Pilha, mas têm comportamentos diferentes: uma aceita tudo, a outra impõe um limite. O método padrão vazia é escrito uma única vez e funciona para ambas porque depende apenas de tamanho, que cada tipo fornece. Isso ilustra bem o poder das traits: contrato comum, implementações específicas e código reutilizado.

Erros comuns

  • Tentar chamar p.vazia() antes de implementar tamanho: o método padrão não compila porque self.tamanho() não existe no tipo.
  • Usar if self.itens.len() <= self.limite no push da limitada: isso permite 4 elementos; o correto é <, porque queremos no máximo limite itens.
  • Tentar mover o vetor no pop: fn pop(&mut self) -> Option<i32> { self.itens } não compila; use self.itens.pop().
  • Esquecer &mut self no push/pop: sem mutabilidade o compilador recusa modificar o vetor interno.

Outra forma de resolver

Poderíamos evitar duplicação criando um único tipo genérico com um limite opcional:

struct PilhaGenerica { itens: Vec<i32>, limite: Option<usize> }

impl Pilha for PilhaGenerica {
    fn push(&mut self, valor: i32) {
        if self.limite.map_or(true, |l| self.itens.len() < l) {
            self.itens.push(valor);
        }
    }
    fn pop(&mut self) -> Option<i32> { self.itens.pop() }
    fn tamanho(&self) -> usize { self.itens.len() }
}

É preferível quando o comportamento é o mesmo e só o limite muda; traits separadas fazem mais sentido quando as implementações divergem de verdade.

Saída esperada

tamanho: 3
desempilhado: 3
desempilhado: 2
desempilhado: 1
vazia: true