🎯 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


📚 Referências e Fontes

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.

  1. Adicionar Tarefa: O usuário digita uma descrição (ex: “Comprar leite”) e ela é salva.
  2. Listar Tarefas: O programa mostra todas as tarefas com um ID (índice), descrição e status (Pendente ou Concluída).
    • Ex: [ ] 0: Comprar leite ou [X] 1: Estudar Rust.
  3. Concluir Tarefa: O usuário digita o ID da tarefa e o status dela muda para “Concluída”.
  4. 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 Task que agrupe a descricao (String) e o status.
  • Enums: Use um enum Status (Pendente, Concluido) dentro da Struct.
  • Vectors: Use um Vec<Task> na função main para armazenar a lista.
  • Iteração: Use um loop for para 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

  1. 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).
  2. Formatando a Saída:
    • Dentro do impl Task, você pode criar um método fn formatar(&self) -> String que retorna algo bonitinho como "[X] Estudar" para simplificar o println! na main.
  3. Lendo Strins:
    • Lembre-se que read_line inclui o “Enter” (\n) no final. O .trim() é seu melhor amigo para limpar o texto da descrição da tarefa.

✅ 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
}