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//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@latestThen 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 failedWire 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 Greeter — Event. 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 failedDelete 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.