#[supertrait]
Expand description
Attach this attribute to a trait definition to transform it into a supertrait, able to make use of default associated types and const fn trait items (the latter with some limitations).
The following example demonstrates some of the major features and edge cases of supertraits:
#[supertrait]
pub trait Fizz<T: Copy>: Copy + Sized {
type Foo = Option<T>;
type Bar;
const fn double_value(val: T) -> (T, T) {
(val, val)
}
const fn triple_value(val: T) -> (T, T, T);
fn double_self_plus(&self, plus: Self::Foo) -> (Self, Self, Self::Foo) {
(*self, *self, plus)
}
const fn interleave<I>(&self, a: T, b: I) -> (I, Self::Foo, T);
}
§Default Associated Types
Supertrait supports default associated types in a supertrait definition. These associated
types can also be used in other trait items via Self::SomeType
and this will work
properly anywhere in the trait or trait impl.
Implementers are free to override default types specified on the trait by simply impling that trait item.
In practice, default associated types in supertrait behave exactly the way you would expect them to behave when they finally reach stable Rust.
Generics are fully supported by this feature, and any types you mention in your default
associated type will automatically be in scope when you
#[impl_supertrait]
the trait.
§Const Fn Trait Items
Supertrait also supports const fn trait items. These items are masked by auto-generated non-const versions of the const fns that are added to enforce trait bounds.
Currently there is no way in stable Rust to add a const fn to a trait. To accomplish something like this, supertrait does two things:
- First, in the expansion of
#[impl_supertrait]
, the const fns are automatically implemented as inherents on the implementing type. This creates major limitations and makes it impossible to do things like blanket impls if they involve const fns. - Second, non-const copies that mask each const fn trait item are created and injected into the resulting supertrait. This allow us to ensure all trait bounds are respected by implementers.
Thus all bounds and trait requirements are enforced on the implementing type, however const
fns in particular are implemented as inherents, i.e. impl MyStruct { const fn something() {...} }
.
This technique has a few limitations. Because of naming collisions on inherent impls, you
can’t impl the same (const-fn-containing) supertrait on the same type multiple times with
different generics, like you can with famous conventional traits like From<T>
.
For more information, see the docs for #[impl_supertrait]
.
§Expansion
Supertrait relies heavily on the token teleportation capabilities provided by macro_magic. As a result, special care has to be taken to ensure any in-scope types mentioned in the teleported areas of a supertrait are accessible anywhere the supertrait is implemented.
To accomplish this, supertrait uses the “module wormhole” technique, whereby the actual
trait item (i.e. MyTrait
) is represented as a module containing use super::*;
, which in turn “teleports” all local
imports from the trait definition site to any context in which the trait is implemented.
Under the hood, the actual generated trait lives in MyTrait::Trait
, along with a trait
and struct pair called DefaultTypes
and Defaults
(the latter, containing an impl
specifying all of the default types with their proper generics). That said, inside impls
for a supertrait called MyTrait
, you can refer to the trait like MyTrait
instead of
MyTrait::Trait
as well as referring to the associated types like Self::Whatever
directly.
The following is the intermediate expansion for the #[supertrait]
trait definition shown
above, decorated with comments explaining what the various parts do:
#[allow(non_snake_case)]
pub mod Fizz {
// "wormhole technique", this allows us to capture the trait definition import scope
// and re-use it at any trait impl sites.
use super::*;
/// Contains default associated types for this SuperTrait
pub struct Defaults;
/// A subset of the original [`Trait`] containing just the default associated types
/// _without_ their defaults. This is automatically implemented on [`Defaults`],
/// which contains the actual default type values.
pub trait DefaultTypes<T: Copy, __Self> {
type __Self;
type Foo;
}
// note that the `__Self` keyword is used internally in place of `Self`, which is
// replaced back with `Self` at the trait impl site
impl<T: Copy, __Self> DefaultTypes<T, __Self> for Defaults {
// default associated type values are stored here
type Foo = Option<T>;
type __Self = ();
}
// This trait is auto-generated and added as a bound on `Trait` to ensure that
// supertraits can only be implemented via the `#[impl_supertrait]` macro.
#[doc(hidden)]
pub trait SupertraitSealed2484139876 {}
// This is the actual internal trait that gets generated, including the
// auto-generated sealing bound
pub trait Trait<T: Copy>: Copy + Sized + SupertraitSealed2484139876 {
type Bar;
fn double_self_plus(&self, plus: Self::Foo) -> (Self, Self, Self::Foo) {
(*self, *self, plus)
}
type Foo;
fn double_value(val: T) -> (T, T) {
(val, val)
}
fn triple_value(val: T) -> (T, T, T);
fn interleave<I>(&self, a: T, b: I) -> (I, Self::Foo, T);
}
// The tokens of this module are all exported via `macro_magic` so that they can be
// accessed directly by `#[impl_supertrait]`. This contains all the information
// needed to complete the trait impl expansion.
#[::supertrait::__private::macro_magic::export_tokens_no_emit(Fizz_exported_tokens)]
mod exported_tokens {
// tokens for const fns are stored here
trait ConstFns {
const fn double_value(val: T) -> (T, T) {
(val, val)
}
const fn triple_value(val: T) -> (T, T, T);
const fn interleave<I>(&self, a: T, b: I) -> (I, Self::Foo, T);
}
// tokens various versions of the trait generics are stored in these fns
fn trait_impl_generics<T: Copy>() {}
fn trait_use_generics<T>() {}
fn default_impl_generics<T: Copy, __Self>() {}
fn default_use_generics<T, __Self>() {}
// tokens for default associated type values are stored here
mod default_items {
type Foo = Option<T>;
}
// This const is included solely so `#[impl_supertrait]` can access its `Ident`
// to unseal and successfully implement the underlying supertrait on some type.
const SupertraitSealed2484139876: () = ();
}
}
Note that in the macro expansion code, these items are only stored as a token tree within a
macro_rules
macro. Thus the syntax here does not need to compile, it just needs to parse
correctly as a module of items from the perspective of syn
.
§Debug Mode
If you enable the debug
feature, you can add debug
as an ident argument to this
attribute macro and its expansion will be pretty-printed to the terminal at build time.
This is extremely useful for debugging supertrait
internals and for providing detailed
information when reporting bugs.