Skip to content

Rust 过程宏:完整Report工作区与输入预期 · 未来自跑与证据至资料与排错 ​

返回分篇目录

这是完整源码阅读工程,示例未执行,未执行cargo、rustc、测试、格式化或宏展开,也未安装依赖。目录在examples/rust/p2-proc-macros。代码中的断言与compile_fail都是未来验证规格,不是本批执行结果;页面通过include展示真实工程文件,避免另维护一份漂移副本。

rs
//! Compile-fail contracts: these are intended tests, not captured compiler output.
//!
//! Unsupported item: expected `Report supports structs only` at the enum name.
//! ```compile_fail
//! #[derive(renamed_report::Report)]
//! #[report(crate = "::renamed_report")]
//! enum Choice { A }
//! ```
//! Tuple fields: expected `Report requires named fields` at the fields.
//! ```compile_fail
//! #[derive(renamed_report::Report)]
//! #[report(crate = "::renamed_report")]
//! struct Tuple(u32);
//! ```
//! Duplicate configuration: expected `duplicate report rename` at the second key.
//! ```compile_fail
//! #[derive(renamed_report::Report)]
//! #[report(crate = "::renamed_report")]
//! struct Duplicate { #[report(rename = "a", rename = "b")] value: u32 }
//! ```
//! Conflicting configuration: expected `report skip conflicts with rename` at field.
//! ```compile_fail
//! #[derive(renamed_report::Report)]
//! #[report(crate = "::renamed_report")]
//! struct Conflict { #[report(skip, rename = "a")] value: u32 }
//! ```
//! Unknown configuration: expected field-supported-options message at `flatten`.
//! ```compile_fail
//! #[derive(renamed_report::Report)]
//! #[report(crate = "::renamed_report")]
//! struct Unknown { #[report(flatten)] value: u32 }
//! ```
//! Empty labels: expected `report label must not be empty` at the string literal.
//! ```compile_fail
//! #[derive(renamed_report::Report)]
//! #[report(crate = "::renamed_report")]
//! struct Empty { #[report(rename = "")] value: u32 }
//! ```
//! Duplicate labels: expected `duplicate report label` at the second label.
//! ```compile_fail
//! #[derive(renamed_report::Report)]
//! #[report(crate = "::renamed_report")]
//! struct Labels { a: u32, #[report(rename = "a")] b: u32 }
//! ```
//! Non-Display field: expected a Display trait-bound error; it is rustc's error.
//! ```compile_fail
//! #[derive(renamed_report::Report)]
//! #[report(crate = "::renamed_report")]
//! struct Unsupported { bytes: Vec<u8> }
//! ```
//! Renamed dependency without override: expected unresolved `report_runtime`.
//! ```compile_fail
//! #[derive(renamed_report::Report)]
//! struct MissingPath { value: u32 }
//! ```
//! Invalid crate syntax: expected a syn path parsing error at the literal.
//! ```compile_fail
//! #[derive(renamed_report::Report)]
//! #[report(crate = "not a path")]
//! struct InvalidPath { value: u32 }
//! ```

use renamed_report::Report;

#[derive(Report)]
#[report(crate = "::renamed_report")]
pub struct AuditRow<'a, T, const N: usize>
where
    T: Clone,
{
    pub name: &'a str,
    #[report(rename = "amount")]
    pub value: T,
    #[report(skip)]
    pub secret: [u8; N],
}

pub fn sample() -> Vec<(&'static str, String)> {
    AuditRow { name: "invoice", value: 7_u32, secret: [1, 2] }.report()
}

consumer/tests/report.rs:

rs
use renamed_report::Report;
use report_consumer::{sample, AuditRow};

#[test]
fn renamed_runtime_and_field_order() {
    assert_eq!(sample(), vec![("name", "invoice".into()), ("amount", "7".into())]);
}

#[test]
fn lifetime_const_generic_where_clause_and_borrow() {
    let name = String::from("borrowed");
    let row = AuditRow { name: &name, value: 12_i64, secret: [9; 3] };
    assert_eq!(row.report(), vec![("name", "borrowed".into()), ("amount", "12".into())]);
    assert_eq!(row.secret, [9; 3]);
    assert_eq!(row.value, 12); // report borrows; it does not move a field.
}

#[derive(Report)]
#[report(crate = "::renamed_report")]
struct Skipped<T> {
    #[report(skip)]
    marker: T,
}

#[test]
fn skipped_type_needs_no_display() {
    let row = Skipped { marker: vec![1_u8] };
    assert!(row.report().is_empty());
    assert_eq!(row.marker, vec![1]);
}

README.md:

md
# Report过程宏完整工程(未执行)

