使用 fmt::Display 自定义 Rust 结构体输出

Beginner

This tutorial is from open-source community. Access the source code

简介

在本实验中,你将学习如何在 Rust 中实现 fmt::Display 特性,以自定义结构体的输出外观。你还将探讨 fmt::Displayfmt::Debug 之间的区别,以及 fmt::Display 对通用容器类型的局限性。最后,你将进行一项活动,为新的 Complex 结构体实现 fmt::Display 特性,并以特定格式打印它。

注意:如果实验未指定文件名,你可以使用任何你想要的文件名。例如,你可以使用 main.rs,并通过 rustc main.rs &&./main 进行编译和运行。

格式化显示

fmt::Debug 的输出看起来很难做到紧凑和简洁,因此自定义输出外观通常是很有必要的。这可以通过手动实现 fmt::Display 来完成,它使用 {} 作为打印标记。实现方式如下:

// 通过 `use` 导入 `fmt` 模块,使其可用。
use std::fmt;

// 定义一个结构体,并为其实现 `fmt::Display`。这是一个名为 `Structure` 的元组结构体,它包含一个 `i32`。
struct Structure(i32);

// 为了使用 `{}` 标记,必须为该类型手动实现 `fmt::Display` 特性。
impl fmt::Display for Structure {
    // 这个特性要求 `fmt` 具有这个确切的签名。
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 只将第一个元素严格写入提供的输出流:`f`。返回 `fmt::Result`,它表示操作是否成功。注意,`write!` 使用的语法与 `println!` 非常相似。
        write!(f, "{}", self.0)
    }
}

fmt::Display 可能比 fmt::Debug 更简洁,但这给标准库带来了一个问题。对于模糊类型应该如何显示呢?例如,如果标准库为所有的 Vec<T> 实现一种单一的样式,应该是什么样式呢?会是以下两种中的一种吗?

  • Vec<路径>/:/etc:/home/用户名:/bin(以 : 分割)
  • Vec<数字>1,2,3(以 , 分割)

不,因为对于所有类型来说没有理想的样式,并且标准库也不会擅自规定一种样式。Vec<T> 或任何其他通用容器类型都没有实现 fmt::Display。对于这些通用情况,必须使用 fmt::Debug

不过这并不是一个问题,因为对于任何新的非通用的 容器 类型,可以实现 fmt::Display

use std::fmt; // 导入 `fmt`

// 一个包含两个数字的结构体。将派生 `Debug`,以便可以将结果与 `Display` 进行对比。
#[derive(Debug)]
struct MinMax(i64, i64);

// 为 `MinMax` 实现 `Display`。
impl fmt::Display for MinMax {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 使用 `self.number` 来引用每个位置的数据点。
        write!(f, "({}, {})", self.0, self.1)
    }
}

// 定义一个结构体,其中的字段是可命名的,以便进行比较。
#[derive(Debug)]
struct Point2D {
    x: f64,
    y: f64,
}

// 同样,为 `Point2D` 实现 `Display`。
impl fmt::Display for Point2D {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 进行自定义,以便只显示 `x` 和 `y`。
        write!(f, "x: {}, y: {}", self.x, self.y)
    }
}

fn main() {
    let minmax = MinMax(0, 14);

    println!("比较结构体:");
    println!("格式化显示:{}", minmax);
    println!("调试格式:{:?}", minmax);

    let big_range =   MinMax(-300, 300);
    let small_range = MinMax(-3, 3);

    println!("大的范围是 {big},小的范围是 {small}",
             small = small_range,
             big = big_range);

    let point = Point2D { x: 3.3, y: 7.2 };

    println!("比较点:");
    println!("格式化显示:{}", point);
    println!("调试格式:{:?}", point);

    // 错误。虽然同时实现了 `Debug` 和 `Display`,但 `{:b}` 需要实现 `fmt::Binary`。这将无法工作。
    // println!("Point2D 的二进制形式是什么样的:{:b}?", point);
}

所以,已经实现了 fmt::Display,但没有实现 fmt::Binary,因此不能使用它。std::fmt 有许多这样的 特性,每个特性都需要自己的实现。std::fmt 中对此有更详细的说明。

活动

在检查上述示例的输出后,以 Point2D 结构体为指导,在示例中添加一个 Complex 结构体。当以相同方式打印时,输出应该是:

格式化显示:3.3 + 7.2i
调试格式:Complex { real: 3.3, imag: 7.2 }

总结

恭喜你!你已经完成了格式化显示实验。你可以在 LabEx 中练习更多实验来提升你的技能。