You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中如何将单个Reader的数据分流到多个Writer?

问题描述

我有如下Rust函数(为简洁省略了错误处理):

pub fn build_tar(dir: Path) -> Result<(), String> {
  let file = File::create("archive.tar.br");
  let brotli = brotli::CompressorWriter::new(file);
  let archive = tar::Builder::new(brotli);

  for file in fs::read_dir(dir) {
    archive.append(file);
  }

  archive.finish();
}

现在我想在构建归档文件且压缩前计算sha256摘要,使用sha2::Sha256(它提供了可用于io::copy的Writer能力),需要将归档的数据同时写入哈希Writer和压缩Writer。我目前的实现方式如下:

pub fn build_tar(dir: Path) -> Result<(), String> {
  let file = File::create("archive.tar.br");
  let brotli = brotli::CompressorWriter::new(file);
  let hash = sha2::Sha256::new();
  let archive1 = tar::Builder::new(brotli);
  let archive2 = tar::Builder::new(hash);

  for file in fs::read_dir(dir) {
    archive1.append(file);
    archive2.append(file);
  }

  archive1.finish();
  archive2.finish();
  HEXLOWER.encode(hash.finalize().as_ref())
}

这种写法符合Rust惯用风格吗?是否合理?我感觉不太对……

另外我曾搜索“分流Reader”或“复制Reader”相关内容但无结果,同时查阅sha2文档也没找到Sha256::new()的实例实现Write trait的相关说明。


解答

你的当前写法既不合理也不符合Rust惯用风格,核心问题有三点:

  • 重复构建归档的性能浪费:创建两个tar::Builder分别处理同一批文件,相当于把整个归档流程执行了两次,会双倍消耗CPU和IO资源,完全没必要。
  • 所有权冲突问题:hash被archive2接管后,archive2.finish()会消耗掉内部的Sha256实例,后续尝试调用hash.finalize()会直接编译报错——你无法再访问已被转移所有权的变量。
  • Sha256的Write实现误解:sha2::Sha256本身没有直接实现std::io::Write,它是通过digest::Digest trait的update方法接收数据,或启用digest的write特性后通过适配层实现std::io::Write。

正确的思路是使用分流Writer(Tee Writer):让tar::Builder只执行一次归档写入,同时把数据转发给哈希计算和压缩两个目标。下面提供两种实现方案:

方案1:自定义分流Writer

自己实现一个简单的TeeWriter,同时向两个Write目标写入数据:

use std::io::{self, Write};
use sha2::{Sha256, Digest};
use tar::Builder;
use brotli::CompressorWriter;
use std::fs::{self, File};
use std::path::Path;
use hex;

// 自定义分流Writer,同时写入两个Write实例
struct TeeWriter<W1, W2> {
    w1: W1,
    w2: W2,
}

impl<W1: Write, W2: Write> Write for TeeWriter<W1, W2> {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        // 先写入第一个目标
        let n1 = self.w1.write(buf)?;
        // 确保第二个目标写入相同长度的数据
        let n2 = self.w2.write(&buf[..n1])?;
        if n1 != n2 {
            return Err(io::Error::new(io::ErrorKind::Other, "分流Writer写入长度不匹配"));
        }
        Ok(n1)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.w1.flush()?;
        self.w2.flush()?;
        Ok(())
    }
}

pub fn build_tar(dir: &Path) -> Result<String, String> {
    // 创建压缩文件
    let file = File::create("archive.tar.br").map_err(|e| e.to_string())?;
    let brotli = CompressorWriter::new(file, 4096, 11, 0);
    // 初始化哈希计算器
    let mut hash = Sha256::new();
    
    // 创建分流Writer,同时指向压缩器和哈希计算器
    let mut tee = TeeWriter {
        w1: brotli,
        w2: &mut hash,
    };

    // 初始化归档构建器,写入分流Writer
    let mut archive = Builder::new(&mut tee);

    // 遍历目录并添加文件到归档
    for entry in fs::read_dir(dir).map_err(|e| e.to_string())? {
        let entry = entry.map_err(|e| e.to_string())?;
        let path = entry.path();
        archive.append_path(&path).map_err(|e| e.to_string())?;
    }

    // 完成归档写入
    archive.finish().map_err(|e| e.to_string())?;
    
    // 计算并返回十六进制哈希值
    Ok(hex::encode(hash.finalize()))
}

方案2:使用现成的tee crate

如果不想自己实现,可以直接使用社区维护的tee crate:

  1. 在Cargo.toml中添加依赖:
[dependencies]
tee = "0.1"
sha2 = "0.10"
tar = "0.4"
brotli = "3.4"
hex = "0.4"
  1. 修改代码:
use std::io::Write;
use sha2::{Sha256, Digest};
use tar::Builder;
use brotli::CompressorWriter;
use std::fs::{self, File};
use std::path::Path;
use hex;
use tee::TeeWriter;

pub fn build_tar(dir: &Path) -> Result<String, String> {
    let file = File::create("archive.tar.br").map_err(|e| e.to_string())?;
    let brotli = CompressorWriter::new(file, 4096, 11, 0);
    let mut hash = Sha256::new();
    
    // 使用crate提供的TeeWriter分流数据
    let mut tee = TeeWriter::new(brotli, &mut hash);
    let mut archive = Builder::new(&mut tee);

    // 遍历添加文件逻辑同方案1
    for entry in fs::read_dir(dir).map_err(|e| e.to_string())? {
        let entry = entry.map_err(|e| e.to_string())?;
        let path = entry.path();
        archive.append_path(&path).map_err(|e| e.to_string())?;
    }

    archive.finish().map_err(|e| e.to_string())?;
    Ok(hex::encode(hash.finalize()))
}

关于Sha256的Write实现说明

sha2::Sha256本身没有实现std::io::Write,但可以通过两种方式适配:

  • 直接调用Digest trait的update方法接收字节数据(如上述代码所示)。
  • 启用digest crate的write特性,使用digest::io::Writer将Sha256包装为std::io::Write实例:
    在Cargo.toml中修改依赖:
    digest = { version = "0.10", features = ["write"] }
    
    代码中使用:
    use digest::io::Writer;
    
    let mut hash_writer = Writer::new(Sha256::new());
    // 此时hash_writer可作为std::io::Write使用
    

内容的提问来源于stack exchange,提问作者ammut

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 20:57:37