Introduction
Go is a relatively new language, but it’s worth exploring. It was designed to have a simple syntax while maintaining the speed of a programming language like C or C++.
In this article, we will be looking at a software engineering technique found in most programming languages and unsurprisingly in Go as well: dependency injection.
What is dependency injection?
Dependency injection is a software engineering technique where an object or struct receives its dependencies at compile time. Wikipedia defines dependency injection as such:
Dependency injection is a technique in which an object receives other objects that it depends on, called dependencies. Typically, the receiving object is called a client and the passed-in (‘injected’) object is called a service.
To get a better view of this, let’s analyze an example. Take a look at the following code:
package main import ( "fmt" ) type Message string type Greeter struct { Message Message } type Event struct { Greeter Greeter } func GetMessage() Message { return Message("Hello world!") } func GetGreeter(m Message) Greeter { return Greeter{Message: m} } func (g Greeter) Greet() Message { return g.Message } func GetEvent(g Greeter) Event { return Event{Greeter: g} } func (e Event) Start() { msg := e.Greeter.Greet() fmt.Println(msg) } func main() { message := GetMessage() greeter := GetGreeter(message) event := GetEvent(greeter) event.Start() }
If you take a look at the code above, we have a message, a greeter, and an event. There is also a GetMessage
function that returns a message; a GetGreeter
function that takes in a message and returns a greeter; and a GetEvent function
that accepts a greeter and returns an event. The event also has a method called Start
that prints out the message.
If you take a look at our main
method, we first create a message, then we pass in the message as a dependency to the greeter and finally pass that to the event. Run the code by running the command go run .
in the terminal.
As you can see, it prints “Hello, world!” to the console. This is a very shallow dependency graph, but you can already see the complexity that comes with this when implementing this in a large codebase. That’s where dependency injection tools like Wire come in.
What is Wire?
Wire is a code dependency tool that operates without runtime state or reflection. Code written to be used with Wire is useful even for handwritten initialization.
Wire can generate source code at compile time as well as implement dependency injection. According to the official documentation, “In Wire, dependencies between components are represented as function parameters, encouraging explicit initialization instead of global variables.”
How to install Wire
To use Wire, first, you need to initialize Go modules in your current working directory. Run the command go mod init go-wire
to do this.
Now, run the command go get github.com/google/wire/cmd/wire
to install.
Now, let’s refactor our code to use Wire as a dependency injection tool. Create a file called wire.py
and add the following code:
package main import "github.com/google/wire" func InitializeEvent() Event { wire.Build(GetMessage, GetGreeter, GetEvent) return Event{} }
First of all, we import Wire, then we create a function called InitializeEvent
. This function returns an event that we will use in our main
method. In the InitializeEvent
function, we make a call to Wire. Then we build and pass in all our dependencies. Note we can pass in these dependencies in any order.
Then we return an empty event. Don’t worry, Wire will take over here!
Now, change your main
method to this:
func main() { event := InitializeEvent() event.Start() }
Notice how we have successfully cut down the code in our main method to just two lines.
Run the command go run github.com/google/wire/cmd/wire
to generate our dependencies with Wire.
Now you will see that Wire has generated a file called wire_gen
.
If you should run the code again, you will get an error.
This is because our InitializeEvent
function has now been redeclared in the wire_gen
file. Add
//+build wireinject
to the beginning of your wire.go
file to tell Go to ignore it when building. Make sure to add a new line after that or this will not work.
If you run go run .
again, you should still see the same “Hello, world!” output.
Working with arguments
What if you wanted to dynamically pass in a message as an argument? Let’s take a look at how we can do this. Modify the GetMessage
function to this:
func GetMessage(text string) Message { return Message(text) }
Now we have to pass in a text to display. Let’s try running this and see the output.
As you can see, Wire recognizes that we have to pass in an argument to the GetMessage
function. Let’s resolve this error. Modify your InitializeEvent
function in your wire.go
file:
func InitializeEvent(string) Event { wire.Build(GetMessage, GetGreeter, GetEvent) return Event{} }
Now we are telling Wire that we expect a string argument.
Run go run github.com/google/wire/cmd/wire
again. If you take a look at our wire_gen.go
file, you will see that Wire has refactored the code to accept this value.
Pass in the string in your main
method:
func main() { event := InitializeEvent("Hello People!") event.Start() }
Run go run .
again and you should see the text printed out.
Error handling in Wire
What if we forget to pass in any initializer? Let’s see how Wire handles this.
In your wire.go
file, omit the GetEvent
argument and run go run github.com/google/wire/cmd/wire
again:
func InitializeEvent(string) Event { wire.Build(GetMessage, GetEvent) return Event{} }
As you can see, Wire detects that we have failed to pass in an argument and prints out a helpful output telling us which argument we omitted. The same thing happens when we pass in more than the required number of arguments.
Conclusion
In this article, we have covered the basic features of Wire. We have seen how we can use it for dynamic dependency injection in our little example above, but the true power of Wire comes out when we have a large codebase. If you want to learn more advanced features, you can check out the documentation here.
LogRocket: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Try it for free.
Why the filename is wire.py not wire.go