runtime_injector icon indicating copy to clipboard operation
runtime_injector copied to clipboard

No examples for interior mutability except for the constant

Open NeveHanter opened this issue 3 years ago • 2 comments

Hey, I started playing around with this DI container but I'm unable to achieve interior mutability. Could you provide some examples how that could be achieved with both Svc modes (Rc/Arc) and:

  • Cell,
  • RefCell,
  • Mutex,
  • RwLock,
  • or any other wrapper of any kind?

I've only found the example for constant with the Mutex in one of the docstrings:

builder.provide(constant(Mutex::new(0i32)));

I would like to be able to provide particular component wrapped for example by Mutex and then be able to retrieve this Mutex from the container.

I've created small example with comments what I would like to be able to achieve:

use runtime_injector::*;
use std::cell::{Cell, RefCell};
use std::error::Error;
use std::sync::{Mutex, RwLock};

pub trait Foo: Service {
    fn increase(&mut self);
}

#[derive(Default)]
struct FooImpl(u64);

impl Foo for FooImpl {
    fn increase(&mut self) {
        self.0 += 1;
    }
}

pub trait Bar: Service {
    fn increase(&mut self);
}

struct BarImpl {
    foo: Svc<dyn Foo>,
}

impl BarImpl {
    // Not possible to get Cell/RefCell/Mutex/RwLock
    pub fn new(foo: Svc<dyn Foo>) -> Self {
        Self { foo }
    }
}

impl Bar for BarImpl {
    fn increase(&mut self) {
        self.foo.increase();
    }
}

interface!(
    dyn Foo = [FooImpl],
    dyn Bar = [BarImpl]
);

fn main() -> Result<(), Box<dyn Error>> {
    let mut builder = Injector::builder();

    // No obvious way to allow interior mutability, like auto creation of Cell/RefCell/Mutex/RwLock
    builder.provide(FooImpl::default.singleton().with_interface::<dyn Foo>());
    builder.provide(BarImpl::new.singleton().with_interface::<dyn Bar>());

    // It could be available for example in such way:
    builder.provide(FooImpl::default.wrap_in_mutex().singleton().with_interface::<dyn Foo>());
    // or:
    builder.provide(FooImpl::default.wrap_using(|result| Mutex::new(result)).singleton().with_interface::<dyn Foo>());

    let injector = builder.build();

    // Not able to call these as they require &mut
    injector.get::<Svc<dyn Foo>>()?.increase();
    injector.get::<Svc<dyn Bar>>()?.increase();

    // No Cell/RefCell/Mutex/RwLock availability
    injector.get::<Cell<Svc<dyn Bar>>>()?.increase();
    injector.get::<RefCell<Svc<dyn Bar>>>()?.increase();
    injector.get::<Mutex<Svc<dyn Bar>>>()?.increase();
    injector.get::<RwLock<Svc<dyn Bar>>>()?.increase();

    Ok(())
}

NeveHanter avatar Oct 01 '21 01:10 NeveHanter

Mutex<Svc<dyn Foo>> (or other similar interior mutability types) is probably not what you want. In this case, you'd be guaranteeing mutually exclusive access to the shared pointer to the data, not to the data itself. This wouldn't give you access to call &mut self methods in dyn Foo. Instead, you probably want a Svc<Mutex<dyn Foo>> to be returned by the injector (for example).

I think this should be doable with new providers like you mentioned. An .in_mutex/.in_rc/etc. suite of extensions might be nice to have. Personally I think implementations of the interface should have interior mutability instead (meaning Foo::increase would instead take &self and FooImpl would hold an AtomicU64, for example), but I'm sure there are use cases where having a mutex/refcell/etc provided for you would be nice.

TehPers avatar Apr 11 '22 19:04 TehPers

On second thought, even just a .map function for service factories could be useful and would cover even more situations:

builder.provide(FooImpl::default.map(Mutex::new).singleton());

I'm not sure how this could handle interfaces though.

TehPers avatar Apr 28 '22 22:04 TehPers