在Rust中写入文件或字符串

12

TL;DR: 我想实现 trait std::io::Write,以便于进行单元测试输出到内存缓冲区,最好是 String。

我一定漏掉了某些简单的东西。

与另一个问题类似,在 Rust 中写入文件或标准输出, 我正在处理可以使用任何 std::io::Write 实现的代码。

它操作的结构定义如下:

pub struct MyStructure {
    writer: Box<dyn Write>,
}

现在,创建可以写入文件或stdout的实例变得很容易:

impl MyStructure {
    pub fn use_stdout() -> Self {
        let writer = Box::new(std::io::stdout());
        MyStructure { writer }
    }

    pub fn use_file<P: AsRef<Path>>(path: P) -> Result<Self> {
        let writer = Box::new(File::create(path)?);
        Ok(MyStructure { writer })
    }
    
    pub fn printit(&mut self) -> Result<()> {
        self.writer.write(b"hello")?;
        Ok(())
    }
}

但是对于单元测试,我还需要一种运行业务逻辑(在这里由方法printit()表示)并捕获其输出的方式,以便可以在测试中检查其内容。

我无法想出如何实现这一点。 这个示例代码 显示了我想要使用它的方式,但它无法编译,因为它违反了借用规则。

// invalid code - does not compile!
fn main() {
    let mut buf = Vec::new(); // This buffer should receive output
    let mut x2 = MyStructure { writer: Box::new(buf) };
    x2.printit().unwrap();
    
    // now, get the collected output
    let output = std::str::from_utf8(buf.as_slice()).unwrap().to_string();
    
    // here I want to analyze the output, for instance in unit-test asserts
    println!("Output to string was {}", output);
}

有没有想法如何正确编写代码?即如何在内存结构(String、Vec等)之上实现一个写入器,以便在之后可以访问它?

将 MyStructure 实现为 std::io::Write。 - Simson
@Simson 不太确定我是否理解了...那是一个建议吗?我不明白它如何有助于,你能详细说明一下吗? - Petr Kozelka
应该有一个impl for语句,您在其中声明已实现了trait,不确定是否需要。 - Simson
1个回答

9

类似这样的 确实可以运行

let mut buf = Vec::new();

{
   // Use the buffer by a mutable reference
   //
   // Also, we're doing it inside another scope
   // to help the borrow checker

   let mut x2 = MyStructure { writer: Box::new(&mut buf) };
   x2.printit().unwrap();
}

let output = std::str::from_utf8(buf.as_slice()).unwrap().to_string();
println!("Output to string was {}", output);

不过,要使这个工作起来,您需要修改类型并添加生命周期参数:

pub struct MyStructure<'a> {
    writer: Box<dyn Write + 'a>,
}

请注意,在您的情况下(即省略+ 'a部分),编译器会假设您使用'static作为trait对象的生命周期。
// Same as your original variant
pub struct MyStructure {
    writer: Box<dyn Write + 'static>
}

这限制了可在此处使用的类型集,尤其是不可使用任何借用引用种类。因此,为了最大化泛型性,我们必须在此处显式定义生命周期参数。

还要注意,根据您的用例,可以使用通用类型替代特质对象:

pub struct MyStructure<W: Write> {
    writer: W
}

在这种情况下,类型在程序的任何时间点都是完全可见的,因此不需要额外的生命周期注释。

2
如果您使用通用方法,还可以通过 x2.writer 访问 buf,如果遵循 OP 在 x2 的创建期间将 buf 的所有权移动到 x2 的方法。 Playground - EvilTak
@Vladimir 谢谢,这就是诀窍!而且,在我的情况下,使用通用方法并避免 dyn 可能确实更好。我感谢您详细的解释。 - Petr Kozelka
通常情况下,使用带有约束的泛型参数比使用特质对象更好。 - Tanveer Badar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接