Introducción
Un equipo de campo necesita dos linternas que funcionen. Dos líneas de escaneo pueden representar dos linternas distintas, una sola escaneada dos veces o una en buen estado y otra dañada. El programa debe conservar estas diferencias antes de decidir si el equipo está completo.
En este laboratorio implementarás en Rust un analizador de estados y un recopilador de activos. Conservarás los objetos físicos distintos, ignorarás los escaneos repetidos idénticos y rechazarás registros contradictorios sin modificar los archivos de origen. El siguiente laboratorio utilizará estas identidades para explicar los faltantes o excedentes de cada tipo de equipo. Son registros sintéticos, no inspecciones de objetos físicos.
Debes conocer las estructuras y enumeraciones de Rust, HashMap, Result, la lectura de archivos de texto y los bucles for básicos. El analizador, la interfaz de comandos y el formateador ya preparados permiten centrarse en las decisiones sobre identidad. El autor estima 45 minutos, incluidas la lectura y las correcciones habituales; no se ha cronometrado con principiantes.
Leer los estados sin adivinar
En este paso examinarás dos archivos de escaneos reales, predecirás sus objetos utilizables e implementarás la breve decisión de estado que permite usar la vista normal de filas del programa. Abre Terminal. El proyecto preparado está en /home/labex/project/field-kit, con Rust 1.99.1.
El siguiente export añade el directorio de Cargo a la ruta de búsqueda de comandos de este shell. $PATH conserva los directorios existentes. Solo dura la sesión actual de Terminal; repítelo en un shell nuevo. No modifica los archivos de inicio del shell.
export PATH=/home/labex/.cargo/bin:$PATH
Utiliza cd para seleccionar el proyecto; repítelo si pierdes la ubicación. pwd imprime el directorio actual para confirmar dónde se realizarán las ediciones posteriores.
cd /home/labex/project/field-kit
pwd
Debes ver /home/labex/project/field-kit. Corrige una errata de ruta con cd, sin reiniciar la VM. cat imprime un archivo sin modificarlo. Lee estas dos entradas reales:
cat data/two-good.tsv
cat data/one-damaged.tsv
TSV significa valores separados por tabuladores. Cada escaneo tiene tres campos: asset_id, type_id, condition, separados por tabuladores reales. En el primer archivo, los activos físicos a y b tienen el estado good. En el segundo, a está en buen estado y b tiene el estado damaged. Comprueba el resultado a mano: ambos archivos contienen dos objetos físicos, pero solo el primero describe dos linternas utilizables. Las palabras good y damaged son los dos estados exactos permitidos; el equipo dañado es un dato válido, no una fila de entrada incorrecta.
El proyecto proporciona src/lib.rs con estructuras y lectores TSV, src/main.rs con la interfaz de comandos y el formateador, y src/readiness.rs para el laboratorio posterior. Conserva esos archivos, Cargo.toml, rust-toolchain.toml y todos los originales de data/ sin cambios. Aquí puedes editar únicamente src/condition.rs y, en el paso siguiente, src/identity.rs, conservando sus nombres y tipos públicos.
Lee el módulo de estados:
cat src/condition.rs
La enumeración y el método de presentación ya están suministrados. impl agrupa los métodos de Condition; Self nombra esa enumeración dentro del bloque. pub expone estos nombres al lector. derive proporciona copia, presentación de depuración e igualdad. parse devuelve Result<Self, String>: Ok con un estado o Err con una explicación de error con propiedad propia. La función provisional rechaza todas las palabras, así que aún no distingue los dos archivos normales.
Antes de editar, crea un punto de recuperación solo del código fuente. mktemp -d crea un directorio nuevo sustituyendo las seis X por un sufijo único; no reutiliza una copia anterior. $() guarda la ruta impresa en una variable de sesión. && ejecuta cp -p solo si se crea el directorio; la copia conserva los metadatos del código. Las comillas mantienen unida la ruta de la variable. Si falla cualquiera de las operaciones, lee el error y consigue una copia correcta antes de editar:
rs03_before_condition=$(mktemp -d .edit-before-condition.XXXXXX) && cp -p src/condition.rs "$rs03_before_condition/condition.rs"
Abre el módulo existente con nano; -c muestra la posición del cursor:
nano -c src/condition.rs
La notación ^ de nano significa Ctrl. Pulsa Ctrl+W, escribe pub fn parse y pulsa Enter para localizar la función. Lee sus llaves de apertura y cierre. Usa las flechas y Retroceso/Supr para editar únicamente el cuerpo de este método y el nombre de su parámetro sin usar. Cambia _value por value, manteniendo el tipo del parámetro. Conserva la enumeración, derive y el método name.
Escribe una expresión match value con una rama exacta para cada palabra permitida y un comodín para cualquier otra cadena. Una rama como "good" => Ok(Self::Good), muestra la sintaxis. Construye por tu cuenta el resultado Damaged correspondiente. _ coincide con cualquier valor restante, que debe devolver Err en lugar de inventar un estado silenciosamente. format! crea un String; {value:?} incluye comillas alrededor del texto inesperado para que los espacios y la escritura sigan visibles en el diagnóstico. Úsalo en un mensaje que explique que la entrada debe ser good o damaged. Cierra las llaves de match y de la función, dejando match como resultado del método.
Pulsa Ctrl+O y después Enter para confirmar src/condition.rs; Ctrl+X sale de nano. No escribas comandos de shell dentro del editor. Lee la edición guardada:
cat src/condition.rs
cargo run compila e inicia el programa preparado. --quiet oculta el progreso de Cargo y -- pasa los argumentos siguientes al programa. rows valida y muestra cada fila del archivo real; aún no elimina duplicados.
cargo run --quiet -- rows data/two-good.tsv
asset_id type_id condition
a light-a good
b light-a good
La vista normal de filas debe mostrar ahora ambos ID y sus estados buenos. Compárala con el otro origen:
cargo run --quiet -- rows data/one-damaged.tsv
asset_id type_id condition
a light-a good
b light-a damaged
Coincide con tu predicción manual: siguen siendo dos objetos escaneados, pero solo uno está en buen estado. Has escrito una decisión real en la ruta del lector real, no un programa de demostración separado.
El contrato de entrada controlado
Lee también el plan real:
cat data/plan.tsv
Sus campos son type_id, name, required. Tanto light-a como light-b muestran Flashlight, pero son ID de tipo distintos. Un nombre es texto de presentación, no identidad. Una necesidad positiva sin escaneos es un faltante; una necesidad cero sin escaneos tiene faltante cero. El lector suministrado aplica estas reglas:
- Los ID empiezan por una letra ASCII minúscula, seguida solo de minúsculas, dígitos o
-. Distinguen mayúsculas y minúsculas. - Los estados son exactamente
goodydamaged; no se acepta ninguna otra escritura. - Los nombres son ASCII imprimible no vacío, incluidos espacios. Las cantidades son dígitos decimales con valor de 0 a 999 y admiten ceros iniciales. Los ID de tipo del plan no pueden repetirse, ni siquiera con contenido idéntico.
- La cabecera exacta de tres campos debe ir primero. Se permiten LF/CRLF, la ausencia de salto final y líneas posteriores completamente vacías. Una línea solo con espacios en blanco no está vacía. Los retornos de carro aislados no terminan líneas. No se recortan los campos: los espacios en un ID o estado son errores, mientras que los de un nombre se conservan.
Aquí ASCII significa letras inglesas básicas, dígitos, espacios y signos de puntuación; los nombres excluyen caracteres de control. Es un formato pequeño y definido, no un lector CSV general. Tu método de estados aporta la decisión entre dos estados dentro de ese lector preparado.
Recuperar una edición del código fuente
Para un error Rust, lee el archivo y la línea indicados por Cargo, vuelve a abrir el archivo, busca la función afectada y repara la edición. Guarda, sal y repite el comando fallido. Para descartar una edición accidental aún sin guardar, usa Ctrl+X y después N en la pregunta de guardado. Eso no deshace cambios guardados anteriormente.
Solo al recuperarte de un error ya guardado, restaura tu propio punto de recuperación de estados en lugar de reiniciar setup. En la misma Terminal, su variable sigue identificando la copia. En una Terminal nueva, repite PATH y cd y lista los directorios reales de esta fase:
ls -d .edit-before-condition.*
-d lista los nombres de directorio en vez de su contenido; * coincide con los sufijos generados. Ejecuta el siguiente comando, pega un nombre de directorio impreso exacto mientras el shell espera y pulsa Enter. read guarda esa línea en la variable de esta sesión:
read rs03_before_condition
Examina el archivo seleccionado. Si la ruta o la fase es incorrecta, repite la selección y la inspección; si no existe ninguna copia, repara con la referencia en lugar de adivinar un sufijo.
cat "$rs03_before_condition/condition.rs"
Tras confirmar el código anterior a la edición que quieres recuperar, este comando sustituye deliberadamente solo el archivo de estados permitido:
cp -p "$rs03_before_condition/condition.rs" src/condition.rs
Restaura el analizador incompleto, así que implementa de nuevo la decisión de estado y repite ambos comandos de filas. La referencia siguiente sirve para comparar un método concreto después de intentarlo, no para sustituir todo el módulo como operación normal de edición.
Referencia completa de estados para comparación y recuperación
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Condition { Good, Damaged }
impl Condition {
pub fn parse(value: &str) -> Result<Self, String> {
match value {
"good" => Ok(Self::Good),
"damaged" => Ok(Self::Damaged),
_ => Err(format!("condition must be good or damaged, got {value:?}")),
}
}
pub fn name(self) -> &'static str {
match self { Self::Good => "good", Self::Damaged => "damaged" }
}
}
Recopilar los activos físicos por ID
En este paso implementarás la recopilación por identidad conservando los activos físicos distintos y contando una sola vez las repeticiones idénticas. Continúa en Terminal, dentro de /home/labex/project/field-kit. Lee el origen más grande:
cat data/scans.tsv
Antes de programar, predice los objetos únicos. El activo a aparece dos veces con el mismo tipo y estado; a, b y c son tres linternas distintas de tipo light-a. El activo d pertenece a light-b, otro tipo con el mismo nombre visible en el plan. e y f pertenecen al tipo no planificado other; el equipo adicional legítimo no debe desaparecer. Deben quedar seis ID físicos, no siete ni uno por tipo o nombre.
Lee la estructura inicial del recopilador existente:
cat src/identity.rs
Scan contiene asset_id, type_id, condition y la ubicación diagnóstica line. El use crate::Scan suministrado importa esa estructura de esta biblioteca. La función toma prestado &[Scan], así que no debe alterar los registros de quien la llama ni tomar su propiedad. Su Result devuelve un vector de registros únicos o una explicación útil del conflicto.
Un HashMap puede asociar un registro a cada ID de activo. Insertar para una clave existente sustituye su valor anterior, así que búscala antes de insertar. Decide por separado qué hacer ante un ID nuevo, una repetición idéntica de ID/tipo/estado y una repetición contradictoria. No compares todo el Scan mediante igualdad derivada: un contenido idéntico aparece naturalmente en números de línea distintos. Compara tipo y estado; conserva ambos números de línea solo para el diagnóstico.
Conserva el código de esta fase antes de editar:
rs03_before_identity=$(mktemp -d .edit-before-identity.XXXXXX) && cp -p src/identity.rs "$rs03_before_identity/identity.rs"
nano -c src/identity.rs
Este código inicial no importa HashMap. Conserva use crate::Scan; y añade a su lado, al principio, la importación de la biblioteca estándar:
use std::collections::HashMap;
std es la biblioteca estándar de Rust; crate es la biblioteca del proyecto. Importar un nombre lo hace disponible en este módulo, no en todos los archivos. También se permite una implementación equivalente con un mapa ordenado u ordenación, con las importaciones que necesite realmente.
Busca con Ctrl+W pub fn collect_assets y pulsa Enter. Cambia _scans por scans, conservando el tipo del slice. Implementa las decisiones de la función; conserva sin cambios las demás definiciones y el módulo de estados ya completo.
Un enfoque con mapa empieza por un HashMap<String, Scan> mutable. Para cada escaneo prestado, get(&scan.asset_id) devuelve Some con un registro anterior o None. if let Some(previous) solo entra cuando existe un registro. Si difiere el tipo o el estado, devuelve un Err que nombre el ID completo del activo, la línea actual y la primera línea. format! puede combinar esos campos en un mensaje con propiedad propia. Si el contenido coincide, continue omite la inserción de esta repetición.
Para un ID nuevo, inserta una copia del ID y otra del registro; .clone() crea copias con propiedad propia de la entrada prestada. Tras examinar todos los escaneos, mueve los registros almacenados a un Vec nuevo y devuelve Ok con ese vector. Vec::new() crea un vector vacío y push añade un elemento. En un bucle como for (_, scan) in by_id, se descompone el par y _ ignora la clave. El orden de iteración de HashMap no está especificado; el formateador suministrado ordena los ID al mostrarlos, así que el orden interno no tiene que ser fijo.
Guarda con Ctrl+O, Enter y después Ctrl+X. Examina la función guardada antes de ejecutarla:
cat src/identity.rs
Compara primero las filas originales reales:
cargo run --quiet -- rows data/scans.tsv
Allí sigue presente la repetición de a. Ahora prueba el recopilador completo:
cargo run --quiet -- assets data/scans.tsv
asset_id type_id condition
a light-a good
b light-a good
c light-a good
d light-b damaged
e other good
f other damaged
Confirma los seis ID, sus tipos y estados. Solo desapareció la repetición idéntica de la vista recopilada; no se editó el archivo de entrada. Se conservaron los ID distintos del mismo tipo, los daños y el equipo no planificado. El siguiente paso comprueba las contradicciones, que deben rechazar todo el resultado recopilado en lugar de elegir silenciosamente el primer o último registro.
Ante un error del compilador, repara en nano la línea indicada, incluida una importación ausente si ese es el problema. Solo para deshacer un error ya guardado, la recuperación condicional siguiente usa tu propio archivo anterior al recopilador. En una Terminal nueva repite PATH y cd y lista los nombres reales de copia de esta fase:
ls -d .edit-before-identity.*
Ejecuta read, pega un nombre de directorio impreso exacto y pulsa Enter. Confirma su contenido antes de restaurarlo. Si no hay ninguna copia coincidente, usa la referencia para una reparación concreta en lugar de adivinar una ruta.
read rs03_before_identity
cat "$rs03_before_identity/identity.rs"
Tras confirmar el código que quieres recuperar, restaura únicamente el archivo de identidad permitido:
cp -p "$rs03_before_identity/identity.rs" src/identity.rs
Eso restaura el recopilador provisional original, incluidas sus importaciones originales. Vuelve a añadir la importación que necesite tu implementación y completa la función antes de probar de nuevo. No elimina el analizador de estados que ya funciona ni cambia las entradas originales.
Referencia completa de identidad para comparación y recuperación
Compara la decisión relevante después de intentar tu propia implementación y realiza una reparación local con nano. Este es el único código fuente completo de identidad usado para preparar las unidades independientes posteriores.
use crate::Scan;
use std::collections::HashMap;
pub fn collect_assets(scans: &[Scan]) -> Result<Vec<Scan>, String> {
let mut by_id: HashMap<String, Scan> = HashMap::new();
for scan in scans {
if let Some(previous) = by_id.get(&scan.asset_id) {
if previous.type_id != scan.type_id || previous.condition != scan.condition {
return Err(format!(
"line {}: conflicting asset {} (first at line {})",
scan.line, scan.asset_id, previous.line
));
}
continue;
}
by_id.insert(scan.asset_id.clone(), scan.clone());
}
let mut unique = Vec::new();
for (_, scan) in by_id {
unique.push(scan);
}
Ok(unique)
}
Rechazar conflictos y recuperarse de errores de entrada
En este paso distinguirás pruebas contradictorias, un estado desconocido y una ruta incorrecta. Continúa en Terminal, en el directorio del proyecto. Los siguientes fallos son intencionales: deben imprimir un error y devolver un estado de salida distinto de cero. No elimines ni repares los archivos de ejemplo conservados para hacerlos desaparecer.
Los ejemplos de error siguientes muestran la redacción de la implementación de referencia. Tu mensaje puede usar otras palabras: un conflicto debe identificar la ruta real, el ID completo del activo y ambas líneas de origen; un estado desconocido debe identificar su ruta y línea reales y explicar el valor rechazado. El objetivo no es reproducir exactamente el texto.
Lee primero las pruebas contradictorias:
cat data/conflict.tsv
Describen el activo a a la vez como good y damaged. Ejecuta el recopilador:
cargo run --quiet -- assets data/conflict.tsv
data/conflict.tsv: line 3: conflicting asset a (first at line 2)
La interfaz de comandos suministrada añade la ruta real de entrada al error del recopilador. No se imprime una tabla de éxito. Una implementación que conserva el último registro ocultaría pruebas y podría cambiar el resultado con solo invertir el orden del archivo. Se permiten repeticiones idénticas, pero las contradicciones requieren una investigación fuera de este ejercicio.
Recupérate seleccionando de nuevo el origen válido:
cargo run --quiet -- assets data/scans.tsv
Lee ahora deliberadamente el ejemplo que contiene un estado desconocido:
cargo run --quiet -- rows data/bad-status.tsv
data/bad-status.tsv: line 2: condition must be good or damaged, got "broken"
Es una decisión de formato, no un activo dañado: damaged es un dato válido, mientras que broken queda fuera del vocabulario controlado del curso. Lee un registro dañado válido para recuperarte:
cargo run --quiet -- rows data/one-damaged.tsv
Por último, utiliza intencionalmente un nombre de archivo en singular que no existe:
cargo run --quiet -- assets data/scan.tsv
El error nombra data/scan.tsv e indica que no encuentra el archivo. Es distinto de un error de línea dentro de un archivo existente. Corrige la ruta sin cambiar el origen real:
cargo run --quiet -- assets data/scans.tsv
Debe reaparecer la tabla de seis activos. Si una edición Rust es incorrecta, vuelve a abrir en nano el código indicado y repara la función afectada. Para deshacer un error ya guardado, usa el punto de recuperación de la fase de estados o identidad y el procedimiento explícito de confirmación con ls/read/cat enseñado antes; restaura solo ese código permitido, vuelve a implementarlo y repite este comando final. Los cambios de nano aún sin guardar se pueden descartar con Ctrl+X y N. Ninguno de los dos casos exige volver a ejecutar setup ni editar una entrada protegida. El programa ya diferencia los errores de origen de los registros repetidos, dañados y adicionales válidos. El siguiente laboratorio utilizará estas identidades únicas para calcular faltantes.
Resumen
Implementaste un analizador de una enumeración con dos estados y recopilaste el equipo por ID de activo físico. Se conservaron las identidades distintas, las repeticiones idénticas no aumentaron el resultado y las contradicciones produjeron errores con contexto de origen, en vez de sustituir pruebas silenciosamente. También te recuperaste de un estado desconocido y de un nombre de archivo incorrecto sin alterar los originales. Estos registros fiables permiten explicar los faltantes y excedentes de cada tipo.



