Skip to main content
Tweeted twitter.com/StackCodeReview/status/1328125661651865601
added 2 characters in body
Source Link
Vlad
  • 132
  • 6
#[cfg(not(feature = "test"))]
mod Bindings {
    // real implementations binding

    // I assume only one implementor per each interface
    // this is not always the case but al least it's good for a simple scenario
    pub type Door = crate::Autocar::DoorMod::DoorImpl;
    pub type Engine = crate::Autocar::EngineMod::EngineImpl;
}

// how can I move these into actual tests files?
#[cfg(all(feature = "test", feature = "test1"))]
mod Bindings {
    // stubs for test1 can be binded here
    pub type Door = crate::Autocar::DoorMod::DoorImpl;
    pub type Engine = crate::Autocar::EngineMod::EngineImpl;
}

#[cfg(all(feature = "test", feature = "test2"))]
mod Bindings {
    // stubs for test2 can be binded here
    pub type Door = crate::Autocar::DoorMod::DoorImpl;
    pub type Engine = crate::Autocar::EngineMod::EngineImpl;
}

// prelude for internal use
mod Usings {
    pub use crate::Bindings::*;
    pub use std::cell::RefCell;
    pub use std::rc::Rc;
    pub type Mrc<T> = Rc<RefCell<T>>; // Mutable Reference Counter
    pub fn Mrc<T>(v: T) -> Mrc<T> {
        Rc::new(RefCell::new(v))
    }
}

fn main() {
    // this code performs constructor injection itself
    // all constructors are called here

    use Autocar::*;
    use Usings::*;

    let engine = Mrc(Engine::new());

    // also we can make factory methods
    let make_door = || -> Door { Door::new(engine.clone()) };

    let doors = vec![make_door(), make_door()];
    let mut car = Car::new(engine, doors);

    // all constructed, now run something
    car.doors[0].open();
}

// now application code
mod Autocar {
    use crate::Usings::*;

    // top-level struct so no interface
    pub struct Car {
        // Since same Engine is used also by a Door too, I have to use Mrc.
        // This may become an issue as once a dependency becomes
        // used by multiple structs I have to change it everywhere to Mrc
        // and insert borrow_mut() everywhere.
        // Which doesn't look like a good design. But no choice. Or?
        pub engine: Mrc<Engine>,

        pub doors: Vec<Door>,
    }

    impl Car {
        pub fn new(engine: Mrc<Engine>, doors: Vec<Door>) -> Car {
            Car { engine, doors }
        }
    }

    // declare Car dependencies:

    // we actually need IDoor so stubs can inherit it and reflect signature changes when refactoring
    pub trait IDoor {
        fn is_opened(&self) -> bool;
        fn open(&mut self);
    }

    pub trait IEngine {
        fn is_running(&self) -> bool;
        fn start(&mut self);
        fn stop(&mut self);
    }

    pub(crate) mod DoorMod {
        use super::*;
        use crate::Usings::*;

        pub struct DoorImpl {
            // I tried to design the code in a way so that DI doesn't prevent optimizations.
            // So I don't use IEngine here or otherwise it becomes dyn implicitly and then
            // no inlining and can't be placed on the stack.
            // But one issue with this approach is that IntelliSense can see
            // all EngineImpl functions even if it implements multiple traits, not just IEngine.
            // But a stub will contain only interface-declared functions
            // so it will be at least checked by the compiler.
            engine: Mrc<Engine>,
        }

        impl IDoor for DoorImpl {
            fn is_opened(&self) -> bool {
                unimplemented!()
            }

            fn open(&mut self) {
                if self.engine.borrow().is_running() {
                    self.engine.borrow_mut().stop();
                }
                println!("opening")
            }
        }

        impl DoorImpl {
            pub fn new(engine: Mrc<Engine>) -> Self {
                DoorImpl { engine }
            }
        }
    }

    pub(crate) mod EngineMod {
        use super::*;
        use crate::Usings::*;

        pub struct EngineImpl;

        impl IEngine for EngineImpl {
            fn is_running(&self) -> bool {
                true
            }

            fn start(&mut self) {
                println!("starting");
            }

            fn stop(&mut self) {
                println!("stopping");
            }
        }

