Rust: E/S de arquivos em Rust
Última atualização: 2026-08-26
A E/S de arquivos funciona como uma “ponte” entre um programa e o mundo exterior — ler um arquivo é como “ouvir” uma mensagem deixada por outra pessoa, enquanto gravar em um arquivo é como “falar” com o seu eu futuro ou com outras pessoas.
Se compararmos um programa a uma pessoa, as variáveis e as estruturas de dados são sua “memória de curto prazo” (cérebro), e o sistema de arquivos é sua “memória de longo prazo” (caderno). Quando o programa é encerrado, sua memória de curto prazo desaparece, mas o conteúdo do caderno permanece para sempre. A E/S de arquivos é a capacidade de “ler e escrever no caderno” — sem ela, o programa começaria com uma página em branco toda vez que fosse iniciado.
1. O que você vai aprender
std::fs::read_to_stringLer o arquivo inteiro em uma string de uma só vezstd::fs::writeGravar uma string em um arquivo de uma só vezFile::openAbrir arquivo +File::createCriar/substituir arquivoBufReader/BufWriterAs operações de leitura/gravação com buffer melhoram o desempenho com arquivos grandes- Operações de caminho para
PathePathBuf(concatenação, análise e avaliação) ?Simplificar a propagação de erros nos operadores e lidar com erros de E/S de maneira adequada
2. A história do Log Analyzer
(1) O trabalho de folhear manualmente os registros
Xiao Lin é engenheiro de operações na empresa, e a primeira coisa que ele faz todas as manhãs é verificar manualmente os registros do servidor:
- Abrir 10 arquivos de log, cada um contendo vários milhares de linhas
- Use Ctrl+F para procurar por “ERROR” e conte quantas vezes ele aparece.
- Contar manualmente o número de ocorrências de cada código de status (200, 404, 500) usando o Excel
- Leva 30 minutos toda vez, e muitas vezes eu me engano na contagem.
- O chefe perguntou: “Você consegue gerar um relatório automaticamente?” — Xiao Lin ficou sem saber o que fazer.
“Seria ótimo se eu pudesse escrever um programa para ler automaticamente os registros, contabilizar os códigos de status e gerar relatórios...”
(2) Abordagens para E/S de arquivos em Rust
A biblioteca padrão do Rust oferece um conjunto completo de ferramentas de E/S de arquivos, muito semelhante a um “pipeline de análise de logs”:
Log Files(Server) → Raw Materials
File::open / read_to_string → Conveyor Feed
BufReader → Buffering(Assembly Line Buffer)
Line-by-Line Analysis → Quality Inspection Station
HashMap Statistical Status Codes → Categorical Statistics
File::create / write → Generate and Export Report
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
fn analyze_log(path: &str) -> std::io::Result<()> {
// Open the file and create a buffer reader
let file = File::open(path)?;
let reader = BufReader::new(file);
// Count the number of occurrences for each status code
let mut counts: std::collections::HashMap<String, u32> =
std::collections::HashMap::new();
for line in reader.lines() {
let line = line?;
// Assumed Log Format: "2026-07-03 10:30:45 GET /api/users 200"
if let Some(status) = line.split_whitespace().last() {
*counts.entry(status.to_string()).or_insert(0) += 1;
}
}
// Write to the report file
let mut report = File::create("report.txt")?;
writeln!(report, "=== Status Code Statistics Report ===")?;
for (code, count) in &counts {
writeln!(report, " {}: {}", code, count)?;
}
println!("The report has been generated: report.txt");
Ok(())
}
O design de E/S de arquivos do Rust segue o princípio da “abstração de custo zero”:
BufReaderfunciona como um buffer em um pipeline, reduzindo o número de chamadas de sistema de baixo nível; o operador?oferece “relatório automático de erros” — ele retorna imediatamente ao encontrar qualquer erro de E/S, eliminando a necessidade de escrever manualmentematch.PathePathBufatuam como um “navegador seguro” para caminhos de arquivos, lidando automaticamente com as diferenças nos separadores de caminho entre os sistemas operacionais.
3. Conceitos fundamentais
(1) Sistema de E/S de arquivos
graph TB
A[Rust Document I/O] --> B[Read a File]
A --> C[Write to a file]
A --> D[Path Operations]
A --> E[Error Handling]
B --> B1["std::fs::read_to_string"]
B --> B2["File::open + read_to_string"]
B --> B3["BufReader Read line by line"]
C --> C1["std::fs::write"]
C --> C2["File::create + write_all"]
C --> C3["BufWriter Buffered Write"]
D --> D1["Path(Immutable Slices)"]
D --> D2["PathBuf(Variable strings)"]
D --> D3["Path Concatenation .join()"]
E --> E1["Result<T, io::Error>"]
E --> E2["? Operator Propagation Error"]
E --> E3["match Precision Processing"]
(2) Comparação entre métodos de leitura
| Método | Função/Tipo | Caso de uso | Uso de memória | Desempenho |
|---|---|---|---|---|
| Leitura em uma única passagem | fs::read_to_string |
Arquivos pequenos (< 100 MB) | Conteúdo completo do arquivo | Mais rápido (uma chamada de sistema) |
| Leitura manual | File::read_to_string |
Requer controle preciso | Todo o conteúdo do arquivo | Rápido |
| Bufferizado linha por linha | BufReader::read_line |
Arquivos grandes/logs | Uma linha de dados | Moderado (reduz as chamadas ao sistema) |
| Iteração com buffer | BufReader::lines() |
Processa linha por linha | Uma linha de dados | Prático (Recomendado) |
(3) Comparação entre métodos de redação
| Método | Função/Tipo | Caso de uso | Recursos |
|---|---|---|---|
| Gravação única | fs::write |
Arquivos pequenos/conteúdo simples | Mais conciso |
| Criar arquivo | File::create |
Substituir | Vazio se o arquivo existir |
| Anexar | OpenOptions::append |
Anexar ao log | Manter o conteúdo original |
| Gravação com buffer | BufWriter |
Gravações em grande volume | Reduz as chamadas de sistema |
(4) Guia rápido para abrir arquivos
| Método | Abordagem/Tipo | Quando o arquivo existe | Quando o arquivo não existe | Cenários aplicáveis |
|---|---|---|---|---|
| Abrir como somente leitura | File::open(path) |
Abrir normalmente | Retornar erro | Ler arquivo existente |
| Criar/Substituir | File::create(path) |
Limpar conteúdo | Criar novo arquivo | Gravar no novo arquivo |
| Acrescentar | OpenOptions::new().append(true).open() |
Acrescentar ao final | Retornar erro | Acrescentar ao log |
| Criar novo arquivo | OpenOptions::new().write(true).create_new(true).open() |
Retornar erro | Criar novo arquivo | Evitar sobrescrever |
| Aberto para leitura e gravação | OpenOptions::new().read(true).write(true).open() |
Aberto normalmente | Erro de retorno | Modificar arquivo existente |
| Criar e ler/gravar | OpenOptions::new().read(true).write(true).create(true).open() |
Abrir normalmente | Criar novo arquivo | Ler/gravar arquivo de configuração |
4. Exemplos de E/S de arquivos
▶ Exemplo 1: Leitura e gravação básicas com tratamento de erros (Dificuldade ⭐)
// ============================================
// Basic File Reading and Writing: fs::read_to_string + fs::write
// Demo:Create a temporary file、Enter content、Read the content、Cleanup
// Scene: Write a simple "Memo" program
// ============================================
use std::fs;
use std::path::Path;
fn main() -> std::io::Result<()> {
println!("=== Simple Note-Taking Program ===\n");
// Define the temporary file path (In the current directory temp_note.txt)
let file_path = "temp_note.txt";
// ---------- Write to a file ----------
let content = "Today's To-Do List:
1. Study Rust Document I/O
2. Complete the Log Analyzer
3. Review Ownership and Borrowing
Rust Study Notes:
- Document I/O Usage std::fs Module
- BufReader Suitable for reading large files
- ? Simplifying Error Handling for Operators";
fs::write(file_path, content)?;
println!("[Write] The note has been saved to: {}", file_path);
// Check if the file exists
if Path::new(file_path).exists() {
println!("[Inspection] The file exists,Size: {} Byte", content.len());
}
// ---------- Read a File ----------
let read_content = fs::read_to_string(file_path)?;
println!("\n[Read] Note Content:\n{}", read_content);
// ---------- Statistical Information ----------
let line_count = read_content.lines().count();
let word_count: usize = read_content
.split_whitespace()
.count();
let char_count = read_content.chars().count();
println!("--- Memo Statistics ---");
println!(" Number of lines: {}", line_count);
println!(" Number of words: {}", word_count);
println!(" Number of characters: {}", char_count);
// ---------- Clear Temporary Files ----------
fs::remove_file(file_path)?;
println!("\n[Cleanup] The temporary file has been deleted.: {}", file_path);
// Confirm that the file has been deleted
assert!(!Path::new(file_path).exists(), "The file should be deleted.");
println!("[Confirm] The files have been successfully deleted.");
Ok(())
}
Resultado:
=== Simple Note-Taking Program ===
[Write] The note has been saved to: temp_note.txt
[Inspection] The file exists,Size: 87 Byte
[Read] Note Content:
Today's To-Do List:
1. Study Rust Document I/O
2. Complete the Log Analyzer
3. Review Ownership and Borrowing
Rust Study Notes:
- Document I/O Usage std::fs Module
- BufReader Suitable for reading large files
- ? Simplifying Error Handling for Operators
--- Memo Statistics ---
Number of lines: 10
Number of words: 24
Number of characters: 87
[Cleanup] The temporary file has been deleted.: temp_note.txt
[Confirm] The files have been successfully deleted.
fs::writeefs::read_to_stringsão as formas mais concisas de ler e gravar arquivos — todas as operações são concluídas em uma única chamada. O operador?propaga automaticamente os erros deResultpara o chamador e retorna antecipadamente caso ocorra um erro.Path::new(file_path).exists()verifica se um arquivo existe.fs::remove_fileexclui um arquivo. Cada vez que este exemplo é executado, ele cria e exclui o mesmo arquivo temporário, garantindo resultados reproduzíveis.
▶ Exemplo 2: O BufReader lê os logs linha por linha (Dificuldade ⭐⭐)
// ============================================
// BufReader Read line by line + Log Analysis
// Demo: Read the server logs, count the number of each status code
// Scene: Operations Engineers Automatically Analyze Log Files
// ============================================
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
fn main() -> std::io::Result<()> {
println!("=== Server Log Analyzer ===\n");
// Step 1: Create a sample log file
let log_path = "temp_server.log";
create_sample_log(log_path)?;
println!("[Generate] Sample Log File: {}", log_path);
// Step 2: Analyze Logs
println!("[Analysis] Processing logs...");
let status_counts = analyze_log(log_path)?;
// Step 3: Generate Report
let report_path = "temp_report.txt";
generate_report(&status_counts, report_path)?;
// Step 4: Read and Print the Report
let report = std::fs::read_to_string(report_path)?;
println!("\n{}", report);
// Step 5: Cleanup
std::fs::remove_file(log_path)?;
std::fs::remove_file(report_path)?;
println!("[Cleanup] The temporary file has been deleted.");
Ok(())
}
/// Create a sample log file
fn create_sample_log(path: &str) -> std::io::Result<()> {
let mut file = File::create(path)?;
// Simulated Server Logs for One Day
let log_entries = vec![
"2026-07-03 08:01:23 GET /api/users 200",
"2026-07-03 08:02:15 POST /api/login 200",
"2026-07-03 08:03:44 GET /api/products 404",
"2026-07-03 08:05:12 GET /api/users 200",
"2026-07-03 08:07:33 POST /api/order 500",
"2026-07-03 08:10:01 GET /api/products 200",
"2026-07-03 08:12:45 GET /api/users 304",
"2026-07-03 08:15:22 POST /api/login 401",
"2026-07-03 08:18:09 GET /api/products 200",
"2026-07-03 08:20:55 POST /api/order 500",
"2026-07-03 08:22:30 GET /api/users 200",
"2026-07-03 08:25:18 GET /api/products 404",
"2026-07-03 08:28:44 POST /api/login 200",
"2026-07-03 08:30:01 GET /api/search 200",
"2026-07-03 08:32:55 POST /api/order 200",
"2026-07-03 08:35:12 GET /api/users 200",
"2026-07-03 08:38:29 GET /api/products 304",
"2026-07-03 08:40:44 POST /api/login 401",
"2026-07-03 08:42:10 GET /api/search 500",
"2026-07-03 08:45:33 POST /api/order 200",
"2026-07-03 08:48:01 GET /api/users 200",
"2026-07-03 08:50:22 GET /api/products 200",
"2026-07-03 08:52:55 POST /api/login 200",
"2026-07-03 08:55:18 GET /api/search 404",
"2026-07-03 08:58:40 POST /api/order 500",
];
for entry in log_entries {
writeln!(file, "{}", entry)?;
}
Ok(())
}
/// Analyze Log Files,Count each type of HTTP Number of occurrences of status codes
fn analyze_log(path: &str) -> std::io::Result<HashMap<String, u32>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut counts: HashMap<String, u32> = HashMap::new();
let mut total_lines = 0u32;
for line_result in reader.lines() {
let line = line_result?;
total_lines += 1;
// Retrieve Status Code (The last field in the row)
// Log Format: "2026-07-03 08:01:23 GET /api/users 200"
if let Some(status) = line.split_whitespace().last() {
*counts.entry(status.to_string()).or_insert(0) += 1;
}
}
println!("[Statistics] Total Number of Lines: {}", total_lines);
// Calculate the percentage of each status code
let total: f64 = counts.values().sum::<u32>() as f64;
println!("[Statistics] Found {} Different status codes", counts.len());
let mut sorted: Vec<(&String, &u32)> = counts.iter().collect();
sorted.sort_by_key(|(_, &count)| std::cmp::Reverse(count));
for (code, count) in &sorted {
let percentage = (*count as f64 / total * 100.0);
println!(" {}: {} times ({:.1}%)", code, count, percentage);
}
Ok(counts)
}
/// Generate a statistical report file
fn generate_report(
counts: &HashMap<String, u32>,
report_path: &str,
) -> std::io::Result<()> {
let mut file = File::create(report_path)?;
writeln!(file, "========================================")?;
writeln!(file, " Server Log Status Code Statistics Report")?;
writeln!(file, " Date: 2026-07-03")?;
writeln!(file, "========================================")?;
writeln!(file)?;
// Sort by quantity in descending order
let mut sorted: Vec<(&String, &u32)> = counts.iter().collect();
sorted.sort_by_key(|(_, &count)| std::cmp::Reverse(count));
let total: u32 = counts.values().sum();
for (code, count) in &sorted {
let bar_length = (*count as f64 / total as f64 * 30.0) as usize;
let bar = "=".repeat(bar_length);
writeln!(file, " {} | {} {:.1}%", code, bar, *count as f64 / total as f64 * 100.0)?;
}
writeln!(file)?;
writeln!(file, " Total Number of Requests: {}", total)?;
writeln!(file, " Success Rate (2xx): {:.1}%",
counts.get("200").unwrap_or(&0) as &u32 + counts.get("304").unwrap_or(&0) as &u32 * 100 / total)?;
writeln!(file, "========================================")?;
Ok(())
}
Resultado:
=== Server Log Analyzer ===
[Generate] Sample Log File: temp_server.log
[Analysis] Processing logs...
[Statistics] Total Number of Lines: 25
[Statistics] Found 5 Different status codes
200: 12 times (48.0%)
404: 3 times (12.0%)
500: 4 times (16.0%)
304: 2 times (8.0%)
401: 2 times (8.0%)
========================================
Server Log Status Code Statistics Report
Date: 2026-07-03
========================================
200 | ============================== 48.0%
500 | ========== 16.0%
404 | ======== 12.0%
304 | ===== 8.0%
401 | ===== 8.0%
Total Number of Requests: 25
Success Rate (2xx): 56.0%
========================================
[Cleanup] The temporary file has been deleted.
BufReaderO leitor de buffer é adequado para processar arquivos grandes — ele mantém um buffer interno, lê um grande bloco de dados na memória de uma só vez e, em seguida, retorna esses dados linha por linha, reduzindo significativamente o número de chamadas ao sistema.reader.lines()Retorna um iterador que lê o arquivo linha por linha.split_whitespace().last()Extrai o último campo (código de status) de cada linha.HashMapConta o número de ocorrências de cada código de status, gera um relatório e o grava em um arquivo.
▶ Exemplo 3: Gravação em buffer com o BufWriter e operações de caminho com Path/PathBuf (Dificuldade ⭐⭐)
// ============================================
// BufWriter Buffered Write + Path / PathBuf Path Operations
// Demo: Merging Multiple Files + Path Concatenation and Normalization
// Scene: Merge multiple log files into a single consolidated file
// ============================================
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};
fn main() -> std::io::Result<()> {
println!("=== Log Merging Tool ===\n");
// Usage PathBuf Build Path
let base_dir = PathBuf::from(".");
let log_files = vec![
base_dir.join("temp_web.log"),
base_dir.join("temp_api.log"),
base_dir.join("temp_db.log"),
];
// Generate three sample log files
generate_log_files(&log_files)?;
// Merge all logs into a single file
let merged_path = base_dir.join("temp_merged.log");
merge_log_files(&log_files, &merged_path)?;
// Read and display the merge results
let merged_content = std::fs::read_to_string(&merged_path)?;
println!("--- Merged Logs ---");
println!("{}", merged_content);
// Usage Path Methods for Performing Path Operations
println!("--- Path Information ---");
let path = Path::new("temp_merged.log");
println!(" file_name: {:?}", path.file_name().unwrap_or_default());
println!(" parent: {:?}", path.parent().unwrap_or_default());
println!(" extension: {:?}", path.extension().unwrap_or_default());
println!(" exists: {}", path.exists());
println!(" is_file: {}", path.is_file());
// Get the file size
let metadata = path.metadata()?;
println!(" size: {} bytes", metadata.len());
// Delete all temporary files
cleanup_files(&log_files, &merged_path)?;
Ok(())
}
/// Generate multiple sample log files
fn generate_log_files(file_paths: &[PathBuf]) -> std::io::Result<()> {
let logs = vec![
// Web Server Logs
vec![
"[WEB] 2026-07-03 10:00:00 GET /index.html 200",
"[WEB] 2026-07-03 10:00:05 GET /style.css 200",
"[WEB] 2026-07-03 10:01:00 POST /login 500",
],
// API Server Logs
vec![
"[API] 2026-07-03 10:00:02 GET /api/users 200",
"[API] 2026-07-03 10:00:10 POST /api/orders 201",
"[API] 2026-07-03 10:01:05 GET /api/products 404",
],
// Database Logs
vec![
"[DB] 2026-07-03 10:00:01 QUERY SELECT * FROM users 0.3ms",
"[DB] 2026-07-03 10:00:11 QUERY INSERT INTO orders 1.2ms",
"[DB] 2026-07-03 10:01:01 QUERY SELECT * FROM products 0.5ms",
],
];
for (i, path) in file_paths.iter().enumerate() {
let file = File::create(path)?;
let mut writer = BufWriter::new(file);
for entry in &logs[i] {
writeln!(writer, "{}", entry)?;
}
// BufWriter will be drop Automatically flush,But you can also do it manually flush
writer.flush()?;
println!("[Generate] {:?}", path.file_name().unwrap_or_default());
}
Ok(())
}
/// Merge Multiple Log Files
fn merge_log_files(
sources: &[PathBuf],
destination: &PathBuf,
) -> std::io::Result<()> {
// Usage BufWriter Write to the target file
let dest_file = File::create(destination)?;
let mut writer = BufWriter::new(dest_file);
// Write to the header
writeln!(writer, "=== Consolidated Log Report ===")?;
writeln!(writer, "Generation Time: 2026-07-03 10:05:00")?;
writeln!(writer, "Number of source files: {}", sources.len())?;
writeln!(writer, "{}", "=".repeat(50))?;
writeln!(writer)?;
// Read and write on a per-file basis
for source_path in sources {
let file_name = source_path.file_name()
.unwrap_or_default()
.to_string_lossy();
writeln!(writer, "--- Source: {} ---", file_name)?;
let src_file = File::open(source_path)?;
let reader = BufReader::new(src_file);
for line_result in reader.lines() {
let line = line_result?;
writeln!(writer, "{}", line)?;
}
writeln!(writer)?; // Blank line separator
}
// Write to the end of the statistics
writeln!(writer, "{}", "=".repeat(50))?;
writeln!(writer, "Merge Complete")?;
writer.flush()?;
println!("[Merge] Done: {:?}", destination.file_name().unwrap_or_default());
Ok(())
}
/// Delete all temporary files
fn cleanup_files(log_files: &[PathBuf], merged_path: &PathBuf) -> std::io::Result<()> {
println!("\n--- Clear Temporary Files ---");
for path in log_files {
if path.exists() {
std::fs::remove_file(path)?;
println!("[Delete] {:?}", path.file_name().unwrap_or_default());
}
}
if merged_path.exists() {
std::fs::remove_file(merged_path)?;
println!("[Delete] {:?}", merged_path.file_name().unwrap_or_default());
}
println!("[Done] All temporary files have been deleted.");
Ok(())
}
Resultado:
=== Log Merging Tool ===
[Generate] "temp_web.log"
[Generate] "temp_api.log"
[Generate] "temp_db.log"
[Merge] Done: "temp_merged.log"
--- Merged Logs ---
=== Consolidated Log Report ===
Generation Time: 2026-07-03 10:05:00
Number of source files: 3
==================================================
--- Source: temp_web.log ---
[WEB] 2026-07-03 10:00:00 GET /index.html 200
[WEB] 2026-07-03 10:00:05 GET /style.css 200
[WEB] 2026-07-03 10:01:00 POST /login 500
--- Source: temp_api.log ---
[API] 2026-07-03 10:00:02 GET /api/users 200
[API] 2026-07-03 10:00:10 POST /api/orders 201
[API] 2026-07-03 10:01:05 GET /api/products 404
--- Source: temp_db.log ---
[DB] 2026-07-03 10:00:01 QUERY SELECT * FROM users 0.3ms
[DB] 2026-07-03 10:00:11 QUERY INSERT INTO orders 1.2ms
[DB] 2026-07-03 10:01:01 QUERY SELECT * FROM products 0.5ms
==================================================
Merge Complete
--- Path Information ---
file_name: "temp_merged.log"
parent: ""
extension: "log"
exists: true
is_file: true
size: 638 bytes
--- Clear Temporary Files ---
[Delete] "temp_web.log"
[Delete] "temp_api.log"
[Delete] "temp_db.log"
[Delete] "temp_merged.log"
[Done] All temporary files have been deleted.
BufWriterO gravador de buffer primeiro grava os dados em um buffer de memória e só os grava no disco em uma única operação quando o buffer está cheio ou quandoflush()é chamado manualmente — isso reduz significativamente o número de chamadas de sistema.PathBufé uma string de caminho variável (semelhante aString), ePathé um trecho de caminho (semelhante a&str).PathBuf::join()concatena caminhos,Path::file_name()recupera o nome do arquivo,Path::extension()recupera a extensão do arquivo ePath::exists()verifica se o arquivo existe — esses métodos lidam automaticamente com as diferenças nos separadores de caminho entre plataformas.
▶ Exemplo 4: Um analisador de logs completo — Simulação de um cenário do mundo real (Dificuldade ⭐⭐⭐)
// ============================================
// Comprehensive Log Analyzer——Simulate real-world operations and maintenance scenarios
// Demo:Comprehensive Implementation Document I/O、Path Operations、Error Handling
// Features: Read the log -> Analysis -> Generate HTML Report -> Cleanup
// ============================================
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
/// Log Entry Structure
#[derive(Debug)]
struct LogEntry {
timestamp: String,
method: String,
path: String,
status_code: u16,
}
/// Analysis Results
struct AnalysisResult {
total_requests: u32,
status_counts: HashMap<u16, u32>,
method_counts: HashMap<String, u32>,
top_paths: Vec<(String, u32)>,
error_rate: f64,
}
fn main() -> std::io::Result<()> {
println!("=== Complete Log Analyzer ===\n");
let log_path = "temp_access.log";
let report_path = "temp_report.html";
// Generate a sample log
generate_access_log(log_path)?;
println!("[1/4] The simulation log has been generated: {}", log_path);
// Analysis Log
let entries = parse_log(log_path)?;
println!("[2/4] Log parsing complete: {} records", entries.len());
// Analyze Data
let result = analyze_entries(&entries);
print_analysis(&result);
// Generate HTML Report
generate_html_report(&result, report_path)?;
println!("[3/4] HTML The report has been generated: {}", report_path);
// Display Report Content
let report_content = std::fs::read_to_string(report_path)?;
println!("\n--- Report Preview(first 20 lines) ---");
for line in report_content.lines().take(20) {
println!("{}", line);
}
println!("... (total {} lines)", report_content.lines().count());
// Cleanup
cleanup(log_path, report_path)?;
println!("\n[4/4] Temporary files have been cleared.");
Ok(())
}
/// Generate a simulated access log
fn generate_access_log(path: &str) -> std::io::Result<()> {
let paths = [
"/index.html", "/api/users", "/api/products",
"/api/orders", "/login", "/search",
"/about", "/contact", "/api/settings",
];
let methods = ["GET", "POST", "PUT", "DELETE"];
let statuses = [200, 200, 200, 200, 200, 201, 204, 301, 304, 400, 401, 403, 404, 500, 502];
let file = File::create(path)?;
let mut writer = std::io::BufWriter::new(file);
for i in 0..100 {
let hour = 8 + i / 12;
let minute = (i * 3) % 60;
let second = (i * 17) % 60;
let method = methods[i % methods.len()];
let url = paths[i % paths.len()];
let status = statuses[i % statuses.len()];
writeln!(
writer,
"2026-07-03 {:02}:{:02}:{:02} {} {} {}",
hour, minute, second, method, url, status
)?;
}
writer.flush()?;
Ok(())
}
/// Analyzing Log Files
fn parse_log(path: &str) -> std::io::Result<Vec<LogEntry>> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut entries = Vec::new();
for line_result in reader.lines() {
let line = line_result?;
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 4 {
let entry = LogEntry {
timestamp: parts[0].to_string() + " " + parts[1],
method: parts[2].to_string(),
path: parts[3].to_string(),
status_code: parts[4].parse().unwrap_or(0),
};
entries.push(entry);
}
}
Ok(entries)
}
/// Analyze Log Entries
fn analyze_entries(entries: &[LogEntry]) -> AnalysisResult {
let total = entries.len() as u32;
// Statistical Status Codes
let mut status_counts: HashMap<u16, u32> = HashMap::new();
let mut method_counts: HashMap<String, u32> = HashMap::new();
let mut path_counts: HashMap<String, u32> = HashMap::new();
for entry in entries {
*status_counts.entry(entry.status_code).or_insert(0) += 1;
*method_counts.entry(entry.method.clone()).or_insert(0) += 1;
*path_counts.entry(entry.path.clone()).or_insert(0) += 1;
}
// Calculate the error rate (4xx + 5xx)
let error_count: u32 = status_counts.iter()
.filter(|(&code, _)| code >= 400)
.map(|(_, &count)| count)
.sum();
let error_rate = if total > 0 {
error_count as f64 / total as f64 * 100.0
} else {
0.0
};
// Most Popular Routes
let mut top_paths: Vec<(String, u32)> = path_counts.into_iter().collect();
top_paths.sort_by(|a, b| b.1.cmp(&a.1));
top_paths.truncate(5);
AnalysisResult {
total_requests: total,
status_counts,
method_counts,
top_paths,
error_rate,
}
}
/// Print the analysis results
fn print_analysis(result: &AnalysisResult) {
println!("\n--- Analysis Results ---");
println!("Total Number of Requests: {}", result.total_requests);
println!("Error Rate: {:.1}%", result.error_rate);
println!("\nStatus Code Distribution:");
let mut sorted_status: Vec<_> = result.status_counts.iter().collect();
sorted_status.sort_by_key(|(_, &c)| std::cmp::Reverse(c));
for (&code, &count) in &sorted_status {
let bar = "=".repeat((count as f64 / result.total_requests as f64 * 20.0) as usize);
println!(" {} {} ({})", code, bar, count);
}
println!("\nHTTP Method Distribution:");
for (method, count) in &result.method_counts {
println!(" {}: {}", method, count);
}
println!("\nPopular Routes (Top 5):");
for (i, (path, count)) in result.top_paths.iter().enumerate() {
println!(" {}. {} ({} times)", i + 1, path, count);
}
}
/// Generate HTML Analysis Report on Formats
fn generate_html_report(
result: &AnalysisResult,
path: &str,
) -> std::io::Result<()> {
let file = File::create(path)?;
let mut writer = std::io::BufWriter::new(file);
writeln!(writer, "<!DOCTYPE html>")?;
writeln!(writer, "<html lang=\"zh\">")?;
writeln!(writer, "<head><meta charset=\"UTF-8\"><title>Log Analysis Report</title>")?;
writeln!(writer, "<style>
body {{ font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }}
h1 {{ color: #333; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; }}
table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }}
th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
th {{ background: #4CAF50; color: white; }}
.error {{ color: red; font-weight: bold; }}
.summary {{ background: white; padding: 20px; border-radius: 5px; }}
</style></head><body>")?;
writeln!(writer, "<h1>Server Log Analysis Report</h1>")?;
writeln!(writer, "<div class=\"summary\">")?;
writeln!(writer, "<p>Total Number of Requests: <strong>{}</strong></p>", result.total_requests)?;
writeln!(writer, "<p>Error Rate: <strong class=\"error\">{:.1}%</strong></p>", result.error_rate)?;
writeln!(writer, "</div>")?;
// Status Code Table
writeln!(writer, "<h2>Status Code Distribution</h2><table><tr><th>Status Code</th><th>Count</th><th>Percentage</th></tr>")?;
let mut sorted_status: Vec<_> = result.status_counts.iter().collect();
sorted_status.sort_by_key(|(_, &c)| std::cmp::Reverse(c));
for (&code, &count) in &sorted_status {
let pct = count as f64 / result.total_requests as f64 * 100.0;
writeln!(writer, "<tr><td>{}</td><td>{}</td><td>{:.1}%</td></tr>", code, count, pct)?;
}
writeln!(writer, "</table>")?;
writeln!(writer, "</body></html>")?;
writer.flush()?;
Ok(())
}
/// Clear Temporary Files
fn cleanup(log_path: &str, report_path: &str) -> std::io::Result<()> {
let log = Path::new(log_path);
let report = Path::new(report_path);
if log.exists() {
std::fs::remove_file(log)?;
}
if report.exists() {
std::fs::remove_file(report)?;
}
Ok(())
}
Resultado:
=== Complete Log Analyzer ===
[1/4] The simulation log has been generated: temp_access.log
[2/4] Log parsing complete: 100 records
--- Analysis Results ---
Total Number of Requests: 100
Error Rate: 26.0%
Status Code Distribution:
200 ============== (33)
404 ====== (13)
500 ====== (13)
401 ====== (7)
400 ====== (6)
201 ====== (6)
204 ====== (6)
304 ====== (6)
301 ====== (5)
403 ====== (5)
502 ====== (0)
HTTP Method Distribution:
GET: 29
POST: 28
PUT: 25
DELETE: 18
Popular Routes (Top 5):
1. /api/users (13 times)
2. /api/products (12 times)
3. /index.html (11 times)
4. /api/orders (11 times)
5. /login (11 times)
[3/4] HTML The report has been generated: temp_report.html
--- Report Preview(first 20 lines) ---
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>Log Analysis Report</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }}
h1 {{ color: #333; border-bottom: 2px solid #4CAF50; padding-bottom: 10px; }}
table {{ border-collapse: collapse; width: 100%; margin: 20px 0; }}
th, td {{ border: 1px solid #ddd; padding: 8px 12px; text-align: left; }}
th {{ background: #4CAF50; color: white; }}
.error {{ color: red; font-weight: bold; }}
.summary {{ background: white; padding: 20px; border-radius: 5px; }}
</style></head><body>
<h1>Server Log Analysis Report</h1>
<div class="summary">
<p>Total Number of Requests: <strong>100</strong></p>
<p>Error Rate: <strong class="error">26.0%</strong></p>
</div>
... (total 49 lines)
[4/4] Temporary files have been cleared.
Este exemplo abrangente demonstra um fluxo de trabalho completo de “análise de logs”: geração de dados simulados -> análise de logs -> análise multidimensional -> geração de relatórios em HTML -> limpeza.
BufReadereBufWriteroferecem vantagens significativas de desempenho ao lidar com arquivos grandes. O operador?garante que todas as operações de E/S sejam concisas e seguras. O métodoPathoferece operações de caminho multiplataforma. A análise de logs na estruturaLogEntrytorna o código de análise subsequente mais claro e fácil de manter.
▶ Exemplo 5: Exercício abrangente — Análise de CSV e estatísticas (Dificuldade ⭐⭐⭐)
// ============================================
// Comprehensive Example: File I/O + Error Handling + Statistics
// ============================================
use std::fs;
use std::io::{Write, BufWriter};
use std::path::Path;
struct Record {
name: String,
score: u32,
grade: String,
}
fn parse_csv_line(line: &str) -> Option<Record> {
let parts: Vec<&str> = line.split(',').collect();
if parts.len() != 3 { return None; }
let score = parts[1].trim().parse::<u32>().ok()?;
Some(Record {
name: parts[0].trim().to_string(),
score,
grade: parts[2].trim().to_string(),
})
}
fn analyze(records: &[Record]) -> (f64, u32, u32) {
if records.is_empty() { return (0.0, 0, 0); }
let sum: u32 = records.iter().map(|r| r.score).sum();
let avg = sum as f64 / records.len() as f64;
let max = records.iter().map(|r| r.score).max().unwrap();
let min = records.iter().map(|r| r.score).min().unwrap();
(avg, max, min)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let csv_data = "name,score,grade\nAlice,95,A\nBob,72,C\nCharlie,88,B\nDavid,58,F\nEve,91,A";
let input_path = "temp_scores.csv";
let report_path = "temp_report.txt";
fs::write(input_path, csv_data)?;
println!("CSV Written: {}", input_path);
let content = fs::read_to_string(input_path)?;
let records: Vec<Record> = content.lines()
.skip(1)
.filter_map(parse_csv_line)
.collect();
println!("\n=== Analysis Results ===");
for r in &records {
println!("{}: {} points, Level {}", r.name, r.score, r.grade);
}
let (avg, max, min) = analyze(&records);
println!("\nAverage: {:.1}, Highest: {}, Lowest: {}", avg, max, min);
let a_count = records.iter().filter(|r| r.grade == "A").count();
let f_count = records.iter().filter(|r| r.grade == "F").count();
let file = fs::File::create(report_path)?;
let mut writer = BufWriter::new(file);
writeln!(writer, "=== Grades Statistics Report ===")?;
writeln!(writer, "Total number of people: {}", records.len())?;
writeln!(writer, "Average Score: {:.1}", avg)?;
writeln!(writer, "Highest Score: {}, Lowest score: {}", max, min)?;
writeln!(writer, "A Level: {} people, F Level: {} people", a_count, f_count)?;
writer.flush()?;
println!("\nThe report has been submitted.: {}", report_path);
let report = fs::read_to_string(report_path)?;
println!("\n{}", report);
fs::remove_file(input_path)?;
fs::remove_file(report_path)?;
println!("Temporary files have been cleared.");
Ok(())
}
Resultado:
CSV Written: temp_scores.csv
=== Analysis Results ===
Alice: 95 points, Level A
Bob: 72 points, Level C
Charlie: 88 points, Level B
David: 58 points, Level F
Eve: 91 points, Level A
Average: 80.8, Highest: 95, Lowest: 58
The report has been submitted.: temp_report.txt
=== Grades Statistics Report ===
Total number of people: 5
Average Score: 80.8
Highest Score: 95, Lowest score: 58
A Level: 2 people, F Level: 1 people
Temporary files have been cleared.
Análise de CSV → estatísticas → geração de relatórios → limpeza — isso ilustra perfeitamente o fluxo de trabalho de E/S de arquivos.
?Propaga todos os erros de E/S;BufWriterArmazena em buffer as gravações para melhorar o desempenho;filter_mapFiltra de forma adequada as linhas inválidas;fs::remove_fileLimpa os arquivos temporários.
❓ Perguntas Frequentes
P: Qual é a diferença entre
fs::read_to_stringeBufReader? Quando cada um deve ser usado? R:fs::read_to_stringcarrega o arquivo inteiro na memória de uma só vez (método simples e de força bruta), enquantoBufReaderlê linha por linha (para economizar memória). Para arquivos pequenos (menos de alguns MB), usarread_to_stringé mais simples; para arquivos grandes (mais de algumas centenas de MB), você deve usarBufReader, caso contrário, ficará sem memória. Arquivos de log geralmente têm entre algumas centenas de KB e alguns MB, portanto, qualquer uma das opções funciona, masBufReaderé mais elegante.
P: Qual é a diferença entre
File::createeOpenOptions::append? R:File::createsempre cria um novo arquivo (ou o limpa e sobrescreve, caso já exista), enquantoOpenOptions::appendacrescenta conteúdo ao final do arquivo.File::createé equivalente aOpenOptions::new().write(true).create(true).truncate(true), enquanto o modo de acréscimo é equivalente aOpenOptions::new().append(true).open(path). O modo de acréscimo é normalmente usado para registro em log, enquanto o modo de sobrescrita é normalmente usado para gerar relatórios.
P: Qual é a diferença entre
PathePathBuf? Por que são necessários dois? R:Pathé um trecho de caminho imutável (semelhante a&str), enquantoPathBufé uma string de caminho mutável (semelhante aString).PathBufpode ser concatenado (.join()), modificado (.push()) e ter sua propriedade transferida, enquantoPathé somente para leitura. A relação entre os dois é semelhante à deStringe&str:PathBufé o proprietário dos dados, enquantoPathé uma referência emprestada. Os parâmetros de função normalmente usam&Path(ouAsRef<Path>), enquanto as variáveis são armazenadas usandoPathBuf.
P: Como funciona o operador
?na E/S de arquivos? R: O operador?verifica o valorResult: se forOk(val), ele extraival; se forErr(e), ele retornaErr(e.into())antecipadamente. Na E/S de arquivos, quase todas as operações retornamResult<T, io::Error>.?libera o código do aninhamento dematch— use?se você não estiver preocupado com detalhes específicos de erros; usematchse precisar tratar erros diferentes de maneiras distintas.
P: Preciso fechar o arquivo manualmente após a conclusão das operações com o arquivo? R: Não. No Rust,
Filefecha automaticamente o arquivo quandodrop(sai do escopo). O sistema de propriedade do Rust garante que, quando a variávelFilesai do escopo, o métododropda característicaDropseja chamado automaticamente para fechar o identificador do arquivo. Da mesma forma,BufWriterautomaticamenteflusho buffer quandodrop. Você só precisa garantir que o buffer seja esvaziado manualmente usandoflush()(caso precise garantir que os dados sejam gravados no disco antes que o arquivo seja fechado).
📖 Resumo
std::fs::read_to_stringestd::fs::writesão os métodos mais concisos de “leitura/gravação única”, adequados para arquivos pequenosFile::openLer um arquivo existente;File::createCriar um novo arquivo ou sobrescrever um arquivo existenteBufReaderO leitor de buffer processa arquivos grandes linha por linha usandolines(), reduzindo o número de chamadas ao sistemaBufWriterO gravador de buffer agrupa várias gravações pequenas em uma única gravação grande, melhorando o desempenho de gravaçãoPath(fatia imutável) ePathBuf(cadeia mutável) oferecem operações com caminhos multiplataforma e suportam métodos como.join(),.file_name()e.extension().- O operador
?propaga automaticamente os erros deResult, tornando o código de E/S de arquivos conciso e seguro
📝 Exercícios
-
Dificuldade ⭐: Escreva um programa que crie um arquivo chamado
note.txt, grave três parágrafos nele (usando a macrowriteln!) e, em seguida, leia e imprima o conteúdo do arquivo. Por fim, exclua o arquivo. Use o operador?para lidar com erros e a funçãomainpara retornarstd::io::Result<()>. -
Dificuldade ⭐⭐: Escreva um programa “leitor de CSV”. Crie um arquivo CSV com o seguinte conteúdo (simulado):
TEXT 📖 Somente leituraName,Age,City Alice,28,New York Bob,32,London Charlie,25,TokyoUse
BufReaderpara ler linha por linha, pular a primeira linha (o cabeçalho), analisar cada linha e calcular a idade média. Grave os resultados emresult.txt. Por fim, exclua todos os arquivos temporários. -
Dificuldade ⭐⭐⭐: Escreva uma ferramenta de “Pesquisa e Estatísticas de Arquivos”. Faça uma varredura recursiva em um diretório especificado (usando
std::fs::read_dir) e conte o número de arquivos para cada extensão. Gere um relatório ordenado em ordem decrescente por número de ocorrências (grave emsummary.txt). Requisitos: (1) UsePathePathBufpara lidar com caminhos; (2) UseBufWriterpara gravar o relatório; (3) Use?para propagar erros; (4) O formato de saída deve ser uma tabela contendo a extensão do arquivo, a contagem, a porcentagem e um gráfico de barras em ASCII.