Rust: عمليات الإدخال/الإخراج للملفات في Rust

آخر تحديث: 2026-08-26

تعمل عمليات الإدخال/الإخراج للملفات كـ«جسر» بين البرنامج والعالم الخارجي — فقراءة ملف ما تشبه «الاستماع» إلى رسالة تركها شخص آخر، بينما الكتابة في ملف تشبه «التحدث» إلى نفسك في المستقبل أو إلى الآخرين.

إذا قارنا البرنامج بشخص ما، فإن المتغيرات وهياكل البيانات تمثل «ذاكرته قصيرة المدى» (الدماغ)، ونظام الملفات يمثل «ذاكرته طويلة المدى» (دفتر الملاحظات). وعندما يتم إغلاق البرنامج، تختفي ذاكرته قصيرة المدى، لكن محتويات دفتر الملاحظات تبقى إلى الأبد. تعد عمليات الإدخال/الإخراج للملفات (File I/O) هي القدرة على «القراءة والكتابة في دفتر الملاحظات» — فبدونها، سيبدأ البرنامج بصفحة فارغة في كل مرة يتم تشغيله فيها.


1. ما ستتعلمه



2. قصة محلل السجلات

(1) عناء تصفح السجلات يدويًّا

شياو لين هو مهندس عمليات في الشركة، وأول ما يفعله كل صباح هو فحص سجلات الخادم يدويًّا:

«سيكون من الرائع لو تمكنت من كتابة برنامج يقوم تلقائيًا بقراءة السجلات، وحصر رموز الحالة، وإنشاء التقارير...»

(2) طرق التعامل مع عمليات الإدخال/الإخراج للملفات في لغة Rust

توفر المكتبة القياسية لـ Rust مجموعة كاملة من أدوات الإدخال/الإخراج للملفات، تشبه إلى حد كبير «خط أنابيب تحليل السجلات»:

TEXT 📖 للعرض فقط
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
RUST
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(())
}

يتبع تصميم عمليات الإدخال/الإخراج للملفات في لغة Rust مبدأ «التجريد بدون تكلفة»: يعمل BufReader كذاكرة مؤقتة في مسار البيانات، مما يقلل من عدد استدعاءات النظام ذات المستوى المنخفض؛ ويوفر المشغل ? «الإبلاغ التلقائي عن الأخطاء» — حيث يعود فور مواجهة أي خطأ في الإدخال/الإخراج، مما يلغي الحاجة إلى كتابة match يدويًّا. يعمل كل من Path وPathBuf كـ«موجه آمن» لمسارات الملفات، حيث يتعاملان تلقائيًا مع الاختلافات في فواصل المسارات عبر أنظمة التشغيل المختلفة.



3. المفاهيم الأساسية

(1) نظام إدخال/إخراج الملفات

100%
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) مقارنة بين أساليب القراءة

الطريقة الوظيفة/النوع حالة الاستخدام استهلاك الذاكرة الأداء
القراءة في مسار واحد fs::read_to_string الملفات الصغيرة (< 100 ميغابايت) محتويات الملف بالكامل الأسرع (استدعاء نظام واحد)
القراءة اليدوية File::read_to_string تتطلب تحكماً دقيقاً محتويات الملف بالكامل سريع
التخزين المؤقت سطراً بسطر BufReader::read_line الملفات/السجلات الكبيرة سطر واحد من البيانات معتدل (يقلل من استدعاءات النظام)
التكرار المؤقت BufReader::lines() المعالجة سطراً سطراً سطر واحد من البيانات ملائم (موصى به)

(3) مقارنة بين أساليب الكتابة

الطريقة الوظيفة/النوع حالة الاستخدام الميزات
الكتابة لمرة واحدة fs::write الملفات الصغيرة/المحتوى البسيط الأكثر إيجازًا
إنشاء ملف File::create الكتابة فوق الملف إفراغ الملف إذا كان موجودًا
إضافة OpenOptions::append إضافة إلى السجل الاحتفاظ بالمحتوى الأصلي
الكتابة المؤقتة BufWriter عمليات الكتابة ذات الحجم الكبير تقلل من عدد استدعاءات النظام