        impl EngineImpl {
            pub fn new() -> Self {
                EngineImpl {}
            }
        }
    }
}
```
#[cfg(not(feature = "test"))]
mod Bindings {
    // real implementations binding

    // I assume only one implementor per each interface
    // this is not always the case but al least it's good for a simple scenario
    pub type Door = crate::Autocar::DoorMod::DoorImpl;
    pub type Engine = crate::Autocar::EngineMod::EngineImpl;
}

// how can I move these into actual tests files?
#[cfg(all(feature = "test", feature = "test1"))]
mod Bindings {
    // stubs for test1 can be binded here
    pub type Door = crate::Autocar::DoorMod::DoorImpl;
    pub type Engine = crate::Autocar::EngineMod::EngineImpl;
}

#[cfg(all(feature = "test", feature = "test2"))]
mod Bindings {
    // stubs for test2 can be binded here
    pub type Door = crate::Autocar::DoorMod::DoorImpl;
    pub type Engine = crate::Autocar::EngineMod::EngineImpl;
}

// prelude for internal use
mod Usings {
    pub use crate::Bindings::*;
    pub use std::cell::RefCell;
    pub use std::rc::Rc;
    pub type Mrc<T> = Rc<RefCell<T>>; // Mutable Reference Counter
    pub fn Mrc<T>(v: T) -> Mrc<T> {
        Rc::new(RefCell::new(v))
    }
}

fn main() {
    // this code performs constructor injection itself
    // all constructors are called here

    use Autocar::*;
    use Usings::*;

    let engine = Mrc(Engine::new());

    // also we can make factory methods
    let make_door = || -> Door { Door::new(engine.clone()) };

    let doors = vec![make_door(), make_door()];
    let mut car = Car::new(engine, doors);

    // all constructed, now run something
    car.doors[0].open();
}

// now application code
mod Autocar {
    use crate::Usings::*;

    // top-level struct so no interface
    pub struct Car {
        // Since same Engine is used also by a Door too, I have to use Mrc.
        // This may become an issue as once a dependency becomes
        // used by multiple structs I have to change it everywhere to Mrc
        // and insert borrow_mut() everywhere.
        // Which doesn't look like a good design. But no choice. Or?
        pub engine: Mrc<Engine>,

        pub doors: Vec<Door>,
    }

    impl Car {
        pub fn new(engine: Mrc<Engine>, doors: Vec<Door>) -> Car {
            Car { engine, doors }
        }
    }

    // declare Car dependencies:

    // we actually need IDoor so stubs can inherit it and reflect signature changes when refactoring
    pub trait IDoor {
        fn is_opened(&self) -> bool;
        fn open(&mut self);
    }

    pub trait IEngine {
        fn is_running(&self) -> bool;
        fn start(&mut self);
        fn stop(&mut self);
    }

    pub(crate) mod DoorMod {
        use super::*;
        use crate::Usings::*;

        pub struct DoorImpl {
            // I tried to design the code in a way so that DI doesn't prevent optimizations.
            // So I don't use IEngine here or otherwise it becomes dyn implicitly and then
            // no inlining and can't be placed on the stack.
            // But one issue with this approach is that IntelliSense can see
            // all EngineImpl functions even if it implements multiple traits, not just IEngine.
            // But a stub will contain only interface-declared functions
            // so it will be at least checked by the compiler.
            engine: Mrc<Engine>,
        }

        impl IDoor for DoorImpl {
            fn is_opened(&self) -> bool {
                unimplemented!()
            }

            fn open(&mut self) {
                if self.engine.borrow().is_running() {
                    self.engine.borrow_mut().stop();
                }
                println!("opening")
            }
        }

        impl DoorImpl {
            pub fn new(engine: Mrc<Engine>) -> Self {
                DoorImpl { engine }
            }
        }
    }

    pub(crate) mod EngineMod {
        use super::*;
        use crate::Usings::*;

        pub struct EngineImpl;

        impl IEngine for EngineImpl {
            fn is_running(&self) -> bool {
                true
            }

            fn start(&mut self) {
                println!("starting");
            }

            fn stop(&mut self) {
                println!("stopping");
            }
        }

        impl EngineImpl {
            pub fn new() -> Self {
                EngineImpl {}
            }
        }
    }
}
```
#[cfg(not(feature = "test"))]
mod Bindings {
    // real implementations binding

    // I assume only one implementor per each interface
    // this is not always the case but al least it's good for a simple scenario
    pub type Door = crate::Autocar::DoorMod::DoorImpl;
    pub type Engine = crate::Autocar::EngineMod::EngineImpl;
}

