hobofan / ambassador

Delegation of trait implementations via procedural macros
Apache License 2.0
251 stars 13 forks source link

Add a `delegate_to_remote_methods` macro #37

Closed rrbutani closed 2 years ago

rrbutani commented 2 years ago

delegate_to_remote_methods

This PR adds delegate_to_remote_methods:

use ambassador::{delegate_to_remote_methods, delegatable_trait};

#[delegatable_trait]
pub trait Shout {
    fn shout(&self, input: &str) -> String;
}

pub struct Cat;

impl Shout for Cat {
    fn shout(&self, input: &str) -> String {
        format!("{} - meow!", input)
    }
}

pub struct BoxedCat(Box<Cat>);

impl BoxedCat {
fn inner(&self) -> &Cat { &self.0 }
}

#[delegate_to_remote_methods]
#[delegate(Shout, target_ref = "inner")]
impl BoxedCat {
    // `inner` can be defined anywhere: trait method or inherent method, in this crate or elsewhere
    fn inner(&self) -> &Cat;
}

It's identical to delegate_to_methods except:

This allows it to be used in scenarios involving foreign Self types (without needing to create intermediary local traits to house the target methods) like the one described in #36:

use ambassador::{delegatable_trait, delegate_to_remote_methods};
use std::ops::{Deref, DerefMut};
use std::{sync::Arc, rc::Rc};

#[delegatable_trait]
pub trait Shout { fn shout(&self, input: &str) -> String; }

pub struct Cat;
impl Shout for Cat { fn shout(&self, input: &str) -> String { format!("{} - meow!", input) } }

pub struct Dog;
impl Shout for Dog { fn shout(&self, input: &str) -> String { format!("{} - wuff!", input) } }

#[delegate_to_remote_methods]
#[delegate(Shout, target_ref = "deref")]
impl<S: ?Sized + Send + Shout, T: Deref<Target = S>> T {
    fn deref(&self) -> &S;
}

pub fn shout(pet: &impl Shout) { println!("{}", pet.shout("hi")); }

pub fn main() {
    shout(&Cat);
    shout(&Dog);

    let a: Box<dyn Shout> = Box::new(Cat);
    let b: Arc<dyn Shout + Send> = Arc::new(Dog);
    let c: Rc<dyn Shout + Send + Sync + 'static> = Rc::new(Cat);
    // shout(&a); // Will error since we required `Send` above.
    shout(&b);
    shout(&c);

    let d: Box<Cat> = Box::new(Cat);
    shout(&d);
}

Other Stuff

There are a couple of other things in this PR:

I can split these into their own PRs if required.

Open Questions

Is the name okay? delegate_to_remote_methods mirrors delegate_remote (which makes sense since, like delegate_remote does not require the type the impls are on to be local, this macro relaxes the requirement that the methods used to do the delegation be local) but it's perhaps misleading since delegate_to_remote_methods will also accept a remote type (as will delegate_to_methods if you place it on a block that implements a trait).

Currently the macro is pretty aggressive about checking that there aren't extra/unused items in the impl it is applied to; my thinking was that silently dropping these items could be a source of confusion. Do we want to relax these checks?

rrbutani commented 2 years ago

@dewert99 I've rebased and updated this PR now that #34 has been merged.

dewert99 commented 2 years ago

Thanks I just merged this