Closed ijackson closed 2 years ago
One potential issue with this solution would be if Piece borrowed self mutably (or took ownership if we weren't using dyn) the macro would still try to use inner() leading to a type error. A potentially more general solution would be to allow the user to choose three different delegates (one for self, &self, and &mut self). It would also probably be useful to be able to access such a feature without using the generated macros directly, probably an attribute macro applied to the impl block similar to #[Derive(Delegate)]. Doing this we could also sanity check the signatures of the chosen delegates. Here is one idea of the end result:
#[delegate_to_methods()]
#[delegate(PieceTrait, target_ref = "inner", target_mut = "inner_mut")]
impl Piece {
fn inner(&self) -> &dyn PieceTrait {
self.ipc
.as_ref()
.expect("attempted to invoke unresolved fastsplit::Piece")
.p.direct_trait_access()
}
fn inner_mut(&mut self) -> &mut dyn PieceTrait {
self.ipc
.as_mut()
.expect("attempted to invoke unresolved fastsplit::Piece")
.p.direct_trait_access_mut()
}
}
One potential issue with this solution would be if Piece borrowed self mutably (or took ownership if we weren't using dyn) the macro would still try to use inner() leading to a type error
Indeed.
A potentially more general solution would be to allow the user to choose three different delegates (one for self, &self, and &mut self)
Yes. That woudl be great.
It would also probably be useful to be able to access such a feature without using the generated macros directly,
Well, maybe. Personally I like using the generated macro. In my application I need to apply bunches of magical attributes to the traits and structs and impls. My actual code looks like this:
#[ambassador::delegatable_trait]
#[enum_dispatch]
#[dyn_upcast]
pub trait OutlineTrait: Debug + Sync + Send + 'static {
[ambassador::delegatable_trait]
#[dyn_upcast]
pub trait PieceBaseTrait: OutlineTrait + Send + Debug + 'static {
#[derive(Serialize,Deserialize,Debug)]
struct Piece {
[dyn_upcast]
impl OutlineTrait for Piece {
ambassador_impl_OutlineTrait_body_single_struct!{ inner() }
}
#[dyn_upcast]
impl PieceBaseTrait for Piece {
ambassador_impl_PieceBaseTrait_body_single_struct!{ inner() }
}
#[typetag::serde(name="FastSplit")]
impl PieceTrait for Piece {
ambassador_impl_PieceTrait_body_single_struct!{ inner() }
}
To my mind, feeding all those attrs on the impls through a derive macro on Piece
would be really quite unpleasant and opaque.
If I could have any API I liked, I think the best for me would be an autogenerated macro that generates the whole impl
block so one can do something like this:
#[dyn_upcast]
ambassador_impl_OutlineTrait!{ for Piece }
or this:
#[cfg(feature = "crazy")]
#[dyn_upcast]
ambassador_impl_ExcitingTrait!{
for Crazy<T> where T:Clone,
inner(),
inner_mut(),
inner_owned(),
}
I still think it would be nice to have a way of avoiding the generated macros (which would probably give better error messages), but I can understand why you find them useful. I was working on this and right now have it so
use ambassador::delegatable_trait;
#[delegatable_trait]
trait PieceTrait {
fn get(&self) -> u32;
fn change(&mut self);
}
struct Piece {
inner: Option<Box<dyn PieceTrait>>,
}
impl Piece {
fn inner(&self) -> &dyn PieceTrait {
// I wanted a self contained example
self.inner
.as_ref()
.expect("attempted to invoke unresolved fastsplit::Piece")
.as_ref()
}
fn inner_mut(&mut self) -> &mut dyn PieceTrait {
self.inner
.as_mut()
.expect("attempted to invoke unresolved fastsplit::Piece")
.as_mut()
}
}
impl PieceTrait for Piece {
ambassador_impl_PieceTrait! {body_struct(<>, dyn PieceTrait, (compile_error!("owned receiver")), (inner()), (inner_mut()))}
}
would work. (The ambassador_impl_PieceBaseTrait_body_single_struct! macro is deprecated and only is generated when using the "backward_compatible" feature). I'm still planning to add a macro you could apply to the impl block but you wouldn't have to use it. Does this seem reasonable to you?
After working on things a bit more I think the best way to use the macro directly would be
ambassador_impl_PieceTrait! {body_struct(<>, dyn PieceTrait, (), (inner()), (inner_mut()))}
since the macro emits a special error message when using () if the corresponding receiver type is used
Cool, thanks! I will update my project to use this new syntax.
FYI I ended up with this:
impl_via_ambassador!{
#[dyn_upcast]
impl OutlineTrait for Piece { inner() }
#[dyn_upcast]
impl PieceBaseTrait for Piece { inner() }
#[typetag::serde(name="FastSplit")]
impl PieceTrait for Piece { inner() }
}
via this
#[macro_export]
macro_rules! impl_via_ambassador{
{
$(
$( #[ $attr:meta ] )*
impl $Trait:ident for $Type:ty
{ $($how_immut:tt)* }
)*
} => { $( paste!{
$( #[ $attr ] )*
impl $Trait for $Type {
[< ambassador_impl_ $Trait >]!{ body_struct( <>, dyn $Trait,
(),
($($how_immut)*),
()
) }
}
} )* }
}
which I am quite happy with. So thank you :-).
In my application, I want to delegate to a more compliated expression than simply a single field. So I have done this:
This works great. But I had to alter ambassador to permit more than just a simple identifier as the delegate.
I pasted the whole of
Piece::inner()
to show that it can do arbitrary stuff without having to complicate the macrology. The only requirement for the delegate argument is that appending it toself.
produces a trait object expression.(I hope this passes the github CI. I tried to run the tests locally but they don't work on master. I think I am probably running them with the wrong rust version or something.)