Qualifiers

Sometimes users may need multiple shared instances of the same type for different purposes. For example the application may need different ThreadPool:

  • A thread pool for UI with a single thread, since OpenGL does not like concurrency.
  • A thread pool for general purpose CPU bound tasks, with a thread for each physical core.
  • A thread pool for IO bound tasks, with a small amount of threads. It is IO bound so more threads won't make it faster
  • A thread pool for other blocking tasks with unbounded threads. Most of them will be idling anyway.

They all share the same type/interface, but have different internal characteristics. Trying to bind them all will cause duplicated binding failures. Furthermore, the user need to be able to specify which one they want.

New type idiom

In most cases, the new type idiom is preferred. With new types, Lockjaw sees them as unrelated and won't cause duplicated binding issues. The compiler also provides strong compile time type check to ensure the correct one is used (an API that do IO work can explicitly ask for the IoThreadPool).

However, the new type idiom does not work with bindings with type generated by Lockjaw such as providers, optional bindings, and multibinding containers. It may also cause a lot of wrapping/unwrapping when working with third party libraries.

Qualifiers

Qualifiers are hints for Lockjaw to give a type different tags, so they can be bound separately.

A qualifier must be declared first using the #[qualifier] attribute macro.

#[qualifier]
pub struct Q1;

The struct body does not matter and probably should be empty.

Once the qualifier is declared, it can then be used in the #[qualified] attribute on a method in a #[module], marking the return type as qualified. Lockjaw will treat the bindings as distinct types.

#[module]
impl MyModule {
    #[provides]
    pub fn provide_string() -> String {
        "string".to_owned()
    }

    #[provides]
    #[qualified(Q1)]
    pub fn provide_q1_string() -> String {
        "q1_string".to_owned()
    }

    #[provides]
    #[qualified(Q2)]
    pub fn provide_q2_string() -> String {
        "q2_string".to_owned()
    }
}

Qualified types can be injected using the #[qualified] attribute on the parameter, or the component method.

#[component(modules: [MyModule])]
pub trait MyComponent {
    fn string(&self) -> String;
    #[qualified(Q1)]
    fn q1_string(&self) -> String;
    #[qualified(Q2)]
    fn q2_string(&self) -> String;
}
    #[inject]
    pub fn new(#[qualified(Q)] s: String) -> Foo {
        Foo { s }
    }