Skip to main content

singleton

Attribute Macro singleton 

Source
#[singleton]
Expand description

单例过程宏

§参数说明

  • name: 单例名称,默认使用结构体名称(首字符小写)生成, 例如: TestData -> testData
  • eager_create: 是否在初始化时立即创建实例(预加载)
  • condition: 可选条件闭包或路径,用于控制此单例的 Provider 是否插入 Context 中
  • binds: 绑定路径列表,一般来说这里可以填入结构体的动态实现
  • async: 可选异步标记路径值,指定是否为异步单例(使用 #[async] 属性重命名)
  • auto_register: 是否自动注册到容器(仅在启用 auto-register 特性时有效,默认为 DEFAULT_AUTO_REGISTER
  • default: 使用结构体实现 Default trait 将实例注入仓库中

§辅助宏参数说明

resource

  • name: 单例名称,优先级高
  • option: 表示这个字段是可能找不到的,那么字段类型必须为 Option<T>
  • default: 此字段需要保证实现了 Default trait,在构建实例时,此字段使用默认值
  • vec: 保证获取到所有此类型的单例,应和 name 参数排斥, 字段类型应为 Vec<T>
  • map: 保证获取到所有此类型的单例, 前提当前字段实现了 Singleton trait, 字段类型应为 std::collections::HashMap<K, V>
  • ref: 引用类型, & T

§示例

use std::sync::Arc;

#[singleton(
    name = "myDataImpl",
    condition = test_condition,
    binds = [Self::into_data],
)]
struct DataImpl {
    #[resource(name = "testData")]
    data: String,
    /// 这里用的是字段名称去仓库找单例并注入 `more_data` -> `moreData`, 若是指定名称则不会这样处理
    more_data: Option<Vec<u8>>,
}

trait Data {
    fn call() -> &'static str;
}

impl Data for DataImpl {
    fn call() -> &'static str {
        "hello"
    }
}

impl DataImpl {

    fn into_data(self) -> Arc<dyn Data> {
        Arc::new(self)
    }
}

fn test_condition(cx: &ApplicationContext) -> bool {
   !cx.contains_single_with_name::<i32>("5")
}

Single instance process macro

§Parameter Description

  • name: singleton name, generated by default using the structure name (lowercase first character), for example: TestData -> testData
  • ‘eager_create’: Whether to immediately create an instance (preloaded) during initialization
  • condition: Optional conditional closure or path, used to control whether the Provider of this singleton is inserted into the Context
  • bindings: a list of binding paths, usually where the dynamic implementation of the structure can be filled in
  • async: Optional asynchronous tag path value, specifying whether it is an asynchronous singleton (rename using the # [async] property)
  • auto_register: Whether it is automatically registered to the container (only valid when the auto register feature is enabled, default is DEFAULT_SUTD_RIGIST)
  • default: Use a struct to implement the Default trait and inject instances into the repository

§Auxiliary Macro Parameter Description

resource

  • name: Single instance name, high priority
  • option: indicates that this field may not be found, so the field type must be Option<T>
  • default: This field needs to ensure that the Default trait is implemented. When building instances, this field uses the default value
  • vec: Ensure to obtain all singleton of this type, which should be excluded from the name parameter, and the field type should be Vec<T>
  • map: Ensure that all singleton instances of this type are obtained, provided that the current field implements the Singleton trait and the field type should be std:: collections: HashMap<K, V>
  • ref: Reference type,&T

§Example

use std::sync::Arc;

#[singleton(
    name = "myDataImpl",
    condition = test_condition,
    binds = [Self::into_data],
)]
struct DataImpl {
    #[resource(name = "testData")]
    data: String,
Here, the field name is used to search for a single instance in the warehouse and inject 'more_data' ->'moreData'. If a name is specified, it will not be processed in this way
    more_data: Option<Vec<u8>>,
}

trait Data {
    fn call() -> &'static str;
}

impl Data for DataImpl {
    fn call() -> &'static str {
        "hello"
    }
}

impl DataImpl {

    fn into_data(self) -> Arc<dyn Data> {
        Arc::new(self)
    }
}

fn test_condition(cx: &ApplicationContext) -> bool {
   ! cx.contains_single_with_name::<i32>("5")
}

#[Singleton], #[Transient] and #[SingleOwner] are attribute macros used to define a Provider, for the difference between the Providers they defined, see Scope.

