std::io 模块教程

Rust 的 std::io 模块是标准库的核心组成部分, 用于处理各种输入/输出(I/O)操作。 它提供了抽象的 trait 如 ReadWrite, 允许开发者统一处理文件、网络、内存和标准流等不同 I/O 来源。std::io 强调安全性,通过 io::ResultResult<T, io::Error> 的别名)强制显式错误处理,避免未检查的异常。该模块与 std::fs(文件系统操作)、std::net(网络 I/O)和 std::process(子进程 I/O)紧密集成,支持跨平台开发(Windows、Unix 等)。

1. std::io 简介

  • 导入和基本结构:通常用 use std::io; 或指定如 use std::io::{Read, Write};。模块分为 trait、类型和函数三大类。
    • Trait 概述
      • Read:从源读取字节,支持 readread_exactread_to_end 等方法。
      • Write:向目标写入字节,支持 writewrite_allflush
      • BufRead:缓冲读取扩展 Read,添加 read_linelines
      • Seek:定位流,支持 seek 以移动读/写位置。
    • 类型io::Error(带 kind()ErrorKind::NotFound)、BufReader/BufWriter(缓冲器)、Cursor(内存流)、Stdin/Stdout/Stderr(标准流)、Bytes/Chain/Take 等适配器。
    • 函数copy(流复制)、empty(空读取器)、repeat(重复字节)、sink(丢弃写入器)。
  • 设计哲学std::io 是零成本抽象,编译时内联;错误通过枚举 ErrorKind 分类,便于模式匹配。
  • 跨平台注意:Windows 用 UTF-16 路径,但 io 抽象它;Unix 支持非阻塞 I/O。
  • 性能基础:小 I/O 操作开销大,用缓冲减少系统调用。
  • 常见用例:CLI 输入、日志写入、文件处理、网络数据流。

2. 标准输入/输出/错误流

标准流是进程与环境的接口。stdin 用于输入,stdout/stderr 用于输出。它们是锁定的(线程安全),但默认缓冲可能导致延迟。

示例:基本标准输入(单行)

use std::io::{self, BufRead};

fn main() -> io::Result<()> {
    let stdin = io::stdin();
    let mut input = String::new();
    stdin.read_line(&mut input)?;
    println!("你输入: {}", input.trim());
    Ok(())
}
  • 解释stdin() 返回 Stdin(实现 ReadBufRead)。read_line 读取到 \n,包括换行符;trim 移除空白。错误如中断(Ctrl+C)返回 ErrorKind::Interrupted

示例:多行输入循环(扩展处理 EOF)

use std::io::{self, BufRead};

fn main() -> io::Result<()> {
    let stdin = io::stdin();
    let mut lines = stdin.lines();
    let mut count = 0;

    loop {
        match lines.next() {
            Some(Ok(line)) => {
                count += 1;
                println!("行 {}: {}", count, line);
            }
            Some(Err(e)) => return Err(e),  // 传播错误
            None => break,  // EOF(如 Ctrl+D)
        }
    }
    println!("总行数: {}", count);
    Ok(())
}
  • 解释lines() 是懒惰迭代器,返回 Result<String>。循环处理 EOF(None)和错误。性能:缓冲内部处理大输入。陷阱:不 flush stdout 可能延迟输出。

示例:标准输出和错误(带 flush 和格式化)

use std::io::{self, Write};

fn main() -> io::Result<()> {
    let mut stdout = io::stdout().lock();  // 锁定以高效写入
    let mut stderr = io::stderr();

    write!(&mut stdout, "正常输出: ")?;
    writeln!(&mut stdout, "值 {}", 42)?;
    stdout.flush()?;  // 立即发送

    writeln!(&mut stderr, "错误消息")?;
    Ok(())
}
  • 解释lock() 返回锁守卫,提高连续写入效率。write!/writeln! 是格式化宏。flush 必要于无缓冲场景。stderr 用于日志分离。扩展:用 eprintln! 宏简化 stderr。

3. Read 和 Write Trait

这些 trait 是 I/O 的基础,允许泛型代码处理任何来源。

示例:从文件读取字节(基本 Read)

use std::fs::File;
use std::io::Read;

fn main() -> std::io::Result<()> {
    let mut file = File::open("data.bin")?;
    let mut buffer = [0u8; 1024];  // 固定缓冲
    let bytes_read = file.read(&mut buffer)?;
    println!("读取 {} 字节: {:?}", bytes_read, &buffer[..bytes_read]);
    Ok(())
}
  • 解释read 填充缓冲,返回读取字节数(可能少于缓冲大小)。EOF 返回 Ok(0)。性能:循环 read 直到 0 处理大文件。

示例:完整读取文件(read_to_end 扩展)

use std::fs::File;
use std::io::Read;