(4) دليل مرجعي سريع لفتح الملفات

الطريقة النهج/النوع عندما يكون الملف موجودًا عندما لا يكون الملف موجودًا السيناريوهات التي تنطبق عليها
فتح كملف للقراءة فقط File::open(path) فتح بشكل عادي إرجاع خطأ قراءة ملف موجود
إنشاء/الكتابة فوق File::create(path) مسح المحتويات إنشاء ملف جديد الكتابة إلى ملف جديد
إضافة OpenOptions::new().append(true).open() إضافة إلى النهاية إرجاع خطأ إضافة إلى السجل
إنشاء ملف جديد OpenOptions::new().write(true).create_new(true).open() ظهور خطأ إنشاء ملف جديد تجنب الكتابة فوق الملف
مفتوح للقراءة والكتابة OpenOptions::new().read(true).write(true).open() فتح عادي خطأ في الإرجاع تعديل الملف الموجود
إنشاء وقراءة/كتابة OpenOptions::new().read(true).write(true).create(true).open() فتح كالمعتاد إنشاء ملف جديد قراءة/كتابة ملف التكوين


4. أمثلة على عمليات الإدخال/الإخراج للملفات

(1) ▶ المثال:القراءة والكتابة الأساسية مع معالجة الأخطاء (مستوى الصعوبة ⭐)

RUST
// ============================================
// 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(())
}

الناتج:

TEXT 📖 للعرض فقط
=== 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::write وfs::read_to_string أكثر الطرق إيجازًا لقراءة الملفات وكتابتها — حيث تُنجز جميع العمليات في استدعاء واحد. يقوم المشغل ? تلقائيًّا بنقل الأخطاء من Result إلى المُستدعي، كما يعود مبكرًا في حالة حدوث خطأ. أما Path::new(file_path).exists() فيتحقق من وجود الملف. fs::remove_file يحذف ملفًا. في كل مرة يتم فيها تشغيل هذا المثال، يقوم بإنشاء وحذف نفس الملف المؤقت، مما يضمن نتائج قابلة للتكرار.


(2) ▶ المثال:يقوم BufReader بقراءة السجلات سطراً سطراً (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// 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(())
}

الناتج:

TEXT 📖 للعرض فقط
=== 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.

BufReader يُعد قارئ المخزن المؤقت مناسبًا لمعالجة الملفات الكبيرة — فهو يحتفظ بمخزن مؤقت داخلي، ويقوم بقراءة جزء كبير من البيانات إلى الذاكرة دفعة واحدة، ثم يعرضها سطرًا سطرًا، مما يقلل بشكل كبير من عدد استدعاءات النظام. reader.lines() يُرجع مُكررًا يقرأ الملف سطرًا سطرًا. split_whitespace().last() يستخرج الحقل الأخير (رمز الحالة) من كل سطر. HashMap يحسب عدد مرات ظهور كل رمز حالة، ويُنشئ تقريرًا، ويكتبه في ملف.


(3) ▶ المثال:الكتابة المؤقتة باستخدام BufWriter وعمليات المسار باستخدام Path/PathBuf (مستوى الصعوبة ⭐⭐)

RUST
// ============================================
// 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(())
}

الناتج:

TEXT 📖 للعرض فقط
=== 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.

