proc-macro-workshop:debug-6
审题
// This test case should not require any code change in your macro if you have
// everything up to this point already passing, but is here to demonstrate why
// inferring `#field_ty: Trait` bounds as mentioned in the previous test case is
// not viable.
//
// #[derive(CustomDebug)]
// pub struct One<T> {
// value: T,
// two: Option<Box<Two<T>>>,
// }
//
// #[derive(CustomDebug)]
// struct Two<T> {
// one: Box<One<T>>,
// }
//
// The problematic expansion would come out as:
//
// impl<T> Debug for One<T>
// where
// T: Debug,
// Option<Box<Two<T>>>: Debug,
// {...}
//
// impl<T> Debug for Two<T>
// where
// Box<One<T>>: Debug,
// {...}
//
// There are two things wrong here.
//
// First, taking into account the relevant standard library impls `impl<T> Debug
// for Option<T> where T: Debug` and `impl<T> Debug for Box<T> where T: ?Sized +
// Debug`, we have the following cyclic definition:
//
// - One<T> implements Debug if there is an impl for Option<Box<Two<T>>>;
// - Option<Box<Two<T>>> implements Debug if there is an impl for Box<Two<T>>;
// - Box<Two<T>> implements Debug if there is an impl for Two<T>;
// - Two<T> implements Debug if there is an impl for Box<One<T>>;
// - Box<One<T>> implements Debug if there is an impl for One<T>; cycle!
//
// The Rust compiler detects and rejects this cycle by refusing to assume that
// an impl for any of these types exists out of nowhere. The error manifests as:
//
// error[E0275]: overflow evaluating the requirement `One<u8>: std::fmt::Debug`
// -->
// | assert_debug::<One<u8>>();
// | ^^^^^^^^^^^^^^^^^^^^^^^
//
// There is a technique known as co-inductive reasoning that may allow a
// revamped trait solver in the compiler to process cycles like this in the
// future, though there is still uncertainty about whether co-inductive
// semantics would lead to unsoundness in some situations when applied to Rust
// trait impls. There is no current activity pursuing this but some discussion
// exists in a GitHub issue called "#[derive] sometimes uses incorrect bounds":
// https://github.com/rust-lang/rust/issues/26925
//
// The second thing wrong is a private-in-public violation:
//
// error[E0446]: private type `Two<T>` in public interface
// -->
// | struct Two<T> {
// | - `Two<T>` declared as private
// ...
// | / impl<T> Debug for One<T>
// | | where
// | | T: Debug,
// | | Option<Box<Two<T>>>: Debug,
// ... |
// | | }
// | |_^ can't leak private type
//
// Public APIs in Rust are not allowed to be defined in terms of private types.
// That includes the argument types and return types of public function
// signatures, as well as trait bounds on impls of public traits for public
// types.
use derive_debug::CustomDebug;
use std::fmt::Debug;
#[derive(CustomDebug)]
pub struct One<T> {
value: T,
two: Option<Box<Two<T>>>,
}
#[derive(CustomDebug)]
struct Two<T> {
one: Box<One<T>>,
}
fn assert_debug<F: Debug>() {}
fn main() {
assert_debug::<One<u8>>();
assert_debug::<Two<u8>>();
}
这道题主要向我们展示了为何第五题最好做泛型部分限定的原因。
如题所示,其中One
中有Two
,Two
中有One
。
如果真的要全部类型限定,必然涉及到路径上的全部包装类型。
比如针对A<B<C<D<E>>>>
,相关的类型就会有ABCDE
五个,逐层的展开,不仅麻烦,如果在这道题中,可能还会无限的嵌套。
而实际上,对于A<B<C<D<E>>>>
,我们实际上只用关心A
即可。
题解
在debug-5已经解答了,这里只是深化一下。
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: