🎯 Gerenciador de Tarefas via Terminal em Rust
Em poucas palavras
Introduzir Structs para criar tipos de dados complexos, Vectors (
Vec<T>) para armazenar listas dinâmicas e o conceito básico de Ownership & Borrowing (referências mutáveis) no Rust.
📝 Notas e Desenvolvimento
🔗 Conexões e Contexto
- Atlas: MdC - Programação
- Notas Relacionadas:
📚 Referências e Fontes
Links Externos
Citações Diretas
📂 Outros Conteúdos Preservados
📝 Projeto: Gerenciador de Tarefas (To-Do CLI)
Objetivo
Introduzir Structs para criar tipos de dados complexos, Vectors (
Vec<T>) para armazenar listas dinâmicas e o conceito básico de Ownership & Borrowing (referências mutáveis) no Rust.
🛠️ 1. Contexto e Desafio
No desafio Construção de um Conversor de Escalas Termométricas em Rust, o dado entrava, era processado e descartado. Mas a maioria dos programas precisa manter um “estado” (memória).
O Desafio: Criar uma ferramenta de linha de comando onde o usuário possa adicionar tarefas, listar o que precisa fazer e marcar itens como concluídos. O programa deve manter essa lista na memória enquanto estiver rodando.
📋 2. Requisitos do Projeto
Funcionais (O que o pRograma fAz)
Aqui está uma lita dos requisitos funcionais do programa. A implementação deve cumprir estes requisitos. Entretanto, a apresentação das informações no terminal é a critério do desenvolvedor.
- Adicionar Tarefa: O usuário digita uma descrição (ex: “Comprar leite”) e ela é salva.
- Listar Tarefas: O programa mostra todas as tarefas com um ID (índice), descrição e status (Pendente ou Concluída).
- Ex:
[ ] 0: Comprar leiteou[X] 1: Estudar Rust.
- Ex:
- Concluir Tarefa: O usuário digita o ID da tarefa e o status dela muda para “Concluída”.
- Sair: Encerra o programa.
Técnicos (Como o Rust dEve sEr uSado)
A seguir são apresentados os requisitos técnicos da implementação do programa em Rust.
- Structs: Crie uma
struct Taskque agrupe adescricao(String) e ostatus. - Enums: Use um
enum Status(Pendente, Concluido) dentro da Struct. - Vectors: Use um
Vec<Task>na funçãomainpara armazenar a lista. - Iteração: Use um loop
forpara percorrer o vetor na hora de listar. - Mutabilidade: Você precisará acessar o vetor de forma mutável (
&mut) para alterar o status de uma tarefa existente.
🧩 3. Estrutura Sugerida (Esqueleto)
Abaixo está apenas a definição dos tipos e a estrutura da main para você preencher. Isso ajuda a começar sem dar a resposta pronta.
use std::io::{self, Write};
// 1. Defina o Enum para o estado da tarefa
#[derive(Debug)] // Adicione outros derives se necessário
enum Status {
// TODO: Defina as variantes (ex: Pendente, Concluido)
}
// 2. Defina a Struct que representa uma tarefa completa
struct Task {
// TODO: Defina os campos (descricao: String, status: Status)
}
impl Task {
// Dica: Uma função construtora ajuda a criar novas tarefas limpas
fn new(descricao: String) -> Task {
// TODO: Retornar uma nova Task com status Pendente
todo!()
}
}
fn main() {
// O Vetor que "segura" a memória do programa
let mut tarefas: Vec<Task> = Vec::new();
println!("--- Gerenciador de Tarefas Rust ---");
loop {
println!("\n1. Adicionar | 2. Listar | 3. Concluir | 4. Sair");
print!("Escolha: ");
io::stdout().flush().unwrap();
// Use a lógica de input que você já aprendeu...
let mut input = String::new();
io::stdin().read_line(&mut input).ok();
match input.trim() {
"1" => {
// TODO: Pedir o nome da tarefa, criar a Task e dar .push() no vetor
},
"2" => {
// TODO: Iterar sobre o vetor e imprimir (Dica: use .iter().enumerate())
},
"3" => {
// TODO: Pedir o índice (número), acessar o vetor e mudar o status
// Cuidado: Verifique se o índice existe para não dar crash!
},
"4" => break,
_ => println!("Opção inválida"),
}
}
}🧠 4. Dicas para o Sucesso
- Vetores (
Vec):- Para adicionar:
vetor.push(item). - Para acessar:
vetor[0](mas cuidado com índices inválidos). - Melhor forma de acessar com segurança:
vetor.get_mut(indice).
- Para adicionar:
- Formatando a Saída:
- Dentro do
impl Task, você pode criar um métodofn formatar(&self) -> Stringque retorna algo bonitinho como"[X] Estudar"para simplificar oprintln!namain.
- Dentro do
- Lendo Strins:
- Lembre-se que
read_lineinclui o “Enter” (\n) no final. O.trim()é seu melhor amigo para limpar o texto da descrição da tarefa.
- Lembre-se que
✅ Solução
Abaixo segue uma possível solução para o problema em questão.
use std::io::{self, Write};
use std::fmt;
/// Representa o estado atual de uma tarefa no sistema.
#[derive(Debug, Clone, PartialEq)]
enum Status {
Pendente,
Concluido,
}
/// Implementação da trait Display para formatar a exibição do Status.
impl fmt::Display for Status {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let label = match self {
Status::Pendente => "Pendente",
Status::Concluido => "Concluído",
};
write!(f, "{}", label)
}
}
/// Estrutura principal que define uma Tarefa.
/// Contém uma descrição textual e um status.
struct Task {
descricao: String,
status: Status,
}
impl Task {
/// Cria uma nova instância de `Task`.
/// Por padrão, toda tarefa nasce com o status `Pendente`.
fn new(descricao: String) -> Self {
Self {
descricao,
status: Status::Pendente,
}
}
/// Altera o status da tarefa para `Concluido`.
fn concluir(&mut self) {
self.status = Status::Concluido;
}
}
/// Implementação da trait Display para facilitar o uso em macros de print.
impl fmt::Display for Task {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}] - {}", self.status, self.descricao)
}
}
fn main() {
let mut tarefas: Vec<Task> = Vec::new();
println!("--- Gerenciador de Tarefas Rust ---");
loop {
println!("\n1. Adicionar | 2. Listar | 3. Concluir | 4. Sair");
let escolha = prompt("Escolha: ");
match escolha.trim() {
"1" => {
let desc = prompt("Digite a descrição: ");
if !desc.trim().is_empty() {
tarefas.push(Task::new(desc.trim().to_string()));
println!("✔ Tarefa adicionada!");
}
},
"2" => {
if tarefas.is_empty() {
println!("ℹ Nenhuma tarefa cadastrada.");
} else {
println!("\n--- Lista de Tarefas ---");
for (i, tarefa) in tarefas.iter().enumerate() {
println!("{}: {}", i, tarefa);
}
}
},
"3" => {
// Imprime a lista de tarefas para o usuário escolher qual concluir
for (i, tarefa) in tarefas.iter().enumerate() {
println!("{}: {}", i, tarefa);
}
let indice_str = prompt("Índice da tarefa: ");
// Tratamento de erro seguro para conversão de string para número
if let Ok(indice) = indice_str.trim().parse::<usize>() {
if let Some(tarefa) = tarefas.get_mut(indice) {
tarefa.concluir();
println!("✔ Tarefa {} marcada como concluída!", indice);
} else {
println!("⚠ Erro: Índice não encontrado.");
}
} else {
println!("⚠ Erro: Digite um número válido.");
}
},
"4" => {
println!("Saindo...");
break;
},
_ => println!("⚠ Opção inválida!"),
}
}
}
/// Função auxiliar para capturar entrada do teclado de forma limpa.
/// Reduz a repetição de `flush()` e `read_line()`.
fn prompt(mensagem: &str) -> String {
print!("{}", mensagem);
io::stdout().flush().expect("Falha ao limpar o buffer de saída");
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("Falha ao ler a entrada");
input
}