BufWriter يقوم كاتب المخزن المؤقت أولاً بكتابة البيانات إلى مخزن مؤقت في الذاكرة، ولا يقوم بكتابتها على القرص إلا في عملية واحدة عندما يمتلئ المخزن المؤقت أو عند استدعاء flush() يدويًّا — مما يقلل بشكل كبير من عدد استدعاءات النظام. PathBuf هي سلسلة مسار متغيرة (مشابهة لـ String)، وPath هي شريحة مسار (مشابهة لـ &str). تقوم PathBuf::join() بربط المسارات، وتسترد Path::file_name() اسم الملف، وتسترد Path::extension() امتداد الملف، وتتحقق Path::exists() من وجود الملف — وتتعامل هذه الطرق تلقائيًا مع الاختلافات في فواصل المسارات عبر الأنظمة الأساسية المختلفة.


(4) ▶ المثال:محلل سجلات متكامل — محاكاة سيناريو واقعي (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// 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(())
}

الناتج:

TEXT 📖 للعرض فقط
=== 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.

يوضح هذا المثال الشامل مسار عمل «محلل السجلات» بالكامل: إنشاء بيانات محاكاة -> تحليل السجلات -> التحليل متعدد الأبعاد -> إنشاء تقارير HTML -> التنظيف. يوفر كل من BufReader وBufWriter مزايا أداء كبيرة عند التعامل مع الملفات الكبيرة. ويضمن المشغل ? أن تكون كل عملية إدخال/إخراج موجزة وآمنة. وتوفر الطريقة Path عمليات مسار عبر الأنظمة الأساسية المختلفة. كما أن تحليل السجلات في البنية LogEntry يجعل كود التحليل اللاحق أكثر وضوحًا وأسهل في الصيانة.


(5) ▶ المثال:تمرين شامل — تحليل ملفات CSV والإحصاءات (مستوى الصعوبة ⭐⭐⭐)

RUST
// ============================================
// 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(())
}

الناتج:

TEXT 📖 للعرض فقط
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.

تحليل ملف CSV → إحصاءات → إنشاء التقرير → التنظيف — وهذا يوضح بشكل كامل سير عمل عمليات الإدخال/الإخراج للملفات. ? ينقل جميع أخطاء الإدخال/الإخراج؛ BufWriter يخزن عمليات الكتابة مؤقتًا لتحسين الأداء؛ filter_map يقوم بتصفية الصفوف غير الصالحة بطريقة سلسة؛ fs::remove_file ينظف الملفات المؤقتة.



❓ أسئلة شائعة