These three macros can be used on struct, enum, impl block, and fn.

  • When used on struct, enum and impl block, a DefaultProvider implementation is generated for the struct or enum, and the associated type DefaultProvider::Type is the struct or enum itself.

  • When used on fn, a struct with the same name as the function is created, and then a DefaultProvider implementation is generated for the struct, with the associated type DefaultProvider::Type being the return type of the function. As mentioned above, it is recommended to use CamelCase when defining functions. Of course, you can still use snake_case.

§Example

use rudi::{Context, Singleton, Transient};

// impl block

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct A;

#[Singleton]
impl A {
    #[di]
    fn new() -> Self {
        Self
    }
}

// struct

#[Singleton]
#[derive(Debug, Clone, PartialEq, Eq)]
struct B(A);

// enum

#[allow(dead_code)]
#[derive(Debug, PartialEq, Eq)]
#[Transient]
enum C {
    One,

    Two(A),

    #[di]
    Three {
        b: B,
    },
}

// fn

#[Singleton]
fn Run(a: A, b: B, c: C) {
    assert_eq!(b, B(a));
    assert_eq!(c, C::Three { b: B(a) });
}

fn main() {
    let mut cx = Context::auto_register();
    cx.resolve()
}

§Attribute arguments

§#[Singleton] / #[Transient] / #[SingleOwner]: used on struct, enum, impl block and fn

§Common arguments that can be used on struct, enum, impl block, and fn
§An argument that can only be used on struct and enum
  • async
    • type: bool
    • example: #[singleton(async)]
    • optional: true
    • default: false
    • description: Specifies whether the constructor method of a defined Provider is asynchronous. Only valid when used on struct and enum, for impl block and fn cases use async fn.

§#[di]: used on struct, enum, impl block and fn

  • rudi_path
    • type: path to the rudi crate.
    • example: #[di(rudi_path = path::to::rudi)]
    • optional: true
    • default: ::rudi
    • description: Specifies the path to the rudi crate. This argument is used when the rudi crate is not in the root of the crate.

§#[di]: used on fn of impl block

Use #[di] to specify which function of the impl block will be called to construct an instance.

§#[di]: used on variant of enum

Use #[di] to specify which variant of the enum will be constructed.

§#[di]: used on field of struct, field of variant of enum and argument of function

  • name

    • conflict: vec
    • type: any expression that implements Into<Cow<'static, str>>.
    • example: #[di(name = "abc")] / #[di(name = a::b::NAME)] / #[di(name = nth(42))]
    • optional: true
    • default: “”
    • description: Specifies the name of the dependency to be taken out of Context.
    • refer:
  • option

    • conflict: default, vec

    • require: The current field or argument, which must be of type Option<T>.

    • type: bool.

    • example: #[di(option)]

    • optional: true

    • default: false

    • description:

      From the call to the following method

      • cx.resolve_with_name::<Option<T>>(name)
      • cx.resolve_with_name_async::<Option<T>>(name).await

      Instead, call the following method

      • cx.resolve_option_with_name::<T>(name)
      • cx.resolve_option_with_name_async::<T>(name).await
    • refer:

  • default

    • conflict: option, vec

    • require: If no default value is specified, the current field or argument must implement the Default trait.

    • type: empty, or an arbitrary expression type.

    • example: #[di(default)] / #[di(default = 42)] / #[di(default = a::b::func())]

    • optional: true

    • default: None

    • description:

      From the call to the following method

      • cx.resolve_with_name(name)
      • cx.resolve_with_name_async(name).await

      Instead, call the following method

      • match cx.resolve_option_with_name(name) { ... }
      • match cx.resolve_option_with_name_async(name).await { ... }
    • refer:

  • vec

    • conflict: name, option, default

    • require: The current field or argument, which must be of type Vec<T>.

    • type: bool.

    • example: #[di(vec)]

    • optional: true

    • default: false

    • description:

      From the call to the following method

      • cx.resolve_with_name::<Vec<T>>(name)
      • cx.resolve_with_name_async::<Vec<T>>(name).await

      Instead, call the following method

      • cx.resolve_by_type::<T>()
      • cx.resolve_by_type_async::<T>()
    • refer:

  • ref

    • require:

      • exist option argument: The current field or argument, which must be of type Option<&T>.
      • exist vec argument: The current field or argument, which must be of type Vec<&T>.
      • exist default argument or not, the current field or argument, which must be of type &T.
      • if using a type alias, specify the original type using #[di(ref = T)], where T is a non-reference type.
    • type: Option<Type>

    • example:

      • #[di(ref)]
      • #[di(ref = i32)]
      • #[di(option, ref)]
      • #[di(option, ref = i32)]
      • #[di(vec, ref)]
      • #[di(vec, ref = i32)]
      • #[di(default, ref)]
      • #[di(default, ref = i32)]
      • #[di(default = &42, ref)]
      • #[di(default = &42, ref = i32)]
    • optional: true

    • default: None

    • description:

      Get a reference to Singleton or SingleOwner from Context .

      1. Not exist option, vec and default argument, will call the following method

        // async
        cx.just_create_single_with_name_async::<T>(name).await;
        let var = cx.get_single_with_name(name);
        
        // sync
        cx.just_create_single_with_name::<T>(name);
        let var = cx.get_single_with_name(name);
      2. Exist option argument, will call the following method

        // async
        cx.try_just_create_single_with_name_async::<T>(name).await;
        let var = cx.get_single_option_with_name(name);
        
        // sync
        cx.try_just_create_single_with_name::<T>(name);
        let var = cx.get_single_option_with_name(name);
      3. Exist vec argument, will call the following method

        // async
        cx.try_just_create_singles_by_type_async::<T>().await;
        let var = cx.get_singles_by_type();
        
        // sync
        cx.try_just_create_singles_by_type::<T>();
        let var = cx.get_singles_by_type();
      4. Exist default argument, will call the following method

        // async
        cx.try_just_create_single_with_name_async::<T>(name).await;
        let var = match cx.get_single_option_with_name(name) {
            Some(value) => value,
            None => default,
        };
        
        // sync
        cx.try_just_create_single_with_name::<T>(name);
        let var = match cx.get_single_option_with_name(name) {
            Some(value) => value,
            None => default,
        };
      5. If specified using #[di(ref = R)], then all of the above Ts will be replaced with the specified type R.

    • refer:

