← Índice

4.1 El lenguaje de las máquinas

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

Un buen artesano lleva registros. Un artesano deficiente confía en la memoria - y eso funciona bien hasta que tres pedidos han desaparecido y hay que explicarlos. La memoria es cómoda: siempre está a mano y siempre, por regla, ya es ligeramente diferente. Los registros son incómodos: hay que llevarlos. Pero solo uno de los dos sobrevive a una puerta cerrada.

Lo que necesitas

tq puede añadir tareas, completarlas y encontrarlas por identificador. Cada vez - desde cero. Todo lo añadido en la ejecución anterior desapareció con ella. tq no tiene registros.

Para guardar tareas en un archivo, deben traducirse a un formato de texto. JSON encaja: es legible por personas, legible por máquinas, y la mayoría de herramientas lo entienden. La herramienta de traducción - serde - ya está en su lugar. Lo que queda es decirle a Task cómo usarla.

El vocabulario

En src/task/mod.rs, añade la importación y los atributos a Status y Task:

// src/task/mod.rs - CAMBIADO: importación de serde; derives en Status y Task
use crate::error::{TqError, TqResult};
use serde::{Deserialize, Serialize};
use std::fmt;

#[derive(Debug, Serialize, Deserialize)]
pub enum Status {
    Todo,
    Done,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Task {
    pub id: u64,
    pub title: String,
    pub status: Status,
}

#[derive(Serialize, Deserialize)] es necesario en Status además de en Task: serde sabe manejar tipos estándar - números, cadenas - pero Status es un tipo propio, y sin instrucción explícita no puede serializarlo.

Añade una línea en src/main.rs para confirmar que la traducción funciona:

// src/main.rs - añadir antes de Ok(())
let json = serde_json::to_string_pretty(store.get(1)?).unwrap();
println!("as JSON:\n{}", json);

El .unwrap() aquí es temporal: el tipo de error de serde_json todavía no está conectado a TqResult.

El resultado

#1: Buy coffee [Todo]
#2: Buy milk [Todo]
#3: Buy eggs [Todo]
found: #1: Buy coffee [Todo]
task 99 not found
done: true
rejected: task title cannot be empty
as JSON:
{
  "id": 1,
  "title": "Buy coffee",
  "status": "Done"
}

Dos nuevos tests en src/task/tests.rs - comprueban la traducción en ambas direcciones:

// src/task/tests.rs - añadir al final
#[test]
fn task_serializes_to_json() {
    let task = Task::new(1, "Buy coffee").unwrap();
    let json = serde_json::to_string(&task).unwrap();
    assert!(json.contains(r#""title":"Buy coffee""#));
    assert!(json.contains(r#""status":"Todo""#));
}

#[test]
fn task_roundtrips_through_json() {
    let task = Task::new(1, "Buy coffee").unwrap();
    let json = serde_json::to_string(&task).unwrap();
    let restored: Task = serde_json::from_str(&json).unwrap();
    assert_eq!(restored.id, task.id);
    assert_eq!(restored.title, task.title);
    assert!(!restored.is_done());
}

make ci pasa. Escribir en disco es el siguiente paso.

El código completo de tq para este capítulo está en 4-memory/01-the-language-of-machines/.


Lore: Macros procedurales

#[derive(Debug)] y #[derive(Clone)] están integrados en el compilador - no se necesita ningún crate. #[derive(Serialize, Deserialize)] es un caso diferente: esta es una macro procedural que viene del propio crate serde. Eso es exactamente lo que activa la opción --features derive del capítulo anterior.

En tiempo de compilación, lee la definición del tipo y genera una implementación del trait Serialize - la misma que podrías haber escrito a mano. No es magia - es una herramienta que escribe código por ti.

¿Por qué una feature separada? Compilar macros procedurales añade tiempo de compilación. Un crate que usa serde solo como un conjunto de traits - en una biblioteca, no en un binario - puede prescindir de la generación de código. La opción significa que no pagas por lo que no usas.