fn main() -> std::io::Result<()> {
    let mut file = File::open("text.txt")?;
    let mut contents = Vec::new();
    file.read_to_end(&mut contents)?;
    println!("内容: {}", String::from_utf8_lossy(&contents));
    Ok(())
}
  • 解释read_to_end 读取所有剩余字节到 Vec。read_to_string 类似但转为 String,处理 UTF-8。陷阱:大文件可能 OOM;用流处理代替。

示例:写入字节(基本 Write)

use std::fs::File;
use std::io::Write;

fn main() -> std::io::Result<()> {
    let mut file = File::create("output.bin")?;
    let data = b"binary data";
    let bytes_written = file.write(data)?;
    assert_eq!(bytes_written, data.len());  // 检查完整写入
    file.flush()?;
    Ok(())
}
  • 解释write 返回写入字节数,可能部分(中断时)。write_all 循环直到全部写入。flush 提交到磁盘。

示例:自定义类型实现 Read/Write

use std::io::{self, Read, Write};

#[derive(Debug)]
struct ReverseReader<'a>(&'a [u8]);

impl<'a> Read for ReverseReader<'a> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let n = std::cmp::min(buf.len(), self.0.len());
        for (i, &byte) in self.0[..n].iter().rev().enumerate() {
            buf[i] = byte;
        }
        self.0 = &self.0[n..];
        Ok(n)
    }
}

fn main() -> io::Result<()> {
    let data = b"hello";
    let mut reader = ReverseReader(data);
    let mut buf = vec![0; 5];
    reader.read_exact(&mut buf)?;
    println!("反转读取: {:?}", String::from_utf8_lossy(&buf));  // "olleh"
    Ok(())
}
  • 解释:自定义 Read 反转字节。生命周期 'a 确保借用安全。扩展:实现 Write 用于日志记录器。

4. 缓冲 I/O:BufReader 和 BufWriter

缓冲减少系统调用,提高效率(默认 8KB)。

示例:BufReader 读取大文件行(性能分析)

use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::time::Instant;

fn main() -> io::Result<()> {
    let start = Instant::now();
    let file = File::open("large_log.txt")?;
    let reader = BufReader::with_capacity(64 * 1024, file);  // 自定义 64KB 缓冲
    let line_count = reader.lines().count();
    println!("行数: {}, 耗时: {:?}", line_count, start.elapsed());
    Ok(())
}
  • 解释with_capacity 自定义缓冲大小。lines().count() 高效计数。性能:无缓冲读小块慢;缓冲适合顺序访问。陷阱:随机访问用 Seek,但缓冲可能失效。

示例:BufWriter 批量写入(带错误回滚)

use std::fs::File;
use std::io::{self, BufWriter, Write};

fn main() -> io::Result<()> {
    let file = File::create("log.txt")?;
    let mut writer = BufWriter::new(file);

    for i in 0..10000 {
        if writeln!(&mut writer, "日志 {}", i).is_err() {
            // 错误时尝试 flush,但实际中用 transaction
            let _ = writer.flush();
            break;
        }
    }
    writer.flush()?;  // 提交所有
    Ok(())
}
  • 解释:批量 writeln 缓冲写入。flush 在结束或错误时调用。扩展:错误时缓冲可能丢失部分数据;用外部 crate 如 atomicwrites 处理原子性。

5. Seek Trait 和 Cursor

Seek 允许非顺序访问,Cursor 用于内存测试。

示例:Seek 在文件中定位(随机访问)

use std::fs::File;
use std::io::{self, Read, Seek, SeekFrom};

fn main() -> io::Result<()> {
    let mut file = File::open("data.txt")?;
    file.seek(SeekFrom::End(-10))?;  // 从末尾倒数 10 字节
    let mut buf = vec![0; 10];
    file.read_exact(&mut buf)?;
    println!("最后 10 字节: {}", String::from_utf8_lossy(&buf));
    Ok(())
}
  • 解释SeekFrom::End(offset) 相对末尾。Current 用于当前位置。陷阱:管道或网络流不支持 Seek(返回 Unsupported)。

示例:Cursor 用于内存 I/O 测试(扩展模拟文件)

use std::io::{self, Cursor, Read, Seek, SeekFrom, Write};

fn main() -> io::Result<()> {
    let mut cursor = Cursor::new(Vec::new());
    cursor.write_all(b"test data")?;
    cursor.seek(SeekFrom::Start(5))?;
    let mut buf = vec![0; 4];
    cursor.read_exact(&mut buf)?;
    println!("从位置 5: {}", String::from_utf8_lossy(&buf));  // "data"

    // 扩展:获取内部 Vec
    let inner = cursor.into_inner();
    println!("完整数据: {:?}", inner);
    Ok(())
}
  • 解释Cursor<Vec<u8>> 像可变文件。into_inner 获取底层数据。用于单元测试 I/O 代码无实际文件。

6. 错误处理和 io::Error

io::Errorkind()raw_os_error() 等,用于细粒度处理。

示例:详细错误分类(重试逻辑)