§Struct, enum and function attributes example

use std::{fmt::Debug, rc::Rc};

use rudi::{Context, Singleton, Transient};

// name

#[Transient]
fn One() -> i8 {
    1
}

#[Transient(name = "2")]
fn Two() -> i8 {
    2
}

// eager_create

#[singleton(name = "3")]
fn Three() -> i16 {
    3
}

#[singleton(name = "4", eager_create)]
fn Four() -> i16 {
    4
}

// condition

fn _5_condition(cx: &Context) -> bool {
    !cx.contains_single_with_name::<i32>("5")
}

#[singleton(name = "5", condition = _5_condition)]
fn Five() -> i32 {
    5
}

#[singleton(name = "6", condition = |_cx| false)]
fn Six() -> i32 {
    6
}

// binds

fn transform<T: Debug + 'static>(t: T) -> Rc<dyn Debug> {
    Rc::new(t)
}

#[singleton(name = "7")]
fn Seven() -> i64 {
    7
}

#[singleton(name = "8", binds = [transform])]
fn Eight() -> i64 {
    8
}

// auto_register

#[singleton(name = "9")]
fn Nine() -> i128 {
    9
}

#[singleton(name = "10", auto_register = false)]
fn Ten() -> i128 {
    10
}

// async

#[Transient]
struct A;

#[Transient(async)]
struct B;

// rudi_path

mod a {
    pub use rudi::*;
}

#[Transient]
#[di(rudi_path = rudi)]
struct C;

#[Transient]
#[di(rudi_path = a)]
struct D;

// `#[di]` used on `variant` of enum

#[allow(dead_code)]
#[derive(PartialEq, Eq, Debug)]
#[Transient]
enum EF {
    #[di]
    E,
    F,
}

#[tokio::main]
async fn main() {
    let mut cx = Context::auto_register();

    assert_eq!(cx.resolve::<i8>(), 1);
    assert_eq!(cx.resolve_with_name::<i8>("2"), 2);

    assert!(!cx.contains_single_with_name::<i16>("3"));
    assert!(cx.contains_single_with_name::<i16>("4"));

    assert!(cx.contains_provider_with_name::<i32>("5"));
    assert!(!cx.contains_provider_with_name::<i32>("6"));

    assert!(cx.resolve_option_with_name::<Rc<dyn Debug>>("7").is_none());
    assert!(cx.resolve_option_with_name::<Rc<dyn Debug>>("8").is_some());

    assert!(cx.get_provider_with_name::<i128>("9").is_some());
    assert!(cx.get_provider_with_name::<i128>("10").is_none());

    assert!(cx.resolve_option::<A>().is_some());
    assert!(cx.resolve_option_async::<B>().await.is_some());

    assert!(cx.resolve_option::<C>().is_some());
    assert!(cx.resolve_option::<D>().is_some());

    assert_eq!(cx.resolve::<EF>(), EF::E);
}

