1.4 Estados del ser
Esta traducción fue asistida por IA y revisada superficialmente. Si encuentras errores o algo no suena natural, escríbeme — lo agradezco.
Un artesano experimentado nunca etiqueta las herramientas como “primera” y “segunda.” Un principiante lo hace - y parece suficiente. Parece suficiente hasta que hay tres herramientas, o hasta que vuelve un mes después, o hasta que llega alguien más. Siempre ocurre una de estas cosas. El estado de la tarea es exactamente eso ahora mismo.
El intento
Tras el capítulo anterior, main.rs imprime el estado así:
println!("Done: {}", task.status);
Eso imprimirá true o false. No está mal, pero se necesita más: una tarea debe poder
cambiar de estado. Añade una línea después de la inicialización:
task.status = true;
El compilador responde:
error[E0594]: cannot assign to `task.status`, as `task` is not declared as mutable
--> src/main.rs:XX:XX
|
XX | task.status = true;
| ^^^^^^^^^^^^^^^^^^ cannot assign
|
help: consider changing this to be mutable
|
XX | let mut task = Task {
| +++
En Rust, los valores no pueden modificarse por defecto - ese derecho debe reclamarse
explícitamente con let mut:
let mut task = Task {
id: 1,
title: String::from("Buy coffee"),
status: false,
};
Ahora el compilador aceptará task.status = true. Pero true es el nombre de un valor
booleano, no el nombre de un estado de tarea. La diferencia parece pequeña al principio - y
en la práctica importa.
Los nombres
Para estados con nombre, Rust tiene el tipo enum, donde se puede definir un conjunto
estricto de variantes. Añádelo al principio del archivo, antes de struct Task. #[derive(Debug)]
es necesario de inmediato: el compilador no puede generar Debug para un struct si alguno
de sus campos no implementa Debug - y Task está a punto de ganar un campo de tipo Status:
#[derive(Debug)]
enum Status {
Todo,
Done,
}
No son dos valores del mismo tipo - son dos variantes distintas con sus propios nombres.
Reemplaza bool en el struct y actualiza main:
struct Task {
id: u64,
title: String,
status: Status, // antes: bool
}
// en main():
let mut task = Task {
id: 1,
title: String::from("Buy coffee"),
status: Status::Todo, // antes: false
};
// ...
task.status = Status::Done; // antes: true
Elimina println!("Done: {}", task.status) - Status no implementa Display, así que esa
línea ya no compilará. Reemplázala con un match. Pruébalo con una sola variante:
match task.status {
Status::Todo => println!("todo"),
}
El conjuro
El compilador señala el problema:
error[E0004]: non-exhaustive patterns: `Done` not covered
--> src/main.rs:XX:XX
|
XX | match task.status {
| ^^^^^^^^^^^ pattern `Done` not covered
|
note: `Status` defined here
--> src/main.rs:XX:XX
|
XX | enum Status {
| ------
XX | Todo,
XX | Done,
| ^^^^ not covered
= note: the matched value is of type `Status`
match en Rust exige que todas las variantes posibles estén cubiertas. Es una garantía: si
Status gana alguna vez una nueva variante, el compilador encontrará cada match que no la
conozca y se negará a compilar. Una variante olvidada no puede pasar desapercibida.
Añade la variante que falta y observa la transición:
#[derive(Debug)]
enum Status {
Todo,
Done,
}
#[derive(Debug)]
struct Task {
id: u64,
title: String,
status: Status,
}
fn main() {
let mut task = Task {
id: 1,
title: String::from("Buy coffee"),
status: Status::Todo,
};
println!("Task #{}: {}", task.id, task.title);
println!("{:#?}", task);
task.status = Status::Done;
println!("{:#?}", task);
match task.status {
Status::Todo => println!("todo"),
Status::Done => println!("done"),
}
}
Task #1: Buy coffee
Task {
id: 1,
title: "Buy coffee",
status: Todo,
}
Task {
id: 1,
title: "Buy coffee",
status: Done,
}
done
El primer {:#?} es la tarea antes del cambio. El segundo es después. El match confirma
el nuevo estado. make ci pasa.
El código completo de
tqpara este capítulo está en1-a-task/04-states-of-being/.
Lore: _ en match
A veces quieres manejar una variante e ignorar el resto. Para eso existe _ - un patrón
que coincide con cualquier cosa:
match task.status {
Status::Done => println!("done"),
_ => {}
}
Esto funciona - pero para Status es mala idea. El objetivo de un match exhaustivo es
precisamente que el compilador te avise cuando se añade una nueva variante. _ elimina esa
protección: añade InProgress y caerá silenciosamente bajo _ sin ningún aviso.
_ es apropiado cuando hay genuinamente muchas variantes y la mayoría se manejan igual, o
cuando el enum pertenece a alguien más y no puede controlarse. Para tu propio Status, es
mejor escribir siempre cada variante explícitamente.