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
Pilhatempush,pop,tamanhoobrigatórios evaziacom corpo padrão. PilhaVecimplementa todos os métodos usandoVec<i32>.PilhaLimitadaignorapushquandotamanho() >= 3.mainmostra 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
- A trait
Pilhadeclarapush,popetamanhosem corpo (obrigatórios) evaziacom corpo padrão que usaself.tamanho(). PilhaVecguarda umVec<i32>; seupushchamaself.itens.push(valor).- O
popdePilhaVecsimplesmente devolveself.itens.pop(), que já éOption<i32>. tamanhoretornaself.itens.len()para as duas structs.PilhaLimitadatem também um campolimite; seupushsó insere quandoself.itens.len() < self.limite.- No
main, o laçofor v in 1..=4tenta empilhar 1, 2, 3 e 4; o 4 é descartado silenciosamente. println!("tamanho: {}", p.tamanho())mostra 3.- O laço
while let Some(v) = p.pop()desempilha na ordem inversa (3, 2, 1) e para quandopopdevolveNone. - Por fim,
p.vazia()usa o método padrão e imprimetrue.
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 implementartamanho: o método padrão não compila porqueself.tamanho()não existe no tipo. - Usar
if self.itens.len() <= self.limitenopushda limitada: isso permite 4 elementos; o correto é<, porque queremos no máximolimiteitens. - Tentar mover o vetor no
pop:fn pop(&mut self) -> Option<i32> { self.itens }não compila; useself.itens.pop(). - Esquecer
&mut selfnopush/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