§Field and argument attributes example

Although the following example only shows how to use attributes on field, it is the same as using them on argument.

use rudi::{Context, Singleton, Transient};

// name

#[Singleton]
fn One() -> i8 {
    1
}

#[singleton(name = "2")]
fn Two() -> i8 {
    2
}

#[Transient]
struct A(i8);

#[Transient]
struct B(#[di(name = "2")] i8);

// option

#[Singleton]
fn Three() -> Option<i16> {
    Some(3)
}

#[Singleton]
fn Four() -> i16 {
    4
}

#[Transient]
struct C(Option<i16>);

#[Transient]
struct D(#[di(option)] Option<i16>);

// default

#[Transient]
struct E(#[di(default)] i32);

#[Transient]
struct F(#[di(default = 42)] i32);

// vec

#[Singleton]
fn Five() -> Vec<i64> {
    vec![5]
}

#[singleton(eager_create)]
fn Six() -> i64 {
    6
}

#[Transient]
struct G(Vec<i64>);

#[Transient]
struct H(#[di(vec)] Vec<i64>);

#[Singleton]
fn Run(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) {
    assert_eq!(a.0, 1);
    assert_eq!(b.0, 2);

    assert_eq!(c.0.unwrap(), 3);
    assert_eq!(d.0.unwrap(), 4);

    assert_eq!(e.0, 0);
    assert_eq!(f.0, 42);

    assert_eq!(g.0[0], 5);
    assert_eq!(h.0[0], 6);
}

#[singleton(name = "ref")]
fn Run2(
    #[di(ref)] one: &i8,
    #[di(ref, name = "2")] two: &i8,

    #[di(ref)] three: &Option<i16>,
    #[di(ref, option)] four: Option<&i16>,

    #[di(ref, default = &0)] zero: &i32,
    #[di(ref, default = &42)] forty_two: &i32,

    #[di(ref)] five: &Vec<i64>,
    #[di(ref, vec)] six: Vec<&i64>,
) {
    assert_eq!(one, &1);
    assert_eq!(two, &2);

    assert_eq!(three, &Some(3));
    assert_eq!(four, Some(&4));

    assert_eq!(zero, &0);
    assert_eq!(forty_two, &42);

    assert_eq!(five, &vec![5]);
    assert_eq!(six, vec![&6]);
}

mod alias {
    use rudi::Singleton;

    type OneAndTwo<'a> = &'a i8;

    type Three<'a> = &'a Option<i16>;
    type Four<'a> = Option<&'a i16>;

    type ZeroAndFortyTwo<'a> = &'a i32;

    type Five<'a> = &'a Vec<i64>;
    type Six<'a> = Vec<&'a i64>;

    #[singleton(name = "ref alias")]
    fn Run3(
        #[di(ref = i8)] one: OneAndTwo<'_>,
        #[di(ref = i8, name = "2")] two: OneAndTwo<'_>,

        #[di(ref = Option<i16>)] three: Three<'_>,
        #[di(ref = i16, option)] four: Four<'_>,

        #[di(ref = i32, default = &0)] zero: ZeroAndFortyTwo<'_>,
        #[di(ref = i32, default = &42)] forty_two: ZeroAndFortyTwo<'_>,

        #[di(ref = Vec<i64>)] five: Five<'_>,
        #[di(ref = i64, vec)] six: Six<'_>,
    ) {
        assert_eq!(one, &1);
        assert_eq!(two, &2);

        assert_eq!(three, &Some(3));
        assert_eq!(four, Some(&4));

        assert_eq!(zero, &0);
        assert_eq!(forty_two, &42);

        assert_eq!(five, &vec![5]);
        assert_eq!(six, vec![&6]);
    }
}

fn main() {
    let mut cx = Context::auto_register();
    cx.resolve::<()>();
    cx.resolve_with_name::<()>("ref");
    cx.resolve_with_name::<()>("ref alias");
}