目的:为发票/审计结构派生有序字段报告;只选择显示字段,隐藏secret。
它不是序列化格式,也不是安全脱敏系统;字段Debug/Display自身可能泄露数据,调用者负责选择字段。

三crate:report-derive在编译期解析语法生成impl;report-runtime提供trait和同名derive再导出;report-consumer在运行期借用对象并调用report。
支持命名字段struct、生命周期、类型/const泛型与已有where条件;非跳过字段的完整类型需要Display。跳过字段不增加Display约束。
item helper仅支持report(crate = "::path");field helper仅支持skip或rename。重复key、冲突、空/重名标签、未知key与不支持输入报错。
默认路径::report_runtime;依赖改名必须显式report(crate = "::renamed_report"),不自动推断Cargo别名。路径语法解析不保证该trait存在。
工程不读文件/环境/网络,无build.rs,不生成可执行文件。

以下命令仅供未来读者自行执行,作者本批没有执行:

```sh
cd examples/rust/p2-proc-macros
cargo test --workspace
cargo test -p report-consumer --doc
```

首次需要联网获得固定直接依赖和传递依赖,详见DEPENDENCIES.md。正例三个测试预期均通过,sample预期为[("name", "invoice"), ("amount", "7")]的字符串值,无终端成功输出承诺。
consumer/src/lib.rs的十个compile_fail块应分别因其注释所列原因失败;doctest通过只表示每块未编译成功,不能代替诊断原因审阅。不得把注释当作实际编译输出。
普通结构体的未知字段类型不由宏语义识别,Display由生成的where条件交给编译器检查。enum、tuple/unit struct本课程明确拒绝。
恢复:根据错误位置修正输入/属性/显式crate路径,不通过删掉整个derive掩盖配置错误。依赖失败先检查版本/网络而非修改生成代码。
清理:读者在确认位于本工程后可执行cargo clean清除target;Cargo.lock应保留供复现。工程没有外部状态需要清理。

DEPENDENCIES.md:

md
# 依赖和复现边界

本工程未执行、未解析依赖、未编译;没有Cargo.lock,不能声称可离线复现。
工作区固定syn =2.0.108、quote =1.0.41、proc-macro2 =1.0.103;这是明确的教学选择,不声称是最新版本。
syn负责DeriveInput、Path、属性语法;quote负责保留token结构的插值;proc-macro2用于可脱离入口函数构造的TokenStream。
使用std,未声明no_std和MSRV支持。读者需自行选定并记录Rust/Cargo工具链,首次联网解析后审查并保存Cargo.lock(含传递依赖)。
本地report-runtime依赖report-derive,consumer把report-runtime重命名为renamed_report;derive不依赖runtime,避免循环。
不引入trybuild,负例采用rustdoc compile_fail;它只断言编译失败,不锁定具体错误文本或span,仍需人工核对诊断原因。

作者资料:
- https://docs.rs/syn/2.0.108/syn/
- https://docs.rs/quote/1.0.41/quote/
- https://docs.rs/proc-macro2/1.0.103/proc_macro2/

未来自跑与证据 ​

未来自跑与证据需区分静态阅读、编译与行为三个层次:阅读只能检查输出结构,Cargo构建可发现语法和类型问题,测试才检查预期值与负例是否失败。先记录工具链,再按README的workspace测试和文档测试步骤执行;命令出现在文档中不等于作者运行过。doc负例的“通过”还需要核对失败原因,避免路径错误掩盖想验证的属性冲突。

本批只做TOML/JSON/Markdown结构检查、include存在性和人工Rust审阅。尚存依赖解析、具体编译器诊断与完整编译可用性的不确定性,不能把静态检查说成cargo check通过。站点成功构建也不代表工程的Rust源码被编译。

实作练习 ​

按以下顺序在纸面审阅:①给AuditRow的secret去掉skip,指出会新增哪条约束;②把value和name都rename为same,指出先在哪个输入位置被拒绝;③把consumer依赖别名改为audit_api,列出必须同步改动的引用;④增加只含skip字段的泛型struct,解释为什么不该约束T: Display。若未来自己修改和运行工程,另保存真实工具链和结果,不覆盖“本批未执行”的说明。

参考解答 ​

展开逐项答案

①新增[u8; N]: Display,标准数组通常不满足,错误属于生成impl的trait检查。②第二个可见同标签字段触发duplicate report label,span关联该标签;错误发生在生成impl前。③修改Cargo别名、use/derive路径及每个显式report(crate)字符串,包括正例、负例和测试;不是只改一个import。④skip字段不产生格式化语句,所以不需要它的类型实现Display,保留原有合法泛型即可。

资料与排错 ​

构建结构参照Cargo工作区,负例机制参照rustdoc文档测试。出现错误时继续过程宏诊断。这些来源说明机制,不是本工程的执行证明。

继续阅读