Abstracting over ownership in FnMut implementation

I'm trying to implement a callable struct which takes either T or &T, and dispatches accordingly:

trait Thing {}

enum Reference<'a> {
  Owned(Box<dyn Thing>),
  Borrowed(&'a dyn Thing)
}

struct Collect<'a>(pub Vec<Reference<'a>>);

// ...

impl<'a, T: Thing> FnMut<(T, )> for Collect<'a> {
    extern "rust-call" fn call_mut (&mut self, args: (T,)) -> Self::Output {
        self.0.push(Reference::Owned(args.0))
    }
}

// ...

impl<'a, T: Thing> FnMut<(&'a T, )> for Collect<'a> {
    extern "rust-call" fn call_mut (&mut self, args: (&'a T,)) -> Self::Output {
        self.0.push(Reference::Borrowed(args.0))
    }
}

Permalink to the playground

This fails with:

   Compiling playground v0.0.1 (/playground)
error[E0119]: conflicting implementations of trait `FnOnce<(&_,)>` for type `Collect<'_>`
  --> src/main.rs:23:1
   |
12 | impl<'a, T: Thing> FnOnce<(T, )> for Collect<'a> {
   | ------------------------------------------------ first implementation here
...
23 | impl<'a, T: Thing> FnOnce<(&'a T, )> for Collect<'a> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Collect<'_>`
   |
   = note: downstream crates may implement trait `Thing` for type `&_`

What's the underyling issue here, and how could I tell Rust to discern between T and &T, dispatching to the correct FnMut impl?

Your misunderstanding is that you think T is an owned type. It isn't. A generic type parameter/type variable can stand in for any type. Thus, T matches all types, including references, and it doesn't make sense to "discern between &T and T", because a &T is also a T (except that a fully precise notation wouldn't call the type variables by the same name).

This would require specialization to solve "naïvely", but I would recommend against doing so. If you can implement a trait for all types T, there's generally no good reason not to do that. If you need borrowing and you want to allow owned data to be passed too, bound the impl with T: Borrow or implement for Cow<'_, T> only.

6 Likes

Thanks! How would I use Borrow or Cow in my example? Changing the second pair of impls to:

impl<'a, T: Borrow<dyn Thing>> FnOnce<(T, )> for Collect<'a> {
    type Output = ();
    extern "rust-call" fn call_once (self, _: (T,)) -> Self::Output { unreachable!() }
}

impl<'a, T: Borrow<dyn Thing>> FnMut<(T, )> for Collect<'a> {
    extern "rust-call" fn call_mut (&mut self, args: (T,)) -> Self::Output {
        self.0.push(Reference::Borrowed(args.0))
    }
}

results in essentially the same error; I'm still trying to wrap my head around Cow and where it's appropriate to use.

The point is exactly that you shouldn't write two impls, only one.

1 Like

Another aspect in this context: The implementation is bound on : Thing, so if &… types would never implement Thing, then there would be a clear distinction between T for T: Thing and &T for T: Thing. Unfortunately for this case, reference types are special (along with a few other types sometimes called “fundamental” from the unstable marker #[fundamental] that’s used) in that downstream crates can add implementations of Thing for &Foo for references to their own local types, so there’s no way to rules out &_: Thing implementations.

I can imagine different new language features that could help here… from negative trait impls to a way to re-define/amend the set of #[fundamental] types for your own traits.


Regarding workarounds on stable… one potentially nice approach if an implementation of Thing for &T (where T: Thing) is possible (e.g. since all methods are &self methods) the implementation for call_mut could become part of the Thing trait, with a default-impl, and the implementation for references could override the default.

#![feature(unboxed_closures, fn_traits)]

use std::fmt;

trait Thing {
    fn collect_call_mut<'a>(self, collect: &mut Collect<'a>)
    where
        Self: 'a + Sized,
    {
        collect.0.push(Reference::Owned(Box::new(self)));
    }
}

impl<T: Thing> Thing for &T {
    fn collect_call_mut<'a>(self, collect: &mut Collect<'a>)
    where
        Self: 'a + Sized,
    {
        collect.0.push(Reference::Borrowed(self));
    }
}

enum Reference<'a> {
    Owned(Box<dyn Thing + 'a>),
    Borrowed(&'a dyn Thing),
}
impl fmt::Debug for Reference<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 
        match self {
            Reference::Owned(_) => f.write_str("Owned(..)"),
            Reference::Borrowed(_) => f.write_str("Borrowed(..)"),
        }
    }
}

#[derive(Debug)]
struct Collect<'a>(pub Vec<Reference<'a>>);

impl<'a, T: Thing + 'a> FnOnce<(T,)> for Collect<'a> {
    type Output = ();
    extern "rust-call" fn call_once(mut self, args: (T,)) -> Self::Output {
        self.call_mut(args)
    }
}

impl<'a, T: Thing + 'a> FnMut<(T,)> for Collect<'a> {
    extern "rust-call" fn call_mut(&mut self, args: (T,)) -> Self::Output {
        args.0.collect_call_mut(self)
    }
}

struct Thang;

impl Thing for Thang {}

fn main() {
    let mut collect = Collect(vec![]);
    collect(Thang);
    collect(&Thang);
    dbg!(&collect);
}
3 Likes

Great one! I already have a impl<T: Thing> Thing for &T, but I never would've thought of using it as the needed workaround. When trying to apply this, however, the Box::new(self) tells me that the parameter type Self may not live long enough and that I should add 'static (which I don't want to). What's with boxes and the static lifetime?

EDIT: nvm, forgot a + 'lifetime in the boxed variant.

Trait objects must always carry a specific lifetime, since the whole point of a trait object is that its concrete underlying type is not known. However, this means that the compiler can't infer any lifetimes related to that (abstract) type (unlike it could with concrete types). Therefore, trait objects must always carry an explicit additional lifetime with them.

However, trait objects are usually made from owning values, therefore it was decided that Box<dyn Trait> is actually syntax sugar for Box<dyn Trait + 'static> to remove some noise. Anything else must be spelled out.

1 Like

Thanks! It's a little counterintuitive, but so are a lot of things. At least this particular fact is "atomic"... though it could've been more discoverable :confused: Really my first run-in with lifetimes in the + 'a position -- been wondering where those come into play :smiley:

In case you want the whole story (in terms of the elision rules at play) here’s the relevant section in the reference :slight_smile:

The relevant effects for this code are that Box<dyn Thing> means Box<dyn Thing + 'static> and that &'a dyn Thing means &'a (dyn Thing + 'a).

In terms of explaining the exact meaning of the + 'a part of trait object types what I could find in the reference is awefully terse.

The basics are not too hard though. At least if one knows the meaning of T: 'a, then a dyn Thing + 'a trait object can – naturally – simply be created from types T that fulfill the bound T: Thing + 'a, i.e. T: Thing and T: 'a.

1 Like