Introdução
Uma equipe de campo precisa de duas lanternas funcionando. Duas linhas de leitura podem representar duas lanternas diferentes, uma única lanterna lida duas vezes ou uma em bom estado e outra danificada. O programa precisa preservar essas diferenças antes de concluir se o conjunto de equipamentos está completo.
Neste laboratório, você implementará em Rust um parser de estados e um coletor de ativos. Manterá os objetos físicos distintos, ignorará leituras repetidas idênticas e rejeitará registros contraditórios sem modificar os arquivos de origem. O próximo laboratório usará essas identidades para explicar faltas ou excedentes de cada tipo de equipamento. São registros sintéticos, não inspeções de equipamentos físicos.
Você já deve conhecer estruturas e enumerações Rust, HashMap, Result, leitura de arquivos de texto e laços for básicos. O parser, a interface de comandos e o formatador preparados permitem focar nas decisões de identidade. O autor estima 45 minutos, incluindo leitura e correções comuns; esse tempo não foi medido com iniciantes.
Ler estados sem adivinhar
Nesta etapa, inspecione dois arquivos reais de leituras, preveja seus itens utilizáveis e implemente a pequena decisão de estado que faz funcionar a visualização normal de linhas do programa. Abra Terminal. O projeto preparado está em /home/labex/project/field-kit, com Rust 1.99.1.
O export a seguir acrescenta o diretório do Cargo ao caminho de busca de comandos deste shell. $PATH mantém os diretórios existentes. Isso dura apenas na sessão atual de Terminal; repita em um novo shell. Não altera arquivos de inicialização do shell.
export PATH=/home/labex/.cargo/bin:$PATH
Use cd para selecionar o projeto; repita se perder seu diretório. pwd imprime o diretório atual para você confirmar onde ocorrerão as edições posteriores.
cd /home/labex/project/field-kit
pwd
Espere /home/labex/project/field-kit. Corrija um erro de digitação no caminho com cd, sem redefinir a VM. cat imprime um arquivo sem alterá-lo. Leia estas duas entradas reais:
cat data/two-good.tsv
cat data/one-damaged.tsv
TSV significa valores separados por tabulação. Cada leitura tem três campos: asset_id, type_id, condition, separados por tabulações literais. No primeiro arquivo, os ativos físicos a e b têm estado good. No segundo, a está em bom estado e b está damaged. Confira manualmente: há dois itens físicos em ambos os arquivos, mas apenas o primeiro descreve duas lanternas utilizáveis. As palavras good e damaged são os dois estados exatos permitidos; equipamento danificado é um dado válido, não uma linha de entrada incorreta.
O projeto fornece src/lib.rs com estruturas e leitores TSV, src/main.rs com a interface de comandos e o formatador, e src/readiness.rs para o laboratório posterior. Mantenha esses arquivos, Cargo.toml, rust-toolchain.toml e todos os arquivos originais de data/ inalterados. Aqui você só pode editar src/condition.rs e, na próxima etapa, src/identity.rs, preservando seus nomes e tipos públicos.
Leia o módulo de estados:
cat src/condition.rs
A enumeração e o método de exibição já são fornecidos. impl agrupa métodos de Condition; Self nomeia essa enumeração dentro do bloco. pub expõe esses nomes ao leitor. derive fornece cópia, exibição de depuração e igualdade. parse retorna Result<Self, String>: Ok com um estado ou Err com uma explicação de erro em uma string própria. A implementação provisória atual rejeita todas as palavras, portanto ainda não distingue os dois arquivos normais.
Crie um ponto de restauração apenas do código-fonte antes de editar. mktemp -d cria um diretório novo substituindo os seis caracteres X por um sufixo único; não reutiliza um backup anterior. $() armazena o caminho impresso em uma variável da sessão. && executa cp -p somente após a criação bem-sucedida do diretório; a cópia preserva os metadados do código-fonte. As aspas mantêm unido o caminho da variável. Se uma operação falhar, leia o erro e obtenha um backup bem-sucedido antes de editar:
rs03_before_condition=$(mktemp -d .edit-before-condition.XXXXXX) && cp -p src/condition.rs "$rs03_before_condition/condition.rs"
Abra o módulo existente com nano; -c mostra a posição do cursor:
nano -c src/condition.rs
A notação ^ do nano significa Ctrl. Pressione Ctrl+W, digite pub fn parse e pressione Enter para localizar a função. Leia suas chaves de abertura e fechamento. Use as setas e Backspace/Delete para editar somente o corpo deste método e a vinculação do parâmetro não usado. Renomeie _value para value, preservando o tipo do parâmetro. Preserve a enumeração, derive e o método name.
Escreva uma expressão match value com um ramo exato para cada palavra permitida e um curinga para qualquer outra string. Um ramo como "good" => Ok(Self::Good), mostra a sintaxe. Construa você mesmo o resultado Damaged correspondente. _ corresponde a qualquer valor restante, que deve retornar Err em vez de inventar silenciosamente um estado. format! cria uma String; {value:?} inclui aspas em torno do texto inesperado para que espaços e grafia permaneçam visíveis no diagnóstico. Use isso em uma mensagem explicando que a entrada deve ser good ou damaged. Feche as chaves do match e da função, mantendo o match como resultado do método.
Pressione Ctrl+O e Enter para confirmar src/condition.rs; Ctrl+X sai do nano. Não digite comandos do shell enquanto ainda estiver no editor. Leia a edição salva:
cat src/condition.rs
cargo run compila e inicia o programa preparado. --quiet oculta o progresso do Cargo, e -- passa os argumentos seguintes ao programa. rows valida e exibe cada linha do arquivo real; ainda não elimina duplicatas.
cargo run --quiet -- rows data/two-good.tsv
asset_id type_id condition
a light-a good
b light-a good
A visualização normal de linhas agora deve mostrar os dois IDs e seus estados bons. Compare com a outra origem:
cargo run --quiet -- rows data/one-damaged.tsv
asset_id type_id condition
a light-a good
b light-a damaged
Isso corresponde à sua previsão manual: ainda são dois itens lidos, apenas um em bom estado. Você escreveu uma decisão real no caminho do leitor efetivo, não um programa de demonstração separado.
O contrato de entrada controlada
Leia também o plano real:
cat data/plan.tsv
Seus campos são type_id, name, required. light-a e light-b exibem Flashlight, mas são IDs de tipo distintos. Um nome é texto de exibição, não identidade. Exigências positivas sem leituras representam faltas; exigências zero sem leituras têm falta zero. O leitor fornecido aplica estas regras:
- IDs começam com uma letra ASCII minúscula, seguida apenas por letras minúsculas, dígitos ou
-. Diferenciam maiúsculas e minúsculas. - Os estados são exatamente
goodedamaged; nenhuma outra grafia é aceita. - Nomes são ASCII imprimível não vazio, incluindo espaços. Quantidades são dígitos decimais com valor de 0 a 999, permitindo zeros à esquerda. IDs de tipo do plano não podem se repetir, mesmo com conteúdo idêntico.
- O cabeçalho exato de três campos deve vir primeiro. LF/CRLF, ausência de quebra de linha final e linhas posteriores completamente vazias são permitidos. Linhas contendo apenas espaços em branco não são vazias. Retornos de carro isolados não são finais de linha. Campos não são aparados: espaços em um ID ou estado são erros, enquanto espaços em um nome são preservados.
ASCII aqui significa letras básicas do inglês, dígitos, espaços e pontuação; nomes excluem caracteres de controle. Este é um formato pequeno e definido, não um leitor CSV geral. Seu método de estados fornece a decisão entre dois estados dentro desse leitor preparado.
Recuperar uma edição de código-fonte
Para um erro Rust, leia o arquivo e a linha indicados pelo Cargo, reabra esse arquivo, busque a função afetada e repare a edição. Salve, saia e repita o comando que falhou. Para descartar uma edição acidental ainda não salva, use Ctrl+X e N na pergunta sobre salvar. Isso não desfaz alterações salvas anteriormente.
Somente ao recuperar um erro salvo, restaure seu próprio ponto de controle de estados em vez de reiniciar a preparação. No mesmo Terminal, sua variável ainda identifica o backup. Em um novo Terminal, repita PATH e cd, depois liste os diretórios reais desta fase:
ls -d .edit-before-condition.*
-d lista nomes de diretório em vez de conteúdo; * corresponde aos sufixos gerados. Execute o comando a seguir, cole um nome de diretório exato impresso enquanto o shell aguarda e pressione Enter. read armazena essa linha na variável desta sessão:
read rs03_before_condition
Inspecione o arquivo selecionado. Se o caminho ou a fase estiver errado, repita a seleção e a inspeção; se não houver backup, repare usando a referência em vez de adivinhar um sufixo.
cat "$rs03_before_condition/condition.rs"
Após confirmar o código-fonte anterior à edição desejado, este comando substitui deliberadamente apenas o arquivo de estados permitido:
cp -p "$rs03_before_condition/condition.rs" src/condition.rs
Isso restaura o parser incompleto, portanto implemente novamente a decisão de estado e repita os dois comandos de linhas. A referência abaixo serve para comparar um método específico após sua tentativa, não para substituir o módulo inteiro como operação normal de edição.
Referência completa de estados para comparação e recuperação
#[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" }
}
}
Coletar ativos físicos por ID
Nesta etapa, implemente a coleta por identidade preservando ativos físicos distintos e contando repetições idênticas uma única vez. Continue em Terminal, em /home/labex/project/field-kit. Leia a origem maior:
cat data/scans.tsv
Antes de programar, preveja os itens únicos. O ativo a ocorre duas vezes com o mesmo tipo e estado; a, b e c são três lanternas separadas do tipo light-a. O ativo d pertence a light-b, outro tipo com o mesmo nome de exibição no plano. e e f pertencem ao tipo não planejado other; equipamento extra legítimo não deve desaparecer. Deve haver seis IDs físicos, não sete nem um por tipo ou nome.
Leia a estrutura existente do coletor:
cat src/identity.rs
Scan contém asset_id, type_id, condition e a line de diagnóstico. O use crate::Scan fornecido importa essa estrutura desta biblioteca. A função toma emprestado &[Scan], portanto não deve alterar nem assumir a propriedade dos registros do chamador. Seu Result retorna um vetor de registros únicos ou uma explicação útil de conflito.
Um HashMap pode associar um registro a cada ID de ativo. Inserir uma chave existente substitui seu valor anterior, portanto consulte antes de inserir. Decida separadamente o que fazer com um ID novo, uma repetição idêntica de ID/tipo/estado e uma repetição contraditória. Não compare o Scan inteiro usando igualdade derivada: conteúdo idêntico ocorre naturalmente em números de linha diferentes. Compare tipo e estado; mantenha os dois números de linha apenas para o diagnóstico.
Preserve o código-fonte desta 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 não importa HashMap. Mantenha use crate::Scan; e acrescente a importação da biblioteca padrão ao lado dela, no início:
use std::collections::HashMap;
std é a biblioteca padrão do Rust; crate é a biblioteca deste projeto. Importar um nome o torna disponível neste módulo, não em todos os arquivos. Uma implementação equivalente com mapa ordenado ou ordenação também é permitida, com as importações de que realmente precisar.
Busque pub fn collect_assets com Ctrl+W e pressione Enter. Renomeie _scans para scans, preservando seu tipo de fatia. Implemente as decisões da função; mantenha as outras definições e o módulo de estados concluído inalterados.
Uma abordagem com mapa começa com um HashMap<String, Scan> mutável. Para cada leitura emprestada, get(&scan.asset_id) retorna Some com um registro anterior ou None. if let Some(previous) só entra quando existe um registro. Se o tipo ou o estado divergir, retorne um Err nomeando o ID completo do ativo, a linha atual e a primeira linha. format! pode combinar esses campos em uma mensagem própria. Se o conteúdo concordar, continue pula a inserção dessa repetição.
Para um ID novo, insira uma cópia clonada do ID e do registro; .clone() cria cópias próprias da entrada emprestada. Após examinar cada leitura, mova os registros armazenados para um novo Vec e retorne Ok com esse vetor. Vec::new() cria um vetor vazio e push acrescenta um item. Em um laço como for (_, scan) in by_id, o par é desestruturado e _ ignora a chave. A ordem de iteração do HashMap não é especificada; o formatador fornecido ordena os IDs de ativos para exibição, portanto sua ordem interna não precisa ser fixa.
Salve com Ctrl+O, Enter e Ctrl+X. Inspecione a função salva antes de executar:
cat src/identity.rs
Compare primeiro as linhas brutas reais:
cargo run --quiet -- rows data/scans.tsv
A repetição de a ainda está presente ali. Agora exercite o coletor concluído:
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
Confirme os seis IDs, tipos e estados. Apenas a repetição idêntica desapareceu da visualização coletada; o arquivo de entrada não foi editado. IDs distintos do mesmo tipo, danos e equipamentos não planejados permaneceram. A próxima etapa verifica contradições, que devem rejeitar todo o resultado coletado em vez de escolher silenciosamente o primeiro ou o último registro.
Para um erro de compilação, repare a linha indicada no nano, incluindo uma importação ausente se esse for o problema. Somente ao desfazer um erro salvo, a recuperação condicional a seguir usa seu próprio arquivo anterior ao coletor. Em um novo Terminal, repita PATH e cd, depois liste os nomes reais de backup da fase:
ls -d .edit-before-identity.*
Execute read, cole um nome de diretório exato impresso e pressione Enter. Confirme seu conteúdo antes de restaurar. Se nenhum backup corresponder, use a referência para um reparo específico em vez de adivinhar um caminho.
read rs03_before_identity
cat "$rs03_before_identity/identity.rs"
Após confirmar o código-fonte desejado, restaure apenas o arquivo de identidade permitido:
cp -p "$rs03_before_identity/identity.rs" src/identity.rs
Isso restaura a implementação provisória original do coletor, incluindo suas importações originais. Acrescente novamente a importação necessária à sua implementação e conclua a função antes de testar outra vez. Isso não remove seu parser de estados bem-sucedido nem altera as entradas brutas.
Referência completa de identidade para comparação e recuperação
Compare a decisão relevante após tentar sua própria implementação e faça um reparo local pelo nano. Este é o único código-fonte completo de identidade usado para preparar as unidades independentes 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)
}
Rejeitar conflitos e recuperar-se de erros de entrada
Nesta etapa, você distinguirá evidências contraditórias, um estado desconhecido e um caminho incorreto. Continue em Terminal, no diretório do projeto. As próximas falhas são intencionais: devem mostrar um erro e retornar um status de saída diferente de zero. Não apague nem conserte os exemplos de origem preservados para fazer os erros desaparecerem.
Os exemplos de erro abaixo mostram o texto da implementação de referência. Sua mensagem pode usar outras palavras: um conflito deve identificar o caminho real, o ID completo do ativo e ambas as linhas de origem; um estado desconhecido deve identificar o caminho e a linha reais e explicar o valor rejeitado. Reproduzir exatamente o texto não é o objetivo.
Primeiro leia as evidências contraditórias:
cat data/conflict.tsv
Elas descrevem o ativo a como good e damaged ao mesmo tempo. Execute o coletor:
cargo run --quiet -- assets data/conflict.tsv
data/conflict.tsv: line 3: conflicting asset a (first at line 2)
A interface de comandos fornecida acrescenta o caminho real de entrada ao erro do coletor. Nenhuma tabela de sucesso é impressa. Uma implementação que escolhe o último registro ocultaria evidências e poderia mudar o resultado simplesmente invertendo a ordem do arquivo. Repetições idênticas são permitidas, mas contradições exigem investigação fora deste exercício.
Recupere-se escolhendo novamente a origem válida:
cargo run --quiet -- assets data/scans.tsv
Agora leia deliberadamente o exemplo com um estado desconhecido:
cargo run --quiet -- rows data/bad-status.tsv
data/bad-status.tsv: line 2: condition must be good or damaged, got "broken"
É uma decisão de formato, não um ativo danificado: damaged é dado válido, enquanto broken está fora do vocabulário controlado do curso. Leia um registro danificado válido para se recuperar:
cargo run --quiet -- rows data/one-damaged.tsv
Por último, use intencionalmente um nome de arquivo no singular que não existe:
cargo run --quiet -- assets data/scan.tsv
O erro cita data/scan.tsv e informa que o arquivo não foi encontrado. Isso é diferente de um erro de linha dentro de um arquivo existente. Corrija o caminho, mantendo a origem real intacta:
cargo run --quiet -- assets data/scans.tsv
A tabela de seis ativos deve retornar. Se uma edição Rust estiver incorreta, reabra o código-fonte indicado no nano e repare a função afetada. Para desfazer um erro salvo, use o ponto de controle da fase de estados ou identidade e o procedimento explícito de confirmação ls/read/cat ensinado na etapa anterior; restaure apenas esse código-fonte permitido, refaça sua implementação e execute novamente este comando final. Alterações não salvas no nano podem ser descartadas com Ctrl+X e N. Nenhum caso exige executar a preparação novamente ou editar uma entrada protegida. Seu programa agora separa erros de origem de registros repetidos, danificados e extras válidos. O próximo laboratório usará essas identidades únicas para calcular faltas.
Resumo
Você implementou um parser de enumeração com dois estados e coletou equipamentos pelo ID de cada ativo físico. Identidades diferentes foram preservadas, repetições idênticas não aumentaram o resultado e contradições geraram erros com contexto de origem, em vez de substituir evidências silenciosamente. Você também se recuperou de um estado desconhecido e de um nome de arquivo incorreto, mantendo os originais. Esses registros confiáveis permitem explicar faltas e excedentes de cada tipo.



