9 个版本 (稳定)

1.3.2 2022年11月24日
1.3.1 2022年11月11日
1.1.0 2022年6月3日
1.0.0 2022年5月19日
0.1.0 2022年3月20日

#193 in 命令行界面

Download history 296/week @ 2024-03-13 255/week @ 2024-03-20 207/week @ 2024-03-27 282/week @ 2024-04-03 372/week @ 2024-04-10 434/week @ 2024-04-17 235/week @ 2024-04-24 183/week @ 2024-05-01 193/week @ 2024-05-08 337/week @ 2024-05-15 334/week @ 2024-05-22 383/week @ 2024-05-29 460/week @ 2024-06-05 205/week @ 2024-06-12 244/week @ 2024-06-19 234/week @ 2024-06-26

每月1,218次下载
2 crates 中使用

MIT 许可证

45KB
877

Cubob

一些 Rust 结构化输出的辅助工具。

名称

该项目具有自动生成的名称,归功于 This Word Does Not Exist 项目。单词定义可以在此 检查。

目的

Rust 核心库提供了作为 core::fmt::Formatter 方法的某些不错的输出原语:如 debug_listdebug_struct 等。此外,它还提供了一个用于 core::fmt::Debug 的惊人的 derive 宏,它完美地执行了实现所述原语的常规操作。不幸的是,没有显示模式的原语对应物,也没有 core::fmt::Display 的 derive 宏。最后一点在文档中直接说明了:这样做是为了鼓励开发者根据相关类型的目的实现 core::fmt::Display。我同意这种做法,但我不相信这种解释足以避免实现任何合适的原语,而现有的原语又过于侧重于调试(例如,它们总是打印类型名称,或者总是打印 None 值,即使在实际显示模式下并不需要)。这个库/包/仓库是对这种差距的小小尝试:它为讨论的情况提供了一些小的原语(见示例)。

示例

让我们考虑以下结构

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

通过使用 derive 宏实现标准 std::fmt::Debug 接口,对于 {:?} 格式,它将产生以下输出

Point { x: 0, y: 0 }

使用美化格式({:#?})时的输出如下

Point { 
    x: 0, 
    y: 0,
}

继续例子,可以用相关的输出描述下一个结构(相应地)

#[derive(Debug)]
struct Line {
    a: Point,
    b: Point,
}
Line { a: Point { x: 0, y: 0 }, b: Point { x: 1, y: 1 } }
Line { 
    a: Point { 
        x: 0, 
        y: 0, 
    }, 
    b: Point { 
        x: 1, 
        y: 1 ,
    } 
}

如果将其实现为显示模式,将会产生相同的输出(实际上,这是 derive 宏内部实现调试模式的方式)

impl Display for Point {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.debug_struct("Point")
            .field("x", &self.x)
            .field("y", &self.y)
            .finish()
    }
}

impl Display for Line {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.debug_struct("Line")
            .field("a", &self.a)
            .field("b", &self.b)
            .finish()
    }
}

显示模式下的输出可能过于详细(我个人觉得类型信息有点烦人)。可以通过使用一些 std::fmt::Formatter 方法的小技巧来做得更好

impl Display for Point {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.debug_map()
            .entry(&"x", &self.x)
            .entry(&"y", &self.y)
            .finish()
    }
}

impl Display for Line {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.debug_map()
            .entry(&"a", &self.a)
            .entry(&"b", &self.b)
            .finish()
    }
}

以下是结果(美化以简化阅读)

{
    "a": Point {
        x: 0,
        y: 0,
    },
    "b": Point {
        x: 1,
        y: 1,
    },
}

Line 的类型名称已被移除,但 Point 保留了,因为由于调用了 debug_mapdebug_ !),它使用调试模式输出所有条目,并且提供的 std::fmt::Display 实现甚至没有被使用。此外,这种方法将字段名称周围放置了 " 符号,因为它们被视为映射键,这是合理的,但在考虑的这种情况下似乎有些冗余。接下来的尝试将处理这两种效果

use std::format_args;

impl Display for Point {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.debug_set()
            .entry(&format_args!("x: {:#}", self.x))
            .entry(&format_args!("y: {:#}", self.y))
            .finish()
    }
}

impl Display for Line {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        f.debug_set()
            .entry(&format_args!("a: {:#}", self.a))
            .entry(&format_args!("b: {:#}", self.b))
            .finish()
    }
}

这种方法达到了美化格式的目标

{
    a: {
        x: 0,
        y: 0,
    },
    b: {
        x: 1,
        y: 1,
    },
}

但它在非美化格式中存在问题

{a: {
    x: 0,
    y: 0,
}, b: {
    x: 1,
    y: 1,
}}

原因在于像 "a: {:#}" 这样的格式字符串。对于 Point 来说没关系,因为它字段的值是标量,但对于 Line 来说却有问题,因为它的字段本身是结构体:它们始终被美化输出,因为相关的格式字符串指定无论什么情况——即使 Line 实例本身被输出为非美化格式。为了避免这些问题,应该在输出代码中注入一些变化

impl Display for Point {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        if f.alternate() {
            f.debug_set()
                .entry(&format_args!("x: {:#}", self.x))
                .entry(&format_args!("y: {:#}", self.y))
                .finish()
        } else {
            f.debug_set()
                .entry(&format_args!("x: {}", self.x))
                .entry(&format_args!("y: {}", self.y))
                .finish()
        }
    }
}

impl Display for Line {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        if f.alternate() {
            f.debug_set()
                .entry(&format_args!("a: {:#}", self.a))
                .entry(&format_args!("b: {:#}", self.b))
                .finish()
        } else {
            f.debug_set()
                .entry(&format_args!("a: {}", self.a))
                .entry(&format_args!("b: {}", self.b))
                .finish()
        }
    }
}

结果是

Non-prettified: {a: {x: 0, y: 0}, b: {x: 1, y: 1}}
Prettified: {
    a: {
        x: 0,
        y: 0,
    },
    b: {
        x: 1,
        y: 1,
    },
}

它工作得很好!但是代码变得相当混乱,对于小型结构体和程序来说这不是大问题,但是当程序有大量具有许多字段的复杂结构体时,这就会成为一个问题。所以这里是 Cubob 的解决方案:一些抽象,它们可以帮助以更简单的操作达到相同的目标

use cubob::display_struct;

impl Display for Point {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        display_struct(
            f,
            &[
                (&"x", &self.x),
                (&"y", &self.y),
            ],
        )
    }
}

impl Display for Line {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        display_struct(
            f,
            &[
                (&"a", &self.x),
                (&"b", &self.y),
            ],
        )
    }
}

这段代码产生了与之前相同的行为,但让代码更加简洁和清晰。

没有运行时依赖