// how can I move these into actual tests files?
#[cfg(all(feature = "test", feature = "test1"))]
mod Bindings {
    // stubs for test1 can be binded here
    pub type Door = crate::Autocar::DoorMod::DoorImpl;
    pub type Engine = crate::Autocar::EngineMod::EngineImpl;
}

#[cfg(all(feature = "test", feature = "test2"))]
mod Bindings {
    // stubs for test2 can be binded here
    pub type Door = crate::Autocar::DoorMod::DoorImpl;
    pub type Engine = crate::Autocar::EngineMod::EngineImpl;
}

// prelude for internal use
mod Usings {
    pub use crate::Bindings::*;
    pub use std::cell::RefCell;
    pub use std::rc::Rc;
    pub type Mrc<T> = Rc<RefCell<T>>; // Mutable Reference Counter
    pub fn Mrc<T>(v: T) -> Mrc<T> {
        Rc::new(RefCell::new(v))
    }
}

fn main() {
    // this code performs constructor injection itself
    // all constructors are called here

    use Autocar::*;
    use Usings::*;

    let engine = Mrc(Engine::new());

    // also we can make factory methods
    let make_door = || -> Door { Door::new(engine.clone()) };

    let doors = vec![make_door(), make_door()];
    let mut car = Car::new(engine, doors);

    // all constructed, now run something
    car.doors[0].open();
}

// now application code
mod Autocar {
    use crate::Usings::*;

    // top-level struct so no interface
    pub struct Car {
        // Since same Engine is used also by a Door too, I have to use Mrc.
        // This may become an issue as once a dependency becomes
        // used by multiple structs I have to change it everywhere to Mrc
        // and insert borrow_mut() everywhere.
        // Which doesn't look like a good design. But no choice. Or?
        pub engine: Mrc<Engine>,

        pub doors: Vec<Door>,
    }

    impl Car {
        pub fn new(engine: Mrc<Engine>, doors: Vec<Door>) -> Car {
            Car { engine, doors }
        }
    }

    // declare Car dependencies:

    // we actually need IDoor so stubs can inherit it and reflect signature changes when refactoring
    pub trait IDoor {
        fn is_opened(&self) -> bool;
        fn open(&mut self);
    }

    pub trait IEngine {
        fn is_running(&self) -> bool;
        fn start(&mut self);
        fn stop(&mut self);
    }

    pub(crate) mod DoorMod {
        use super::*;
        use crate::Usings::*;

        pub struct DoorImpl {
            // I tried to design the code in a way so that DI doesn't prevent optimizations.
            // So I don't use IEngine here or otherwise it becomes dyn implicitly and then
            // no inlining and can't be placed on the stack.
            // But one issue with this approach is that IntelliSense can see
            // all EngineImpl functions even if it implements multiple traits, not just IEngine.
            // But a stub will contain only interface-declared functions
            // so it will be at least checked by the compiler.
            engine: Mrc<Engine>,
        }

        impl IDoor for DoorImpl {
            fn is_opened(&self) -> bool {
                unimplemented!()
            }

            fn open(&mut self) {
                if self.engine.borrow().is_running() {
                    self.engine.borrow_mut().stop();
                }
                println!("opening")
            }
        }

        impl DoorImpl {
            pub fn new(engine: Mrc<Engine>) -> Self {
                DoorImpl { engine }
            }
        }
    }

    pub(crate) mod EngineMod {
        use super::*;
        use crate::Usings::*;

        pub struct EngineImpl;

        impl IEngine for EngineImpl {
            fn is_running(&self) -> bool {
                true
            }

            fn start(&mut self) {
                println!("starting");
            }

            fn stop(&mut self) {
                println!("stopping");
            }
        }

        impl EngineImpl {
            pub fn new() -> Self {
                EngineImpl {}
            }
        }
    }
}
Source Link
Vlad
  • 132
  • 6

Simple constructor DI implementation in Rust

From my experience in C# programming I think that DI is important. But it's not possible to do it same way in Rust. There are some DI frameworks but I've come with an idea on how it can be made and decided to implement it my way. My implementation uses only constructor injection, no container needed. I'm only learning Rust now so can you review my code, find possible pitfalls, suggest improvements or better approaches?