س ما الفرق بين fs::read_to_string وBufReader؟ ومتى ينبغي استخدام كل منهما؟
ج يقوم fs::read_to_string بقراءة الملف بأكمله في الذاكرة دفعة واحدة (طريقة بسيطة وقوية)، بينما يقوم BufReader بقراءته سطراً سطراً (لتوفير الذاكرة). بالنسبة للملفات الصغيرة (أقل من بضعة ميغابايت)، يكون استخدام read_to_string أبسط؛ أما بالنسبة للملفات الكبيرة (أكثر من بضع مئات من الميغابايت)، فيجب عليك استخدام BufReader، وإلا ستنفد الذاكرة. تتراوح أحجام ملفات السجل عادةً بين بضع مئات من الكيلوبايت وبضعة ميغابايت، لذا فإن كلا الخيارين مناسبان، لكن BufReader أكثر أناقة.
س ما الفرق بين File::create وOpenOptions::append؟
ج يقوم File::create دائمًا بإنشاء ملف جديد (أو مسحه والكتابة فوقه إن كان موجودًا)، بينما يقوم OpenOptions::append بإضافة المحتوى إلى نهاية الملف. File::create يعادل OpenOptions::new().write(true).create(true).truncate(true)، بينما يعادل وضع الإضافة OpenOptions::new().append(true).open(path). يُستخدم وضع الإضافة عادةً للتسجيل، بينما يُستخدم وضع الكتابة فوق عادةً لإنشاء التقارير.
س ما الفرق بين Path وPathBuf؟ ولماذا نحتاج إلى الاثنين؟
ج Path عبارة عن شريحة مسار غير قابلة للتعديل (على غرار &str)، بينما PathBuf عبارة عن سلسلة مسار قابلة للتعديل (على غرار String). يمكن ربط PathBuf (.join()) وتعديله (.push()) ونقل ملكيته، بينما Path مخصص للقراءة فقط. العلاقة بين الاثنين مشابهة لتلك بين String و&str: PathBuf يمتلك البيانات، في حين أن Path هو مرجع مستعار. عادةً ما تستخدم معلمات الدوال &Path (أو AsRef<Path>)، بينما يتم تخزين المتغيرات باستخدام PathBuf.
س كيف يعمل المشغل ? في عمليات الإدخال/الإخراج للملفات؟
ج يقوم المشغل ? بفحص قيمة Result: إذا كانت Ok(val)، فإنه يستخرج val؛ وإذا كانت Err(e)، فإنه يعيد Err(e.into()) مبكرًا. في عمليات الإدخال/الإخراج للملفات، تُرجع جميع العمليات تقريبًا Result<T, io::Error>. يُحرر ? الكود من التداخل match — استخدم ? إذا لم تكن مهتمًا بتفاصيل أخطاء محددة؛ واستخدم match إذا كنت بحاجة إلى معالجة الأخطاء المختلفة بطرق مختلفة.
س هل أحتاج إلى إغلاق الملف يدويًّا بعد انتهاء العمليات المتعلقة بالملف؟
ج لا. في لغة Rust، يقوم File بإغلاق الملف تلقائيًّا عند drop (خروج المتغير من نطاقه). يضمن نظام الملكية في Rust أنه عند خروج المتغير File من نطاقه، يتم استدعاء الطريقة drop الخاصة بالسمة Drop تلقائيًا لإغلاق مقبض الملف. وبالمثل، يقوم BufWriter تلقائيًا بـ flush المخزن المؤقت عند drop. ما عليك سوى التأكد من مسح المخزن المؤقت يدويًّا باستخدام flush() (إذا كنت بحاجة إلى التأكد من كتابة البيانات على القرص قبل إغلاق الملف).

📖 ملخص


📝 تمارين

  1. الصعوبة ⭐: اكتب برنامجًا يقوم بإنشاء ملف باسم note.txt، وكتابة ثلاث فقرات فيه (باستخدام الماكرو writeln!)، ثم قراءة محتويات الملف وعرضها. وأخيرًا، احذف الملف. استخدم المشغل ? لمعالجة الأخطاء، والدالة main لإرجاع std::io::Result<()>.

  2. الصعوبة ⭐⭐: اكتب برنامجًا لـ«قراءة ملفات CSV». أنشئ ملف CSV يحتوي على المحتوى التالي (محاكاة):

    TEXT 📖 للعرض فقط
    Name,Age,City
     Alice,28,New York
     Bob,32,London
     Charlie,25,Tokyo
    

    استخدم BufReader لقراءة البيانات سطراً سطراً، وتخطي السطر الأول (العنوان)، وتحليل كل سطر، وحساب متوسط العمر. اكتب النتائج في result.txt. وأخيراً، احذف جميع الملفات المؤقتة.

  3. الصعوبة ⭐⭐⭐: اكتب أداة «البحث عن الملفات والإحصائيات». قم بمسح دليل محدد بشكل متكرر (باستخدام std::fs::read_dir) واحسب عدد الملفات لكل امتداد ملف. قم بإنشاء تقرير مرتب ترتيبًا تنازليًّا حسب العدد (اكتبه في summary.txt). المتطلبات: (1) استخدم Path وPathBuf للتعامل مع المسارات؛ (2) استخدم BufWriter لكتابة التقرير؛ (3) استخدم ? لإبلاغ الأخطاء؛ (4) يجب أن يكون تنسيق الإخراج عبارة عن جدول يحتوي على امتداد الملف، والعدد، والنسبة المئوية، ومخطط شريطي ASCII.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%