// 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 協議》,轉載必須註明作者和本文連結