How to specify a `Fn`-bound generic argument in associated type

I'm struggling with a decorator pattern where I want to count the number of invocations of a method without making intrusive modification to the method implementation.

Here is the smallest example I can think of. It doesn't compile and the compiler error shows where I'm stuck -- Not being able to specify a Fn-bound generic argument.

mod lib {
    // Library code, which I have 0 control over.
    pub trait Thing {
        fn do_something(&self);
    }
    
    pub trait Factory {
        type Th: Thing;
        
        fn get_thing(&self) -> Self::Th;
    }
    
    pub fn run<F: Factory>(f: &F) {
        let thing = f.get_thing();
        thing.do_something();
    }
}

mod _impl {
    // My implementation code,
    // which I prefer not to change just to add instrumentation.
    use super::lib::{Thing, Factory};
    
    pub struct ThingImpl;
    
    impl Thing for ThingImpl {
        fn do_something(&self) {
            println!("ThingImpl::do_something");
        }
    }
    
    
    pub struct FactoryImpl;
    
    impl Factory for FactoryImpl {
        type Th = ThingImpl;
        
        fn get_thing(&self) -> Self::Th {
            ThingImpl {}
        }
    }
}

mod instrument {
    // Instrumentation decorator,
    // which can instrument my implementations
    // via its public API.
    use std::cell::RefCell;

    use super::lib::{Thing, Factory};
    
    pub struct ThingWithDecorator<Th, Deco> {
        inner: Th,
        decorator: Deco,
    }
    
    impl<Th: Thing, D: Fn()> Thing for ThingWithDecorator<Th, D> {
        fn do_something(&self) {
            (self.decorator)();
            self.inner.do_something()
        }
    }
    
    pub struct FactoryWithCounter<Inner> {
        inner: Inner,
        count: RefCell<usize>,
    }
    
    impl<Fty> FactoryWithCounter<Fty> {
        pub fn count(&self) -> usize {
            *self.count.borrow()
        }

        fn inc_counter(&self) {
            let mut count = self.count.borrow_mut();
            *count += 1;
        }
    }
    
    impl<F: Factory> Factory for FactoryWithCounter<F> {
        type Th = ThingWithDecorator<F::Th, _>;
        
        fn get_thing(&self) -> Self::Th {
            ThingWithDecorator {
                inner: self.inner.get_thing(),
                decorator: || {
                    self.inc_counter()
                },
            }
        }
    }
    
    pub trait FactoryExt : Factory {
        fn with_counter(self) -> FactoryWithCounter<Self> where Self: Sized {
            FactoryWithCounter {
                inner: self,
                count: Default::default(),
            }
        }
    }
    
    impl<F: Factory> FactoryExt for F {}
}


fn main() {
    use instrument::FactoryExt;
    
    let factory = _impl::FactoryImpl {};
    
    let factory = factory.with_counter();
    
    lib::run(&factory);
    
    println!("do_something called {} times", factory.count());
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0121]: the placeholder `_` is not allowed within types on item signatures for associated types
  --> src/main.rs:81:45
   |
81 |         type Th = ThingWithDecorator<F::Th, _>;
   |                                             ^ not allowed in type signatures

For more information about this error, try `rustc --explain E0121`.
error: could not compile `playground` (bin "playground") due to 1 previous error

There's no cost-free ways on stable, which I'll get back to in a moment, but there's a bigger blocker with your playground.

If you have a function that captures the input lifetime on &self , then the output type is a different type for every input lifetime:

// Get a `&'a str` back if you pass in a `&'a str`
// Get a `&'b str` back if you pass in a `&'b str`
// These are distinct types (unless `'a == 'b`)
fn trim(s: &str) -> &str { s }

And that's what you're trying to do with your implementation:

    impl<F: Factory> Factory for FactoryWithCounter<F> {
        type Th = ???;
        fn get_thing(&self)
        -> //                         vvvvvvvvvvvvvvvvvvv
            ThingWithDecorator<F::Th, SomeClosureType<'_>>
        {
            ThingWithDecorator {
                inner: self.inner.get_thing(),
                decorator: || {
                    self.inc_counter()
                },
            }
        }
    }

But the trait doesn't allow that: you must declare one single type for Factory::Th, and return exactly that type no matter what the input lifetime on &self is.

    pub trait Factory {
        //   vv A concrete type, not a lifetime-parameterized 
        type Th: Thing;             // type constructor
        fn get_thing(&self) -> Self::Th;
    }

If that wasn't a problem, the main solution on stable is to use a Box<dyn Fn()> or similar. And eventually we'll be able to do this:

    impl<F: Factory> Factory for FactoryWithCounter<F> {
        type Th = ThingWithDecorator<F::Th, impl Fn()>;        
        fn get_thing(&self) -> Self::Th {

But neither will help unless you can stop capturing &self in your closure.

1 Like

The simplest solution to your stated problem (count invocations) is to not try to use a function when the RefCell<usize> itself[1] will do. To solve the lifetime/capturing problem that @quinedot points out, use Rc or Arc on the counter instead of borrowing the FactoryWithCounter. (Rc and RefCell are often seen together, because when you need mutation that the borrow checker can't check, you also often need shared ownership.[2])

Also, as a small performance improvement, Cell<usize> is more efficient than RefCell<usize> because it doesn't need any borrow checking.

Here's your instrument module with those changes, after which the program will compile and run.

mod instrument {
    use std::cell::Cell;
    use std::rc::Rc;

    use super::lib::{Thing, Factory};
    
    pub struct ThingWithCounter<Th> {
        inner: Th,
        count: Rc<Cell<usize>>,
    }
    
    impl<Th: Thing> Thing for ThingWithCounter<Th> {
        fn do_something(&self) {
            self.count.set(self.count.get() + 1);
            self.inner.do_something()
        }
    }
    
    pub struct FactoryWithCounter<Inner> {
        inner: Inner,
        count: Rc<Cell<usize>>,
    }
    
    impl<Fty> FactoryWithCounter<Fty> {
        pub fn count(&self) -> usize {
            self.count.get()
        }
    }
    
    impl<F: Factory> Factory for FactoryWithCounter<F> {
        type Th = ThingWithCounter<F::Th>;
        
        fn get_thing(&self) -> Self::Th {
            ThingWithCounter {
                inner: self.inner.get_thing(),
                count: self.count.clone(),
            }
        }
    }
    
    pub trait FactoryExt : Factory {
        fn with_counter(self) -> FactoryWithCounter<Self> where Self: Sized {
            FactoryWithCounter {
                inner: self,
                count: Default::default(),
            }
        }
    }
    
    impl<F: Factory> FactoryExt for F {}
}

  1. or, if you prefer, a newtype around it that only permits incrementing ↩︎

  2. But not all such uses have them directly nested like this! ↩︎

3 Likes

Yes, I noticed the lifetime issue as well. Probably I can solve it by moving a Rc or Arc into the closure. Thanks for the note that we can use Box<dyn Fn()> now and the ultimate solution is type_alias_impl_trait.

Thank you kpreid.

This is an interesting alternative solution for the problem, and it is indeed a valid one! However, I may still need a closure decorator for some other cases. Thanks for the input though!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.