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 命令行界面
每月1,218次下载
在 2 crates 中使用
45KB
877 行
Cubob
一些 Rust 结构化输出的辅助工具。
名称
该项目具有自动生成的名称,归功于 This Word Does Not Exist 项目。单词定义可以在此 处 检查。
目的
Rust 核心库提供了作为 core::fmt::Formatter 方法的某些不错的输出原语:如 debug_list、debug_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_map
(debug_ !),它使用调试模式输出所有条目,并且提供的 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),
],
)
}
}
这段代码产生了与之前相同的行为,但让代码更加简洁和清晰。