hobofan / ambassador

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

Add support for conditional delegation #55

Closed vigna closed 3 months ago

vigna commented 4 months ago

We are in a situation in which we have frequently this pattern: there's a struct S \<B> with a field f of type B. We need to implement trait T on S conditionally on the fact that B implements T, and to do so by delegating T's method to the implementation of T provided by field f. playground

It seems to me that presently this use case is not covered by ambassador, but all the pieces are there. Would it be difficult to implement?

dewert99 commented 3 months ago

I think this is already supported, based on you playground ambassador supports:

use ambassador::{delegatable_trait, Delegate};

#[derive(Delegate)]
#[delegate(T)]
struct S<B> {
    f: B,
}

/*
Expands to:
impl<B, > T for S<B> where B: T {
    #[inline]
    fn foo(&self) {
        self.f.foo()
    }
}
*/

#[delegatable_trait]
trait T {
    fn foo(&self);
}

impl T for u32 {
    fn foo(&self) {
        println!("{self}")
    }
}

fn main() {
    let s = S { f: 5 };
    s.foo() //Prints: 5
}

More generally #[delegate(Trait)] adds a where clause where Type: Trait where Type is the type of the field being delegate to. This can be disabled using #[delegate(Trait, automatic_where_clause = "false")]. If additional where clauses are required they can be added using #[delegate(Trait, where = "Type1: Trait1, Type2: Trait2, ...")] where Type1, Trait1, Type2, Trait2, ..., can be arbitrary types and traits respectively (including generics).

vigna commented 3 months ago

Well... in theory. I have this trait

#[delegatable_trait]
#[autoimpl(for<T: trait + ?Sized> &T, &mut T, Box<T>)]
pub trait BitLength {
    /// Returns a length in bits.
    fn len(&self) -> usize;
}

But when I use

#[derive(Epserde, Debug, Clone, MemDbg, MemSize, Delegate)]
#[delegate(BitLength, target = "bits")]
pub struct Rank9<B = BitVec, C = Box<[BlockCounters]>> {
    pub(super) bits: B,
    pub(super) counts: C,
}

I get (cargo expand)

        impl<B, C> BitLength for Rank9<B, C>
        where
            B: BitLength,
        {}

Is it possible that there is some interference from the other derive macros?

vigna commented 3 months ago

More in detail, this is the compiler output:

error: cannot find macro `ambassador_impl_BitLength` in this scope
  --> src/rank_sel/rank9.rs:64:12
   |
64 | #[delegate(BitLength, target = "bits")]
   |            ^^^^^^^^^
   |
   = help: have you added the `#[macro_use]` on the module/import?
help: consider importing this macro through its public re-export
   |
9  + use crate::ambassador_impl_BitLength;
   |

error[E0046]: not all trait items implemented, missing: `len`
  --> src/rank_sel/rank9.rs:63:50
   |
63 | #[derive(Epserde, Debug, Clone, MemDbg, MemSize, Delegate)]
   |                                                  ^^^^^^^^ missing `len` in implementation
   |
  ::: src/traits/rank_sel.rs:28:5
   |
28 |     fn len(&self) -> usize;
   |     ----------------------- `len` from trait
   |
   = note: this error originates in the derive macro `Delegate` (in Nightly builds, run with -Z macro-backtrace for more info)
dewert99 commented 3 months ago

Are you trying to use derive(Delegate) in a different module that delegable(Trait)? If so the problem might be related to #45

vigna commented 3 months ago

Well... yes. 😅 Usually traits are somewhere and implementations elsewhere. 🤷🏻

One vote for the comment in the other issue: this should be the first thing in the documentation.

I spent a lot of time writing my ad hoc delegation macros but if I can make this work I'll certainly switch to ambassador, it's a much better solution.

The use ambassador_impl* is quite ugly and appears to use undocumented internals, which might be a problem for future compatibility. It should be output by the derive code IMHO.

dewert99 commented 3 months ago

Closing as duplicate of #45.