← Índice

5.2 Los cuatro comandos

Esta traducción fue asistida por IA y revisada superficialmente. Si encuentras errores o algo no suena natural, escríbeme — lo agradezco.

Un artesano que explica una negativa con la misma claridad con que describe un éxito es más raro de lo que uno desearía. El motivo es comprensible: las negativas ocurren menos a menudo que los éxitos, y reciben menos atención. Este razonamiento siempre parece sólido - hasta que algo sale mal.

Lo que necesitas

tq responde a los comandos erróneos en formato debug: como se ven los datos dentro del programa, no lo que el taller se tomó la molestia de formular para las personas. El código de salida del proceso nunca se establece explícitamente. El comando add confirma el resultado con una sola palabra, sin nombrar cuál es ese resultado.

La construcción

Concretamente: cargo run -- add "" actualmente sale así:

Error: EmptyTitle

fn main() -> TqResult<()> depende del trait Termination - imprime el error con {:?}, el formato Debug. Por eso aparece EmptyTitle en lugar de title cannot be empty, que TqError es perfectamente capaz de producir. La implementación de Display está ahí; solo que todavía no se usa.

Unas pocas ediciones en src/main.rs para tomar el control:

// src/main.rs - CAMBIADO
fn main() {
    if let Err(e) = run() {
        eprintln!("tq: {e}");
        std::process::exit(1);
    }
}

fn run() -> TqResult<()> {
    let cli = Cli::parse();
    let config = Config::load(Path::new("config.toml"))?;
    cli::run(cli, config)
}

eprintln! escribe en stderr - el flujo de diagnóstico. println! escribe en stdout, el flujo de salida. La distinción importa: cargo run -- list | grep coffee lee stdout; si algo va mal, el error en ese pipeline debe pasar por separado, en stderr.

{e} llama a Display - el que devuelve title cannot be empty.

process::exit(1) termina el proceso con código de salida 1. El código 0 significa éxito; cualquier valor distinto de cero significa fallo. Esta es la convención sobre la que se construyen los pipelines de shell.

run() es una función auxiliar donde vive el familiar ?. main ahora solo comprueba el resultado.


El comando add actualmente imprime una sola palabra. Es hora de darle al usuario algo más útil.

Primero, enseña a Store::add a devolver el ID de la tarea añadida. En src/store/mod.rs:

// src/store/mod.rs - CAMBIADO
pub trait Store<T: HasId> {
    fn add(&mut self, item: impl Into<T>) -> TqResult<u64>;
    // ...
}

impl Store<Task> for TaskStore {
    fn add(&mut self, item: impl Into<Task>) -> TqResult<u64> {
        let mut task = item.into();
        let id = self.next_id;
        task.id = id;
        self.next_id += 1;
        self.items.push(task);
        Ok(id)
    }
    // ...
}

Ahora en src/cli/mod.rs:

// src/cli/mod.rs - CAMBIADO
Commands::Add { title } => {
    let task = Task::new(0, &title)?;
    let id = store.add(task)?;
    persistence::save(store.all(), &path)?;
    println!("{}", store.get(id)?);
}

store.add() devuelve el ID de la tarea añadida - pasado directamente a store.get().

El comando done todavía imprime esa única palabra. Intenta corregirlo tú mismo - store.get(id)? ya es familiar. La solución completa está en el código completo de este capítulo bajo tq/.

El resultado

$ cargo run -- add "Buy coffee" && echo "success"
#1: Buy coffee [Todo] (2026-06-03)
success

&& continúa la cadena solo con código de salida 0.

$ cargo run -- add "" && echo "success"
tq: title cannot be empty

Los errores van a stderr, en texto legible. El éxito va a stdout, con detalles. make ci pasa.

El código completo de tq para este capítulo está en 5-interface/02-the-four-commands/.


Lore: Por qué fn main() -> Result imprime Debug

Rust permite escribir fn main() -> Result<(), E> - conveniente, ? funciona directamente en main. Pero cuando main devuelve Err(e), la biblioteca estándar toma el control del formateo: imprime Error: {e:?} - Debug, no Display. De ahí EmptyTitle en lugar de title cannot be empty.

Display fue escrito a mano. La biblioteca estándar no lo pidió.

El manejo explícito en run() restaura el control: ahora decidimos qué escribir y dónde.