Rust 编译错误 E0802 深度解析:`derive(CoercePointee)` 派生宏的合法目标类型约束 Rust 编译错误 E0802 深度解析derive(CoercePointee)派生宏的合法目标类型约束【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust本文基于 rustc 编译器错误码文档 E0802.md 展开系统讲解当#[derive(CoercePointee)]应用于不满足约束的目标类型时编译器报告 E0802 错误的全部触发场景、底层判定逻辑与正确的修复写法。读完本文你将掌握CoercePointee派生宏对目标类型的完整要求必须是带#[repr(transparent)]布局、至少含一个数据字段、至少拥有一个泛型类型参数的结构体并能精准规避六类典型误用。背景CoercePointee派生宏是什么CoercePointee是标准库中定义在内置宏集里的派生宏声明位于 library/core/src/marker.rs是一个rustc_builtin_macro允许附带#[pointee]属性#[rustc_builtin_macro(CoercePointee, attributes(pointee))] #[allow_internal_unstable(dispatch_from_dyn, coerce_unsized, unsize, coerce_pointee_validated)] #[rustc_diagnostic_item CoercePointee] #[unstable(feature derive_coerce_pointee, issue 123430)] pub macro CoercePointee($item:item) { /* compiler built-in */ }它的用途是让用户自定义的智能指针类型如自定义Rc、MySmartPointer能够参与“非尺寸化强制转换”unsizing coercion即让MySmartPointerT被自动强转为MySmartPointerdyn Trait从而支持 trait object 动态分派。其派生实现会为类型自动生成core::ops::DispatchFromDyn与core::ops::CoerceUnsized的impl块见 compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs同时还生成一个core::marker::CoercePointeeValidated的实现用于后续在rustc_hir_analysis中校验派生合法性library/core/src/marker.rs。由于派生宏对目标类型的要求比手写impl更严格编译器会在宏展开阶段与类型检查阶段分别进行多项检查任何一项不满足都会报出 E0802 错误。E0802 错误总览目标类型“规格不合格”E0802 的诊断信息为The target of derive(CoercePointee) macro has inadmissible specification for a meaningful use.derive(CoercePointee)宏的目标类型规格不符合有意义的使用要求。它由编译器的多个检查点共同抛出对应的诊断结构体定义在 compiler/rustc_hir_analysis/src/diagnostics.rs 和 compiler/rustc_builtin_macros/src/deriving/coerce_pointee.rs 中包括检查点错误消息触发条件RequireTransparentCoercePointeecan only be derived onstructs with#[repr(transparent)]目标不是结构体或结构体缺少#[repr(transparent)]RequireOneFieldCoercePointeecan only be derived onstructs with at least one field结构体没有任何数据字段RequireOneGenericCoercePointeecan only be derived onstructs that are generic over at least one type结构体没有任何泛型类型参数RequireOnePointeeexactly one generic type parameter must be marked as#[pointee]有多个泛型类型参数但没有任何一个标记#[pointee]TooManyPointeesonly one type parameter can be marked as#[pointee]when derivingCoercePointeetraits有多个泛型类型参数被标记为#[pointee]RequiresMaybeSizedderive(CoercePointee)requires{$name}to be marked?Sized被选为 pointee 的泛型参数未标记?Sized以下逐一结合官方错误码文档中的compile_fail示例说明每个场景。场景一目标类型不是结构体CoercePointee只能派生在结构体上。将派生宏应用于枚举enum会直接触发 E0802#![feature(coerce_pointee)] use std::marker::CoercePointee; #[derive(CoercePointee)] enum NotStructa, T: ?Sized { Variant(a T), }从实现上看coerce_pointee.rs 在宏展开时对item做模式匹配只有ItemKind::Struct分支会被接受命中其他ItemKind枚举、联合体、trait 等时直接调用RequireTransparent报告 E0802 并中止展开。类型检查阶段还有一道兜底检查对应CoercePointeeNotStruct诊断rustc_hir_analysis/src/diagnostics.rs消息为derive(CoercePointee) is only applicable to struct, instead of {$kind}。场景二目标结构体缺少#[repr(transparent)]透明布局结构体的内存布局必须是透明的即单一非零尺寸字段承载实际数据。缺少该属性会报错#![feature(coerce_pointee)] use std::marker::CoercePointee; #[derive(CoercePointee)] struct NotTransparenta, #[pointee] T: ?Sized { ptr: a T, }原因在于只有repr(transparent)才能保证“智能指针结构体的布局与内层指针字段完全一致”这是CoerceUnsized/DispatchFromDyn能够安全地把T换成dyn Trait而不改变结构体 ABI 的前提。宏展开阶段会直接检查struct_data形态coerce_pointee.rs类型检查阶段的CoercePointeeNotTransparentdiagnostics.rs也会再次确认透明布局这一事实。测试用例 tests/ui/derives/coercepointee/deriving-coerce-pointee-neg.rs 中即覆盖了这一反例。场景三结构体没有任何数据字段即使加上了#[repr(transparent)]一个空壳结构体单元结构体或没有字段的命名/元组结构体也无法派生#![feature(coerce_pointee)] use std::marker::CoercePointee; #[derive(CoercePointee)] #[repr(transparent)] struct NoFielda, #[pointee] T: ?Sized {}对应的展开期检查是只接受VariantData::Struct字段非空或VariantData::Tuple字段非空两种形态fields.is_empty()时报告RequireOneFieldcoerce_pointee.rs。测试 deriving-coerce-pointee-neg.rs 同时验证了struct NoField {}与元组形态struct NoFieldUnit();两种空字段反例。场景四结构体没有任何泛型类型参数CoercePointee的意义在于对“被指向的类型参数”做动态分派因此结构体必须至少拥有一个泛型类型参数#![feature(coerce_pointee)] use std::marker::CoercePointee; #[derive(CoercePointee)] #[repr(transparent)] struct NoGenerica(a u8);上面的结构体只有生命周期参数a没有泛型类型参数。展开期代码通过统计GenericParamKind::Type的数量来判定type_params.is_empty()时调用RequireOneGenericcoerce_pointee.rs。场景五有多个泛型类型参数但未指定哪个是 pointee当结构体拥有多个泛型类型参数时必须显式用#[pointee]标注出用于强制转换的那一个#![feature(coerce_pointee)] use std::marker::CoercePointee; #[derive(CoercePointee)] #[repr(transparent)] struct AmbiguousPointeea, T1: ?Sized, T2: ?Sized { a: (a T1, a T2), }编译器无法猜测T1与T2哪个才是“被指向的类型”因此报 E0802RequireOnePointee。注意区分两个分支逻辑coerce_pointee.rs仅有一个泛型类型参数时无论是否标记#[pointee]都直接以它作为 pointee标记是可选的、不强制有多个泛型类型参数时必须恰好有一个被#[pointee]标记零个或多个都会报错。场景六多个泛型类型参数同时被标记为#[pointee]与场景五相反如果多个泛型类型参数都被打上了#[pointee]同样触发 E0802#![feature(coerce_pointee)] use std::marker::CoercePointee; #[derive(CoercePointee)] #[repr(transparent)] struct TooManyPointees a, #[pointee] A: ?Sized, #[pointee] B: ?Sized ((a A, a B));实现中通过迭代器取前两个 pointee 候选恰好一个则采用零个报RequireOnePointee两个及以上报TooManyPointeescoerce_pointee.rs。TooManyPointees还会用#[label]标出“第二个被标记的#[pointee]”位置coerce_pointee.rs帮助定位多余标注。该反例同样出现在 deriving-coerce-pointee-neg.rs。场景七被标记的 pointee 泛型参数未声明?Sized最后一项要求被选为 pointee 的泛型类型参数无论是唯一泛型参数还是显式#[pointee]标记的参数必须用?Sized放宽尺寸约束因为 trait object 本身是动态尺寸类型#![feature(coerce_pointee)] use std::marker::CoercePointee; #[derive(CoercePointee)] #[repr(transparent)] struct NoMaybeSizeda, #[pointee] T { ptr: a T, }展开期会检查 pointee 参数的内联边界或where子句中是否存在?Sizedcontains_maybe_sized_bound与contains_maybe_sized_bound_on_pointee两个辅助函数coerce_pointee.rs缺失时报告RequiresMaybeSizedcoerce_pointee.rs。需要说明的是?Sized约束既可以写在泛型参数声明处#[pointee] T: ?Sized也可以写在where T: ?Sized子句中两者均被认可。合法用法满足全部约束的正确写法综合 E0802 文档末尾的总结E0802.mdCoercePointee派生宏对目标类型的要求可归纳为五条必须是结构体struct不能是枚举等其余 ADT必须采用#[repr(transparent)]透明布局必须至少含有一个数据字段必须至少有一个泛型类型参数若不止一个则须用#[pointee]恰好标记其中一个作为 pointee 的那个泛型参数必须标记为?Sized。一个完全合法的最小示例来自 library/core/src/marker.rs 的文档示例#![feature(derive_coerce_pointee)] use std::marker::CoercePointee; use std::ops::Deref; #[derive(CoercePointee)] #[repr(transparent)] struct MySmartPointerT: ?Sized(BoxT); implT: ?Sized Deref for MySmartPointerT { type Target T; fn deref(self) - T { self.0 } } trait MyTrait {} impl MyTrait for i32 {} fn main() { let ptr: MySmartPointeri32 MySmartPointer(Box::new(4)); // 没有 derive(CoercePointee) 时这一行会报 E0308 类型不匹配 let ptr: MySmartPointerdyn MyTrait ptr; }多泛型参数时指定 pointee 的写法library/core/src/marker.rs#![feature(derive_coerce_pointee)] use std::marker::{CoercePointee, PhantomData}; #[derive(CoercePointee)] #[repr(transparent)] struct MySmartPointer#[pointee] T: ?Sized, U { ptr: BoxT, _phantom: PhantomDataU, }注意零尺寸字段若引用了泛型参数必须使用PhantomData类型这也是宏的硬性要求之一library/core/src/marker.rs。底层原理宏展开时做了什么理解 E0802 的触发位置有助于定位问题。整个#[derive(CoercePointee)]的处理入口是expand_deriving_coerce_pointeecoerce_pointee.rs流程如下前置遍历DetectNonGenericPointeeAttr访问器扫描整个目标项凡是在非泛型类型参数位置如 const 泛型、关联类型、字段类型内部出现#[pointee]属性一律报NonGenericPointee错误coerce_pointee.rs——这是 E0802 家族里比较隐蔽的一类误用形态检查确认是struct且字段非空否则报RequireTransparent/RequireOneField泛型统计确认至少一个泛型类型参数并确定 pointee 参数下标否则报RequireOneGeneric/RequireOnePointee/TooManyPointees?Sized检查确认 pointee 参数带?Sized否则报RequiresMaybeSized生成代码把 pointee 类型参数在self类型中替换为__S表示未知的 unsized 目标为#[pointee]参数补上Unsize__S边界重写其余泛型参数的边界与where子句将涉及 pointee 的边界复制一份替换为__S版本最后插入__S泛型参数并为DispatchFromDyn、CoerceUnsized各生成一个impl块。展开完成后CoercePointeeValidated的实现会在rustc_hir_analysis的类型检查阶段被再次校验对应CoercePointeeNotStruct、CoercePointeeNotConcreteType、CoercePointeeNoUserValidityAssertion、CoercePointeeNotTransparent、CoercePointeeNoField等诊断diagnostics.rs防止用户绕开派生宏、在where子句或字段类型中做手脚。相关测试与验证编译器仓库在 tests/ui/derives/coercepointee/ 目录下提供了完整测试套件deriving-coerce-pointee-neg.rs本文所述各反例的集中回归测试每条反例均通过//~^ ERROR:注释断言对应的 E0802 错误消息其配套 deriving-coerce-pointee-neg.stderr 记录了精确的输出deriving-coerce-pointee.rs 与 deriving-coerce-pointee-expanded.rs验证合法结构体的派生成功与宏展开结果coerce-pointee-bounds-issue-127647.rs针对具体 issue 的边界场景测试。如果你在本地用 nightly 工具链复现注意 E0802 文档示例中使用的特性门是#![feature(coerce_pointee)]而当前仓库 marker.rs 中登记的 unstable 特性为derive_coerce_pointeeissue #123430不同版本可能有所差异请以实际使用的工具链支持的特性名为准。小结E0802 是derive(CoercePointee)派生宏的“规格校验器”它把自定义智能指针参与 trait object 强制转换的合法性前提固化为一组可在编译期静态检查的形态约束struct 透明布局 至少一个数据字段 至少一个泛型类型参数 恰好一个#[pointee]标记 ?Sized。掌握这六类反例及其判定逻辑你就能在编写自定义智能指针如仿Rc、仿Arc的薄封装时一次性通过编译并理解宏展开、DispatchFromDyn/CoerceUnsized生成与CoercePointeeValidated二次校验之间的完整链路。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考