Course contentsShow
← Rust
Lesson 151 of 1,9102. The Ownership ModelFree lesson

Ownership-Based Dependency Injection

Design modular systems where ownership of dependencies is explicitly passed to components.

In this lesson, you will learn how to build modular systems by using Rust’s ownership model to pass dependencies directly into your components.

The Concept: Dependency Injection via Ownership

In many languages, dependency injection involves passing shared references or pointers around, which can make it difficult to track who owns what. In Rust, we can make our code safer and more modular by transferring ownership. When a component needs a dependency to function, we simply pass that dependency into its constructor (or "init" function). By moving the object, we explicitly hand over the responsibility of managing that resource to the new component.

Practical Illustration

Imagine a Logger component and a Database component. Instead of the Database reaching out to a global logger, we pass the logger into the database when it is created.

struct Logger {
    prefix: String,
}

struct Database {
    logger: Logger,
}

impl Database {
    // The Database takes full ownership of the Logger.
    // Once passed here, the caller no longer has access to this logger.
    fn new(logger: Logger) -> Self {
        Database { logger }
    }
}

fn main() {
    let my_logger = Logger { prefix: String::from("DB_LOG:") };
    
    // Ownership of 'my_logger' is moved into the Database.
    let db = Database::new(my_logger);
}

Why This Matters

When we use ownership-based injection, we gain three distinct advantages:

  1. Clear Lifecycle Management: Because the Database owns the Logger, the logger will be dropped exactly when the database is dropped. You don't have to worry about dangling references or complex cleanup.
  2. Explicit Boundaries: By looking at the new function signature, you know exactly what a component requires to exist. If you can’t provide the required dependency, the compiler prevents you from creating the component.
  3. Encapsulation: Because ownership is moved, no other part of your program can accidentally modify or drop the dependency while the component is using it. It creates a "single source of truth" for the dependency's existence.

By moving dependencies rather than sharing them, you force your application architecture to be more intentional about how components relate to one another.

Key Takeaway: Ownership-based dependency injection leverages the compiler to guarantee that every component exclusively owns its dependencies, ensuring clear lifecycles and thread-safe modularity.