rdi

package module
v1.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 4, 2026 License: MIT Imports: 2 Imported by: 2

README

rDI – Dependency Injection for Go 🥷

rDI Mascot rDI is a lightweight and intuitive dependency injection container for Go. It is designed to be flexible, testable, and unobtrusive, letting you wire up your dependencies cleanly and explicitly.

Installation

$ go get -u github.com/rathil/rdi

Then import it:

import "github.com/rathil/rdi"

Quick start

Custom functionality
type IUser interface{}
type User struct{}

func NewIUser() (IUser, error) {
    return &User{}, nil
}
func NewUserPoint() (*User, error) {
    return &User{}, nil
}
func NewUser() User {
    return User{}
}

type IDevice interface{}
type Device struct{
    User *User
}

var sameErrorDevice = fmt.Errorf(`same device error`)

func NewDevicePoint(user *User, sameVar int) (*Device, error) {
    if sameVar > 5 {
        return nil, sameErrorDevice
    }
    return &Device{user}, nil
}
func NewDevice(user *User) (Device, error) {
    return Device{user}, nil
}
func NewIDevicePoint(user *User) (IDevice, error) {
    return &Device{user}, nil
}
Initializing a container
di := standard.New()
Provide (*User, User) dependency
user := NewUserPoint()
// ...
if err := di.Provide(user); err != nil {
    panic(err)
}

Or using a constructor directly:

if err := di.Provide(NewUserPoint); err != nil {
    panic(err)
}

Or with MustProvide:

di.MustProvide(NewIUser)
di.MustProvide(NewUserPoint)
di.MustProvide(NewUser)
Incorrect use of IUser interface type
user := NewIUser()
// ...
di.MustProvide(user) // Will register *User instead of IUser
Correct way to provide IUser interface
di.MustProvide(NewIUser)
Invoking (IUser, *User, User) dependencies
if err := di.Invoke(func(user *User) {
    // ...
}); err != nil {
    panic(err)
}

With error handling:

var someError = fmt.Errorf(`some error`)
// ...
if err := di.Invoke(func(user *User) error {
    // ...
    if varName > 5 {
        return someError
    }
    return nil
}); err != nil {
    if !errors.Is(err, someError) {
        panic(err)
    }
}

Chaining multiple invocations:

di.
	MustInvoke(func(user IUser) {
        // ...
    }).
	MustInvoke(func(user *User) {
        // ...
    }).
	MustInvoke(func(user User) {
        // ...
    })
Auto-initializing structs with Wire

Wire[T] asks the container to create T and fill its fields from registered dependencies. It is useful when you want to receive a fully initialized struct without registering a constructor for that struct.

T can be either a struct type or a pointer to a struct type. Fields tagged with di:"-" are skipped.

type Config struct {
    Name string
}
type App struct {
    Config Config
    Port   int
    Secret string `di:"-"`
}

di := standard.New().
    MustProvide(Config{Name: "example"}).
    MustProvide(8080)

di.MustInvoke(func (app rdi.Wire[App]) {
    fmt.Println(app.Value.Config.Name) // example
    fmt.Println(app.Value.Port) // 8080
    fmt.Println(app.Value.Secret) // empty, skipped by tag di:"-"
})

If you need a pointer, use Wire[*T]:

di.MustInvoke(func(app rdi.Wire[*App]) {
    app.Value.Port = 9090
})
Provider Options

By default, providers are singleton and cached after first creation:

di := standard.New().
    MustProvide(func() context.Context { // Will be called once
        return context.Background()
    })

di.MustInvoke(func(c context.Context) {})
di.MustInvoke(func(c context.Context) {})

To get a new instance on each request, use WithTransient():

di := standard.New().
    MustProvide(func() context.Context { // It will be called every time when the context is requested
        return context.Background()
    }, rdi.WithTransient())

di.MustInvoke(func(c context.Context) {})
di.MustInvoke(func(c context.Context) {})
Container Copying and Overrides

You can create a child container to override or extend dependencies:

di := standard.New().
    MustProvide(22).
    MustProvide(NewUserPoint)

di.MustInvoke(func(data int) { /* data == 22 */ })

diCopy := standard.NewWithParent(di).
    MustProvide(NewDevicePoint)

diCopy.MustInvoke(func(data int) { /* data == 22 */ })
err := diCopy.Invoke(func(device *Device) {
    // ...
})
// err == sameErrorDevice if input int is > 5

diCopyOfCopy := standard.NewWithParent(diCopy).
    MustProvide(3)

diCopyOfCopy.MustInvoke(func(data int) { /* data == 3 */ })
diCopyOfCopy.MustInvoke(func(device *Device) {
    // ...
})

Or you can use the integrated override functionality:

standard.New().
    MustProvide(22).
    MustOverride(15).
    MustInvoke(func(data int) { /* data == 15 */ })

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrDependencyAlreadyExists = errors.New("dependency is already registered")
	ErrProviderWithoutOutputs  = errors.New("provider function has no return types")
	ErrInvalidValueProvided    = errors.New("cannot provide an invalid value")
	ErrNilValueProvided        = errors.New("cannot provide a nil value")
	ErrNotAFunction            = errors.New("expected a function, got a non-function value for invocation")
	ErrDependencyNotFound      = errors.New("missing")
	ErrCyclicDependency        = errors.New("cyclic dependency detected")
	ErrWireValueNotStruct      = errors.New("wire value must be a struct or pointer to struct")
)

Functions

func Get

func Get[T any](di DI) (T, error)

Get a single dependency

func MustGet

func MustGet[T any](di DI) T

MustGet is like Get but panics if an error has occurred

Types

type DI

type DI interface {
	// Override tries to register the given provider in the current container.
	// If registration fails (e.g., due to a conflict), it creates a new child container,
	// registers the provider there, and returns it.
	// If registration succeeds, the current container is returned.
	Override(provide any, options ...Option) (DI, error)
	// MustOverride is like Override, but panics if provider registration fails in both the current and fallback container.
	// Use it when failure to override should be treated as a fatal error.
	MustOverride(provide any, options ...Option) DI
	// Provide registers a new dependency provider in the container.
	// It returns an error if the given value is not a valid provider function or type.
	Provide(provider any, options ...Option) error
	// MustProvide is like Provide, but panics if an error occurs.
	// Use it when failure to register a provider should be considered a fatal error.
	MustProvide(provider any, options ...Option) DI
	// Invoke resolves and calls the given function using dependencies from the container.
	// Returns an error if any dependency is missing or cannot be constructed.
	Invoke(functions ...any) error
	// MustInvoke is like Invoke, but panics if an error occurs during resolution or invocation.
	// Use it when failing to invoke the function should stop execution immediately.
	MustInvoke(functions ...any) DI
	// InvokeWithDI resolves top-level dependencies using the current container,
	// while resolving their nested dependencies using the provided container.
	// Useful when composing containers or overriding lower-level dependencies.
	InvokeWithDI(di DI, functions ...any) error
}

type Option

type Option func(option)

func WithTransient

func WithTransient() Option

WithTransient marks the dependency as transient (non-singleton). This means a new instance will be created each time it is requested from the container.

type Wire added in v1.3.0

type Wire[T any] struct {
	Value T
	// contains filtered or unexported fields
}

Wire requests automatic construction and field injection for T.

Invoke, MustInvoke, and InvokeWithDI recognize this wrapper by its private marker field and pass the initialized value through Value. T must be either a struct type or a pointer to a struct type.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL