|
#[doc(hidden)] |
|
#[macro_export] |
|
macro_rules! peripheral_trait { |
|
($nom:ident, $(#[$attr:meta])* pub trait $trait:ident $(<$lifetime:lifetime>)? $(: $bound:ident )? { $($rest:tt)* }) => { |
|
$(#[$attr])* |
|
pub trait $trait $(<$lifetime>)? where Self: $($bound)? { $($rest)* } |
|
|
|
// $crate::deref_impl!($trait$(<$lifetime>)? $(| $lifetime |)?, { $($rest)* }); |
|
// $crate::borrow_impl!($trait$(<$lifetime>)? $(| $lifetime |)?, { $($rest)* }); |
|
$crate::peripheral_set_impl!($trait$(<$lifetime>)? $(| $lifetime |)?, { $crate::func_sig!($nom, $($rest)*); }); |
|
// $crate::peripheral_deref_set_impl!($trait$(<$lifetime>)? $(| $lifetime |)?, { $crate::func_sig!($nom, $($rest)*); }); |
|
}; |
|
} |
|
|
|
#[doc(hidden)] |
|
#[macro_export] |
|
macro_rules! deref_impl { |
|
($trait:path $(| $lifetime:lifetime |)?, { $($rest:tt)* }) => { |
|
#[allow(unnecessary_qualification)] |
|
impl<$($lifetime,)? I, T: Default + core::ops::Deref<Target = I> + core::ops::DerefMut> $trait for T |
|
where |
|
I: $trait, |
|
{ $crate::func_sig!(+(*), $($rest)*); } |
|
}; |
|
} |
|
|
|
#[doc(hidden)] |
|
#[macro_export] |
|
macro_rules! borrow_impl { |
|
($trait:path $(| $lifetime:lifetime |)?, { $($rest:tt)* }) => { |
|
#[allow(unnecessary_qualification)] |
|
impl<$($lifetime,)? I, T: Default + core::ops::Deref<Target = I> + core::ops::DerefMut + core::borrow::Borrow<I> + core::borrow::BorrowMut<I>> $trait for T |
|
where |
|
I: $trait, |
|
{ $crate::func_sig!(%(borrow, borrow_mut), $($rest)*); } |
|
}; |
|
} |
|
|
|
#[doc(hidden)] |
|
#[macro_export] |
|
macro_rules! peripheral_set_impl { |
|
($trait:ty $(| $lifetime:lifetime |)?, { $($rest:tt)* }) => { |
|
impl<$($lifetime,)? 'p, G, A, P, T, C, I, O> $trait for $crate::peripherals::PeripheralSet<'p, G, A, P, T, C, I, O/*, G, A, P, T, C, I, O*/> |
|
where |
|
$($lifetime: 'p,)? |
|
G: $crate::peripherals::gpio::Gpio<'p>, |
|
A: $crate::peripherals::adc::Adc, |
|
P: $crate::peripherals::pwm::Pwm, |
|
T: $crate::peripherals::timers::Timers<'p>, |
|
C: $crate::peripherals::clock::Clock, |
|
I: $crate::peripherals::input::Input<'p>, |
|
O: $crate::peripherals::output::Output<'p>, |
|
{ $($rest)* } |
|
}; |
|
} |
|
|
|
// #[doc(hidden)] |
|
// #[macro_export] |
|
// macro_rules! peripheral_deref_set_impl { |
|
// ($trait:ty $(| $lifetime:lifetime |)?, { $($rest:tt)* }) => { |
|
// impl<$($lifetime,)? 'p, G, A, P, T, C, I, O, GInner, AInner, PInner, TInner, CInner, IInner, OInner> $trait for $crate::peripherals::PeripheralSet<'p, G, A, P, T, C, I, O> |
|
// where |
|
// $($lifetime: 'p,)? |
|
// G: 'p + $crate::peripherals::gpio::Gpio<'p>, |
|
// A: 'p + $crate::peripherals::adc::Adc, |
|
// P: 'p + $crate::peripherals::pwm::Pwm, |
|
// T: 'p + $crate::peripherals::timers::Timers<'p>, |
|
// C: 'p + $crate::peripherals::clock::Clock, |
|
// I: 'p + $crate::peripherals::input::Input<'p>, |
|
// O: 'p + $crate::peripherals::output::Output<'p>, |
|
// GInner: 'p + Deref< |
|
// { $($rest)* } |
|
// }; |
|
// } |
|
|
|
#[doc(hidden)] |
|
#[macro_export] |
|
macro_rules! func_sig { |
|
// [No block + Ret] Our ideal form: specified return type, no block: |
|
// (none) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident($($idents:ident : $types:ty),*) -> $ret:ty; $($rest:tt)*) => { |
|
#[inline] fn $fn_name($($idents : $types),*) -> $ret { compile_error!("trait functions not supported yet!") } |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, $($rest)*); |
|
}; |
|
// (self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(self$(,)? $($idents:ident : $types:ty),*) -> $ret:ty; $($rest:tt)*) => { |
|
#[inline] fn $fn_name(self, $($idents : $types),*) -> $ret { ($($indir$indir)?self)$(.$nom)?$(.$i_im())?.$fn_name($($idents),*) } |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, $($rest)*); |
|
}; |
|
// (mut self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(mut self$(,)? $($idents:ident : $types:ty),*) -> $ret:ty; $($rest:tt)*) => { |
|
#[inline] fn $fn_name(mut self, $($idents : $types),*) -> $ret { ($($indir$indir)?self)$(.$nom)?$(.$i_mut())?.$fn_name($($idents),*) } |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, $($rest)*); |
|
}; |
|
// (&self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(&self$(,)? $($idents:ident : $types:ty),*) -> $ret:ty; $($rest:tt)*) => { |
|
#[inline] fn $fn_name(&self, $($idents : $types),*) -> $ret { ($($indir$indir)?self)$(.$nom)?$(.$i_im())?.$fn_name($($idents),*) } |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, $($rest)*); |
|
}; |
|
// (&mut self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(&mut self$(,)? $($idents:ident : $types:ty),*) -> $ret:ty; $($rest:tt)*) => { |
|
#[inline] fn $fn_name(&mut self, $($idents : $types),*) -> $ret { ($($indir$indir)?self)$(.$nom)?$(.$i_mut())?.$fn_name($($idents),*) } |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, $($rest)*); |
|
}; |
|
|
|
|
|
// [Block + Ret] Ditch blocks if you've got them: |
|
// (none) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident($($idents:ident : $types:ty),*) -> $ret:ty $block:block $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name($($idents : $types),*) -> $ret; $($rest)*); }; |
|
// (self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(self$(,)? $($self:expr,)? $($idents:ident : $types:ty),*) -> $ret:ty $block:block $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name(self, $($idents : $types),*) -> $ret; $($rest)*); }; |
|
// (mut self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(mut self$(,)? $($idents:ident : $types:ty),*) -> $ret:ty $block:block $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name(mut self, $($idents : $types),*) -> $ret; $($rest)*); }; |
|
// (&self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(&self$(,)? $($idents:ident : $types:ty),*) -> $ret:ty $block:block $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name(&self, $($idents : $types),*) -> $ret; $($rest)*); }; |
|
// (&mut self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(&mut self$(,)? $($idents:ident : $types:ty),*) -> $ret:ty $block:block $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name(&mut self, $($idents : $types),*) -> $ret; $($rest)*); }; |
|
|
|
|
|
// [No Block + No Ret] Add in return types if they're not specified: |
|
// (none) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident($($idents:ident : $types:ty),*); $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name($($idents : $types),*) -> (); $($rest)*); }; |
|
// (self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(self$(,)? $($idents:ident : $types:ty),*); $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name(self, $($idents : $types),*) -> (); $($rest)*); }; |
|
// (mut self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(mut self$(,)? $($idents:ident : $types:ty),*); $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name(mut self, $($idents : $types),*) -> (); $($rest)*); }; |
|
// (&self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(&self$(,)? $($idents:ident : $types:ty),*); $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name(&self, $($idents : $types),*) -> (); $($rest)*); }; |
|
// (&mut self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(&mut self$(,)? $($idents:ident : $types:ty),*); $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name(&mut self, $($idents : $types),*) -> (); $($rest)*); }; |
|
|
|
|
|
// [Block + No Ret] Strip blocks + add in return types: |
|
// (none) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident( $($idents:ident : $types:ty),*) $block:block $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name($($idents : $types),*) -> (); $($rest)*); }; |
|
// (self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(self$(,)? $($idents:ident : $types:ty),*) $block:block $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name(self, $($idents : $types),*) -> (); $($rest)*); }; |
|
// (mut self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(mut self$(,)? $($idents:ident : $types:ty),*) $block:block $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name(mut self, $($idents : $types),*) -> (); $($rest)*); }; |
|
// (&self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(&self$(,)? $($idents:ident : $types:ty),*) $block:block $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name(&self $($idents : $types),*) -> (); $($rest)*); }; |
|
// (&mut self) |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, $(#[$attr:meta])* fn $fn_name:ident(&mut self$(,)? $($idents:ident : $types:ty),*) $block:block $($rest:tt)*) => { |
|
$crate::func_sig!($(+($indir))? $(%($i_im, $i_mut))? $($nom)?, fn $fn_name(&mut self, $($idents : $types),*) -> (); $($rest)*); }; |
|
|
|
|
|
// And, finally, the end: |
|
($(+($indir:tt))? $(%($i_im:ident, $i_mut:ident))? $($nom:ident)?, ) => {}; |
|
} |
what
Background
I.e. this macro:
core/traits/src/peripherals/mod.rs
Lines 196 to 358 in 5e0f0eb
which is invoked on all the peripheral trait definitions:
core/traits/src/peripherals/gpio.rs
Line 141 in 5e0f0eb
Like #176 this was a well-intentioned but ultimately ill-advised early design decision.
The idea here was to have a
Peripheralstrait that is a super trait of all of the individual peripheral traits, for a few reasons:P: Peripheralsinstance instead of needing to grow 7+ generic parametersGpio::read(p, ...),Clock::get_milliseconds(p, ...). This is particularly useful in testsInputimpl and anOutputimpl that share state to act as a loopback device) to use external state sharing mechanisms (i.e.Arc<Mutex<_>>,RefCell<_>, etc.)The 3rd point is a little subtle; here's an example:
PeripheralsSet:inputandoutputto be separate instances; if we want one type (i.e.LoopbackIo) and one instance of that type to provide both ourInputandOutputimplementations we need to resort to "external" state sharing mechanisms likeArc<Mutex<_>>to produce two instances that ultimately reference the same instance or some shared dataPeripheralssupertrait on the other hand lets us sidestep this:Peripheralsimplementor can choose to shell out to another type for a particular peripheral traitFor the above reasons, we made
Peripheralsa supertrait:core/traits/src/peripherals/mod.rs
Line 37 in 5e0f0eb
We still recognized that the common case was definitely going to be separate instances for each peripheral and that it would be unacceptable for users to implement
Peripheralsfor each unique set of peripheral trait implementors they wanted to use together, so:PeripheralSetwhich held instances of each peripheral and was generic over the 7 peripheralsPeripheralSetimplement each of the peripheral traits by shelling out to the peripheral instance contained within (this is what theperipheral_trait!macro did)Problems
This worked but had several downsides, mostly with the
peripheral_traitmacro:peripheral_traitmacro contains an ad-hoc redefinition of the Rust grammar's definition of what constitutes legal syntax for trait definitions: it's both very hard to read and subtly wrong in many waysrustfmt) don't interact well with items declared inside declarative macros like this. errors stemming from mistakes in the declarative macro's definition of trait definition syntax are extremely hard to diagnose for developers not very familiar with Rust declarative macrosperipheral_trait!because we can't have a declarative macro like it run on the output of a macro like#[async_trait]A Solution
3 years later, with the benefit of hindsight, we can come up with a solution that sidesteps these issues while still fulfilling the design constraints that led us to write
peripheral_trait.PeripheralstraitFirst: We should have the
Peripheralstrait have associated types for each of the peripheral traits within instead of being a super trait. Something like:Our goal was to hide the particular types used for the peripherals instead of having them leak into users of the peripherals as generic parameters. Associated types are a cleaner way of achieving this than supertraits.
As an added bonus it's now easier to selectively have some peripherals be shared; i.e. if you want to have a
LoopbackIodevice, you only need to:LoopbackIodevice and whatever other peripherals you want to usePeripheralson that struct, exposing the same type and instance forInputandOutputand their gettersUnder the existing setup you would have had to write delegating trait implementations of all the peripheral traits for your new type (with the
InputandOutputimpls delegating to the same instance).PeripheralsWrapperThe above addresses the "shared state between peripheral impls" concern (the 3rd point) and manages to make using the Peripherals in your own API not overly cumbersome (you can just be generic over
P: Peripheralsinstead of all 7 individual peripheral traits) but it does not address the 2nd concern: actually using this version of thePeripheralstrait is still more cumbersome that the current incarnation because you now need to callget_gpio, etc. before calling your particular peripheral trait's methods.We can fix this by: providing a type that does have all the peripheral traits implemented on it and delegates to an underlying
Peripheralsinstance.I think we had kind of the right idea with
peripheral_trait!but just bad execution: it's hard to make a declarative macro like this robust and composable but proc macro attributes are a good fit for this task.ambassadoris one such proc macro that does exactly whatperipheral_traitwas looking to do.We can use
ambassadorto provide delegated impls of all the peripheral traits onPeripheralsWrapper, wherePeripheralsWrapperis a type like:with delegations like this:
We can then offer this extension to actually construct a
PeripheralsWrapperwithout consuming the underlyingPeripheralsinstance:And have the
Deref/DerefMutimpls onInstructionInterpreterleverage the above methods.We can also provide delegated impls on
PeripheralSetto cover the common case.steps
Peripheralstraitperipheral_trait!macroambassadorprovided delegated implswhere
branch:
imp/peripheral-trait-reformopen questions