use std::fs::File;
use std::io::{self, Read};
use std::thread::sleep;
use std::time::Duration;

fn read_with_retry(path: &str, retries: u32) -> io::Result<Vec<u8>> {
    let mut attempts = 0;
    loop {
        match File::open(path) {
            Ok(mut file) => {
                let mut contents = Vec::new();
                file.read_to_end(&mut contents)?;
                return Ok(contents);
            }
            Err(e) => {
                match e.kind() {
                    io::ErrorKind::NotFound => return Err(e),  // 不可恢复
                    io::ErrorKind::PermissionDenied => return Err(e),
                    io::ErrorKind::Interrupted | io::ErrorKind::TimedOut => {
                        attempts += 1;
                        if attempts > retries {
                            return Err(e);
                        }
                        sleep(Duration::from_secs(1));  // 重试
                    }
                    _ => return Err(e),  // 其他错误
                }
            }
        }
    }
}

fn main() {
    match read_with_retry("temp.txt", 3) {
        Ok(data) => println!("数据: {:?}", data),
        Err(e) => println!("最终错误: {} ({:?})", e, e.kind()),
    }
}
  • 解释kind() 分类错误,重试可恢复的如 Interrupted。raw_os_error 获取 OS 错误码(errno)。扩展:日志 e.to_string() 或 e.source() 链错误。

7. 高级主题:Copy、适配器和自定义 I/O

  • io::copy:高效复制,支持缓冲。
  • 适配器:io::Chain(链流)、io::Take(限制字节)、io::Bytes(字节迭代)。

示例:io::copy 与适配器

use std::io::{self, copy, Chain, Cursor, Read, Take};

fn main() -> io::Result<()> {
    let header = Cursor::new(b"header\n");
    let body = Cursor::new(b"body content that is long");
    let limited_body = body.take(10);  // 限制 10 字节
    let mut chained = Chain::new(header, limited_body);

    let mut sink = io::sink();  // 丢弃写入
    let copied = copy(&mut chained, &mut sink)?;
    println!("复制字节: {}", copied);  // 17 (header 7 + 10 body)

    Ok(())
}
  • 解释Chain 连接流。Take 限制读取。sink 丢弃数据,用于测试。empty 是空 Read。

示例:自定义适配器(加密读取器)

use std::io::{self, Read};

struct XorReader<R: Read> {
    inner: R,
    key: u8,
}

impl<R: Read> XorReader<R> {
    fn new(inner: R, key: u8) -> Self {
        Self { inner, key }
    }
}

impl<R: Read> Read for XorReader<R> {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let n = self.inner.read(buf)?;
        for byte in &mut buf[..n] {
            *byte ^= self.key;  // 简单 XOR 加密
        }
        Ok(n)
    }
}

fn main() -> io::Result<()> {
    let data = Cursor::new(b"secret");
    let mut encrypted = XorReader::new(data, 0xAA);
    let mut output = vec![0; 6];
    encrypted.read_exact(&mut output)?;
    println!("加密: {:?}", output);  // XOR 结果
    Ok(())
}
  • 解释:包装 Read 应用 XOR。扩展自定义 I/O,如压缩或日志。

8. 最佳实践和常见陷阱

  • 缓冲策略:默认 8KB 适合大多数;大文件用更大容量。避免无缓冲小读(高开销)。
  • 错误最佳实践:分类 kind();重试 Transient 错误;日志完整 e(包括 backtrace 用 anyhow crate)。
  • 性能陷阱:循环小 read/write 慢(用缓冲);flush 过多减速(批量后 flush)。
  • 安全性:验证输入大小避免缓冲溢出;处理 Interrupted(信号)以重试。
  • 跨平台扩展:Windows 行结束 \r\n,Unix \n;用 lines() 抽象。
  • 测试扩展:用 Cursor/Vec 测试 Read/Write 无文件;mock trait 用 trait objects。
  • 与异步:同步 io 阻塞;用 tokio::io 异步版本。
  • 资源管理:drop 时自动关闭,但显式 flush/close 好习惯。
  • 常见错误扩展
    • 部分写入:write 返回 < len,循环 write_all。
    • InvalidData:非 UTF-8,用 from_utf8_lossy 处理。
    • WouldBlock:非阻塞模式,返回后重试。
    • Os 特定:用 raw_os_error 检查 errno。

练习建议

  1. 编写 CLI:从 stdin 读取 JSON,解析并写 stdout,用 BufReader 和 serde (外部,但模拟 io)。
  2. 实现日志旋转:用 Write 写入文件,检查大小后 Seek 重置。
  3. 创建管道模拟:用 Chain 和 Take 组合流,copy 到 BufWriter。
  4. 处理错误重试:写函数读网络流(模拟 Cursor),重试 TimedOut 3 次。
  5. 基准测试:比较 BufReader vs 裸 Read 大文件时间,用 Instant。