【问题标题】:Formatting a byte slice in Rust在 Rust 中格式化字节切片
【发布时间】:2020-05-10 05:12:19
【问题描述】:

使用 Rust,我想从 vec 中获取一个字节片段并将它们显示为十六进制,在控制台上,我可以使用 itertools 格式函数和 println! 来完成这项工作,但我无法弄清楚它是如何工作的,这里是代码,简化...

use itertools::Itertools;

// Create a vec of bytes
let mut buf = vec![0; 1024];

... fill the vec with some data, doesn't matter how, I'm reading from a socket ...

// Create a slice into the vec
let bytes = &buf[..5];

// Print the slice, using format from itertools, example output could be: 30 27 02 01 00
println!("{:02x}", bytes.iter().format(" "));

(顺便说一句,我意识到我可以使用更简单的 itertools 连接函数,但在这种情况下,我不想要默认的 0x## 样式格式,因为它有点笨重)

这到底是如何在幕后工作的?我知道 itertools 格式正在创建一个“格式”结构,我可以在这里看到源代码 https://github.com/rust-itertools/itertools/blob/master/src/format.rs ,但我并不聪明。我怀疑答案与“macro_rules!impl_format”有关,但这正是我头脑爆炸的地方。

Rust 专家能解释一下它的魔力吗?我讨厌在没有线索的情况下盲目复制粘贴代码,我是否在滥用 itertools,也许有更好、更简单的方法来解决这个问题。

【问题讨论】:

    标签: rust iterator formatting


    【解决方案1】:

    我怀疑答案与“macro_rules!impl_format”有关,但这正是我头疼的地方。

    impl_format! 宏用于实现各种格式化特征。

    impl_format!{Display Debug
                 UpperExp LowerExp UpperHex LowerHex Octal Binary Pointer}
    

    作者选择编写宏是因为实现看起来都一样。重复在宏中的工作方式意味着宏即使只使用一次也会非常有用(在这里,我们可以通过为每个特征调用一次宏来做到这一点,但通常情况并非如此)。

    我们把LowerHex的实现扩展为Format看看:

    impl<'a, I> fmt::LowerHex for Format<'a, I>
        where I: Iterator,
              I::Item: fmt::LowerHex,
    {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            self.format(f, fmt::LowerHex::fmt)
        }
    }
    

    fmt 方法调用在同一模块中定义的另一个方法 format

    impl<'a, I> Format<'a, I>
        where I: Iterator,
    {
        fn format<F>(&self, f: &mut fmt::Formatter, mut cb: F) -> fmt::Result
            where F: FnMut(&I::Item, &mut fmt::Formatter) -> fmt::Result,
        {
            let mut iter = match self.inner.borrow_mut().take() {
                Some(t) => t,
                None => panic!("Format: was already formatted once"),
            };
    
            if let Some(fst) = iter.next() {
                cb(&fst, f)?;
                for elt in iter {
                    if self.sep.len() > 0 {
                        f.write_str(self.sep)?;
                    }
                    cb(&elt, f)?;
                }
            }
            Ok(())
        }
    }
    

    format 有两个参数:格式化程序(f)和格式化函数(cb 用于回调)。这里的格式化函数是fmt::LowerHex::fmt。这是来自 LowerHex 特征的 fmt 方法;编译器如何确定使用哪个LowerHex 实现?它是从format 的类型签名推断出来的。 cb的类型为FF必须实现FnMut(&amp;I::Item, &amp;mut fmt::Formatter) -&gt; fmt::Result。注意第一个参数的类型:&amp;I::ItemI 是传递给format 的迭代器的类型)。 LowerHex::fmt'签名是:

    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
    

    对于实现LowerHex的任何类型Self,此函数将实现FnMut(&amp;Self, &amp;mut fmt::Formatter) -&gt; fmt::Result。因此,编译器推断出Self == I::Item

    这里需要注意的重要一点是格式化属性(例如,格式化字符串中的02)存储在Formatter 中。例如的实现LowerHex 将使用诸如Formatter::width 之类的方法来检索属性。这里的技巧是使用相同的格式化程序来格式化多个值(具有相同的属性)。

    在 Rust 中,可以通过两种方式调用方法:使用方法语法和使用函数语法。这两个函数是等价的:

    use std::fmt;
    
    pub fn method_syntax(f: &mut fmt::Formatter) -> fmt::Result {
        use fmt::LowerHex;
    
        let x = 42u8;
        x.fmt(f)
    }
    
    pub fn function_syntax(f: &mut fmt::Formatter) -> fmt::Result {
        let x = 42u8;
        fmt::LowerHex::fmt(&x, f)
    }
    

    formatfmt::LowerHex::fmt 一起调用时,这意味着cb 引用fmt::LowerHex::fmtformat 必须使用函数调用,因为无法保证回调甚至是方法!


    我是不是在滥用 itertools

    一点也不;事实上,这正是 format 的用途。

    也许有更好、更简单的方法来解决这个问题

    当然有更简单的方法,但是使用format 非常有效,因为它不分配动态内存。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-30
      • 1970-01-01
      • 2013-09-07
      相关资源
      最近更新 更多