My goals in DI:

  1. Make stubs possible for testing.
  2. Systems shouldn't know "sub-dependencies" of their dependencies. Separate construction and providing dependencies from actual usage. All dependencies are "wired" in one place.
  3. No global shared state.
  4. The design principle that higher level systems declare what they need and lower level systems implement it, not other way.
  5. Avoid God objects.
  6. All dependencies can be clearly determined by a constructor signature, no looking inside code needed. Therefore I don't use Resource Locator pattern.
#[cfg(not(feature = "test"))]
mod Bindings {
    // real implementations binding

    // I assume only one implementor per each interface
    // this is not always the case but al least it's good for a simple scenario
    pub type Door = crate::Autocar::DoorMod::DoorImpl;
    pub type Engine = crate::Autocar::EngineMod::EngineImpl;
}

// how can I move these into actual tests files?
#[cfg(all(feature = "test", feature = "test1"))]
mod Bindings {
    // stubs for test1 can be binded here
    pub type Door = crate::Autocar::DoorMod::DoorImpl;
    pub type Engine = crate::Autocar::EngineMod::EngineImpl;
}

#[cfg(all(feature = "test", feature = "test2"))]
mod Bindings {
    // stubs for test2 can be binded here
    pub type Door = crate::Autocar::DoorMod::DoorImpl;
    pub type Engine = crate::Autocar::EngineMod::EngineImpl;
}

// prelude for internal use
mod Usings {
    pub use crate::Bindings::*;
    pub use std::cell::RefCell;
    pub use std::rc::Rc;
    pub type Mrc<T> = Rc<RefCell<T>>; // Mutable Reference Counter
    pub fn Mrc<T>(v: T) -> Mrc<T> {
        Rc::new(RefCell::new(v))
    }
}

fn main() {
    // this code performs constructor injection itself
    // all constructors are called here

    use Autocar::*;
    use Usings::*;

    let engine = Mrc(Engine::new());

    // also we can make factory methods
    let make_door = || -> Door { Door::new(engine.clone()) };

    let doors = vec![make_door(), make_door()];
    let mut car = Car::new(engine, doors);

    // all constructed, now run something
    car.doors[0].open();
}

// now application code
mod Autocar {
    use crate::Usings::*;

    // top-level struct so no interface
    pub struct Car {
        // Since same Engine is used also by a Door too, I have to use Mrc.
        // This may become an issue as once a dependency becomes
        // used by multiple structs I have to change it everywhere to Mrc
        // and insert borrow_mut() everywhere.
        // Which doesn't look like a good design. But no choice. Or?
        pub engine: Mrc<Engine>,

        pub doors: Vec<Door>,
    }

    impl Car {
        pub fn new(engine: Mrc<Engine>, doors: Vec<Door>) -> Car {
            Car { engine, doors }
        }
    }

    // declare Car dependencies:

    // we actually need IDoor so stubs can inherit it and reflect signature changes when refactoring
    pub trait IDoor {
        fn is_opened(&self) -> bool;
        fn open(&mut self);
    }

    pub trait IEngine {
        fn is_running(&self) -> bool;
        fn start(&mut self);
        fn stop(&mut self);
    }

    pub(crate) mod DoorMod {
        use super::*;
        use crate::Usings::*;

        pub struct DoorImpl {
            // I tried to design the code in a way so that DI doesn't prevent optimizations.
            // So I don't use IEngine here or otherwise it becomes dyn implicitly and then
            // no inlining and can't be placed on the stack.
            // But one issue with this approach is that IntelliSense can see
            // all EngineImpl functions even if it implements multiple traits, not just IEngine.
            // But a stub will contain only interface-declared functions
            // so it will be at least checked by the compiler.
            engine: Mrc<Engine>,
        }

        impl IDoor for DoorImpl {
            fn is_opened(&self) -> bool {
                unimplemented!()
            }

            fn open(&mut self) {
                if self.engine.borrow().is_running() {
                    self.engine.borrow_mut().stop();
                }
                println!("opening")
            }
        }

        impl DoorImpl {
            pub fn new(engine: Mrc<Engine>) -> Self {
                DoorImpl { engine }
            }
        }
    }

    pub(crate) mod EngineMod {
        use super::*;
        use crate::Usings::*;

        pub struct EngineImpl;

        impl IEngine for EngineImpl {
            fn is_running(&self) -> bool {
                true
            }

            fn start(&mut self) {
                println!("starting");
            }

            fn stop(&mut self) {
                println!("stopping");
            }
        }

        impl EngineImpl {
            pub fn new() -> Self {
                EngineImpl {}
            }
        }
    }
}
```