# 📝 Projeto: Gerenciador de Tarefas (To-Do CLI)
> [!NOTE] 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 [[Linguagem Rust|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 [[Linguagem Rust|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.
```rust
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.
```rust
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
}
```