【问题标题】:Struggling to wrap a fmt::Formatter in io::Write努力在 io::Write 中包装 fmt::Formatter
【发布时间】:2020-05-12 01:34:13
【问题描述】:

我有一个函数可以将我的 DocObj 呈现为 fmt::Write:

impl DocObj {
    fn render(&self, write: &mut dyn Write) -> io::Result<()>;
}

为了实现fmt::Display,我想将相同的字节写入fmt::Formatter,而不需要复制和粘贴代码。

我是 rust 的新手,我尝试了各种解决方案,但到目前为止没有任何效果。

这是我尝试过的:

策略 1:为 std::Formatter 实现 Write 特征

失败是因为 rust 不允许我在另一个 crate 中实现一个类型的 trait。好的。

策略 2:为实现 Writestd::Formatter 创建一个小包装器

失败是因为我还不够了解生命周期或生锈。

struct DisplayWriter<'a> {
    formatter: &'a fmt::Formatter<'a>
}

impl<'a> DisplayWriter<'a> {
    fn from_formatter(aformatter: &'a mut fmt::Formatter<'a>) -> DisplayWriter<'a> {
        DisplayWriter {formatter: aformatter}
    }
}

impl<'a> io::Write for DisplayWriter<'a> {
    fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, std::io::Error> {
        use std::fmt::Write;
        for c in bytes.iter() {
            match self.formatter.write_char(*c as char) {
                Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err))
            }
        }
        Ok(bytes.len())
    }
    fn flush(&mut self) -> std::result::Result<(), std::io::Error> { todo!() }
}

impl fmt::Display for DocObj {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let write = DisplayWriter::from_formatter(f);
        match self.render(&mut write) {
            Ok(_) => Ok(()),
            Err(err) => Err(fmt::Error)
        }
    }
}

使用此代码,编译器会报错:

error[E0623]: lifetime mismatch
   --> src\cos.rs:286:51
    |
285 |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    |                      -------------------
    |                      |
    |                      these two types are declared with different lifetimes...
286 |         let write = DisplayWriter::from_formatter(f);
    |                                                   ^ ...but data from `f` flows into `f` here

关于我的上下文的一些说明:

  1. 我要显示的字节是不是 UTF-8 编码的。它们是二进制的。我想为每个字节显示一个字符。因此,任何进行 UTF 编码或解码的解决方案都不适用于我的上下文。
  2. 代码处于高性能循环中,因此我想避免分配内存。

感谢您的帮助!

【问题讨论】:

    标签: rust lifetime


    【解决方案1】:

    std::Formatteralready implements Write,所以你可以直接调用你的render方法来实现显示:

    impl Display for DocObj {
        fn fmt (&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            self.render (f).or (Err (fmt::Err))
        }
    }
    

    【讨论】:

    • std::Formatter 实现 std::fmt::Write,而不是 std::io::Write
    • @harmic 根据问题的第一行,DocObj 确实实现了fmt::Write
    • 我认为这是一个错字,按照 OP 发布的代码,他们尝试在 std::io::Write 和 std::fmt::Write 之间实现适配器(有效)
    • @harmic 和函数返回io::Result
    【解决方案2】:

    如果要将对格式化程序的引用包装在结构中,则需要为引用和泛型参数使用不同的生命周期:

    struct DisplayWriter<'a, 'b> {
        formatter: &'a mut fmt::Formatter<'b>
    }
    

    完整代码,包括对拥有两个生命周期后可能遇到的其他错误的修复:

    use std::{ fmt, io };
    
    struct DocObj {}
    
    impl DocObj {
        fn render(&self, write: &mut dyn io::Write) -> io::Result<()> { Ok (()) }
    }
    
    struct DisplayWriter<'a, 'b> {
        formatter: &'a mut fmt::Formatter<'b>
    }
    
    impl<'a, 'b> DisplayWriter<'a, 'b> {
        fn from_formatter(aformatter: &'a mut fmt::Formatter<'b>) -> Self {
            DisplayWriter {formatter: aformatter}
        }
    }
    
    impl<'a, 'b> io::Write for DisplayWriter<'a, 'b> {
        fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, std::io::Error> {
            use std::fmt::Write;
            for c in bytes.iter() {
                if let Err (err) = self.formatter.write_char(*c as char) {
                    return Err(io::Error::new(io::ErrorKind::Other, err));
                }
            }
            Ok(bytes.len())
        }
        fn flush(&mut self) -> std::result::Result<(), std::io::Error> { todo!() }
    }
    
    impl fmt::Display for DocObj {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            let mut write = DisplayWriter::from_formatter(f);
            match self.render(&mut write) {
                Ok(_) => Ok(()),
                Err(_) => Err(fmt::Error)
            }
        }
    }
    

    Playground

    【讨论】:

      【解决方案3】:

      在外来类型上实现外来特征的一种方法是使用NewType 模式。此模式涉及定义一个仅包含一个元素(在本例中为外部类型)的“元组结构”。

      它与您尝试实现的包装器基本相同(@Jmb 为您完成),样板更少:

      struct DisplayWriter<'a, 'b>(&'a mut fmt::Formatter<'b>);
      
      impl<'a, 'b> io::Write for DisplayWriter<'a, 'b> {
          fn write(&mut self, bytes: &[u8]) -> std::result::Result<usize, std::io::Error> {
      
              bytes.iter()
                  .try_for_each(|c| self.0.write_char(*c as char) )
                  .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
      
              Ok(bytes.len())
          }
          fn flush(&mut self) -> std::result::Result<(), std::io::Error> { todo!() }
      }
      

      我还简化了 write 实现以使用函数式样式。

      那么你可以这样使用它:

      impl fmt::Display for DocObj {
          fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
              match self.render(&mut DisplayWriter(f)) {
                  Ok(_) => Ok(()),
                  Err(_) => Err(fmt::Error)
              }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2023-02-15
        • 1970-01-01
        • 2018-07-17
        • 1970-01-01
        • 1970-01-01
        • 2013-10-31
        • 1970-01-01
        • 2021-06-15
        • 2018-07-29
        相关资源
        最近更新 更多