Skip to content
Getting started

Getting started

Let’s learn Wire by example. We’ll build a small greeter program and follow the whole workflow: write providers, write an injector, run wire, and look at the generated code. If you’d rather read the rules first, jump to the user guide.

The finished program lives in the _tutorial/ directory of the repository.

A first pass at the greeter program

We want to simulate an event where a greeter welcomes guests with a particular message. That’s three types: a message, a greeter that carries it, and an event that starts with the greeter greeting guests.

type Message string

type Greeter struct {
    // ... TBD
}

type Event struct {
    // ... TBD
}

Message just wraps a string, so the initializer can return a hard-coded one for now:

func NewMessage() Message {
    return Message("Hi there!")
}

Greeter needs a reference to the Message, so give it an initializer too:

func NewGreeter(m Message) Greeter {
    return Greeter{Message: m}
}

type Greeter struct {
    Message Message // <- adding a Message field
}

The initializer assigns the Message field, which lets us add a Greet method:

func (g Greeter) Greet() Message {
    return g.Message
}

Next, Event needs a Greeter:

func NewEvent(g Greeter) Event {
    return Event{Greeter: g}
}

type Event struct {
    Greeter Greeter // <- adding a Greeter field
}

func (e Event) Start() {
    msg := e.Greeter.Greet()
    fmt.Println(msg)
}

Start holds the core of the program: it asks the greeter for a greeting and prints it.

Now that every component is ready, here is what initialization looks like without Wire:

func main() {
    message := NewMessage()
    greeter := NewGreeter(message)
    event := NewEvent(greeter)

    event.Start()
}

Create a message, create a greeter with it, create an event with the greeter, start the event. That’s dependency injection: each component is handed whatever it needs. Code written this way is easy to test and makes it easy to swap one dependency for another.

Using Wire to generate code

The downside of dependency injection is all those initialization steps. Change main to this:

func main() {
    e := InitializeEvent()

    e.Start()
}

Then, in a separate file called wire.go, declare InitializeEvent:

// wire.go

func InitializeEvent() Event {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}
}

Instead of constructing each component in turn and passing it along, there’s a single wire.Build call listing the initializers to use. In Wire terminology these are providers — functions that provide a particular type. The Event zero value at the end exists only to satisfy the compiler; Wire ignores it.

The injector’s only job is to say which providers to use, so this file must not end up in the final build. That’s what the build constraint is for:

//go:build wireinject
// +build wireinject

package main
A build constraint requires a blank line after it, otherwise it is just a comment. Both forms are shown above: //go:build for current Go versions and // +build for older ones.

InitializeEvent is called an injector. With the injector written, install the command line tool:

go install github.com/wireinject/wire/cmd/wire@latest

Then run wire in the same directory. It finds the InitializeEvent injector and writes the function body to wire_gen.go:

// wire_gen.go

func InitializeEvent() Event {
    message := NewMessage()
    greeter := NewGreeter(message)
    event := NewEvent(greeter)
    return event
}

It looks just like what we wrote by hand. With three components that’s no great victory; with thirty it is.

Commit both wire.go and wire_gen.go to source control.

Making changes: a provider that fails

To see how Wire handles something more involved, make NewEvent able to fail:

func NewEvent(g Greeter) (Event, error) {
    if g.Grumpy {
        return Event{}, errors.New("could not create event: event greeter is grumpy")
    }
    return Event{Greeter: g}, nil
}

Say a greeter is sometimes grumpy, and a grumpy greeter can’t host an event. The NewGreeter initializer becomes:

func NewGreeter(m Message) Greeter {
    var grumpy bool
    if time.Now().Unix()%2 == 0 {
        grumpy = true
    }
    return Greeter{Message: m, Grumpy: grumpy}
}

We added a Grumpy field: if the initializer runs on an even number of seconds since the Unix epoch, it builds a grumpy greeter instead of a friendly one.

Greet then becomes:

func (g Greeter) Greet() Message {
    if g.Grumpy {
        return Message("Go away!")
    }
    return g.Message
}

A grumpy greeter is no good for an event, so NewEvent may fail and main has to deal with that:

func main() {
    e, err := InitializeEvent()
    if err != nil {
        fmt.Printf("failed to create event: %s\n", err)
        os.Exit(2)
    }
    e.Start()
}

InitializeEvent needs the error return value too:

// wire.go

func InitializeEvent() (Event, error) {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}, nil
}

Run wire again — and once wire_gen.go exists, go generate works too. The generated code now looks like this:

// wire_gen.go

func InitializeEvent() (Event, error) {
    message := NewMessage()
    greeter := NewGreeter(message)
    event, err := NewEvent(greeter)
    if err != nil {
        return Event{}, err
    }
    return event, nil
}

Wire noticed that NewEvent can fail and did the right thing: check the error, return early if it is set.

The injector signature drives generation

The message is still hard-coded inside NewMessage. In practice the caller should decide it, so change InitializeEvent to take the phrase:

func InitializeEvent(phrase string) (Event, error) {
    wire.Build(NewEvent, NewGreeter, NewMessage)
    return Event{}, nil
}

and add the argument to NewMessage:

func NewMessage(phrase string) Message {
    return Message(phrase)
}

Run wire again and the generated initializer threads the phrase through:

// wire_gen.go

func InitializeEvent(phrase string) (Event, error) {
    message := NewMessage(phrase)
    greeter := NewGreeter(message)
    event, err := NewEvent(greeter)
    if err != nil {
        return Event{}, err
    }
    return event, nil
}

Wire inspected the injector’s arguments, saw a new string, noticed that NewMessage takes a string, and passed it in.

Helpful errors when you make a mistake

Now let’s forget the provider for Greeter on purpose:

func InitializeEvent(phrase string) (Event, error) {
    wire.Build(NewEvent, NewMessage) // woops! We forgot to add a provider for Greeter
    return Event{}, nil
}

Running wire reports:

# wrapping the error across lines for readability
.../_tutorial/wire.go:24:1:
inject InitializeEvent: no provider found for github.com/wireinject/wire/_tutorial.Greeter
(required by provider of github.com/wireinject/wire/_tutorial.Event)
wire: generate failed

Wire can’t find a provider for Greeter, and it says so along with the exact place: line 24, inside InitializeEvent. It also names the provider that needs the GreeterEvent. Add the provider and the problem goes away.

What if there are too many providers instead?

func NewEventNumber() int  {
    return 1
}

func InitializeEvent(phrase string) (Event, error) {
     // woops! NewEventNumber is unused.
    wire.Build(NewEvent, NewGreeter, NewMessage, NewEventNumber)
    return Event{}, nil
}

Wire reports the unused provider:

.../_tutorial/wire.go:24:1:
inject InitializeEvent: unused provider "NewEventNumber"
wire: generate failed

Delete it from the wire.Build call and the error is gone.

Conclusion

To recap: we wrote components with their initializers, or providers. We declared an injector, specifying its arguments and return types, and filled its body with a wire.Build call listing the providers it needs. Then we ran wire to generate code that wires all the initializers together. When we added an argument and an error return, running wire again brought the generated code up to date.

The example is small, but it shows the point: Wire takes most of the pain out of initializing code with dependency injection, and what comes out is ordinary Go code. There are no bespoke types tying you to Wire — just generated code you can do whatever you like with.

Wire has more to offer than this tutorial covers: providers can be grouped into provider sets, and there is support for interface binding, value binding and cleanup functions. See the user guide for the rest.