派生宏

实现PartialEq对于Ticket来说有点繁琐,对吧?你不得不手动比较结构体中的每一个字段。

解构语法

而且,这种实现方式很脆弱:如果结构体的定义发生变化(比如添加了一个新字段),你还得记得更新PartialEq的实现。

为了降低风险,你可以使用解构来将结构体分解为各个字段:

#![allow(unused)]
fn main() {
impl PartialEq for Ticket {
    fn eq(&self, other: &Self) -> bool {
        let Ticket {
            title,
            description,
            status,
        } = self;
        // [...]
    }
}
}

如果Ticket的定义发生了变化,编译器将会报错,提示你的解构不再全面。你也可以重命名结构体字段,以避免变量遮蔽:

#![allow(unused)]
fn main() {
impl PartialEq for Ticket {
    fn eq(&self, other: &Self) -> bool {
        let Ticket {
            title,
            description,
            status,
        } = self;
        let Ticket {
            title: other_title,
            description: other_description,
            status: other_status,
        } = other;
        // [...]
    }
}
}

解构是一个有用的编程模式,但还有一种更便捷的方式:派生宏(derive macros)。

在之前的练习中,你已经遇到过一些宏:

  • 测试用例中的assert_eq!assert!
  • 向控制台打印的println!

Rust的宏是代码生成器。它们根据你提供的输入生成新的Rust代码,这段生成的代码随后会与程序的其他部分一起被编译。有些宏是内置在Rust标准库中的,但你也可以编写自己的宏。虽然本课程不涉及创建宏,但你可以在“进一步阅读”部分找到一些有用的信息。

检视宏

一些集成开发环境(IDE)允许你展开宏以检查生成的代码。如果IDE不支持此功能,你可以使用cargo-expand工具。

派生宏

派生宏是Rust宏的一种特殊形式。它作为属性放在结构体定义的顶部。

#![allow(unused)]
fn main() {
#[derive(PartialEq)]
struct Ticket {
    title: String,
    description: String,
    status: String,
}
}

派生宏用于自动为自定义类型实现一些常见(且显而易见)的特质。在上面的例子中,PartialEq特质自动为Ticket实现。如果你展开这个宏,会看到生成的代码在功能上等同于你手动编写的代码,尽管读起来可能稍显复杂:

#![allow(unused)]
fn main() {
#[automatically_derived]
impl ::core::cmp::PartialEq for Ticket {
    #[inline]
    fn eq(&self, other: &Ticket) -> bool {
        self.title == other.title && self.description == other.description
            && self.status == other.status
    }
}
}

编译器会在可能的情况下提示你使用派生特质。

参考资料

  • 本节的练习位于exercises/04_traits/04_derive目录下。