Golang Technique: How to Add Default Values to Function Parameters?
Are you one of the many coders who find it annoying that Go doesn't have default parameter values? If that's the case, you're far from being the only one.
Have you ever wished that Go had default parameter values built in?
I know I have.
You end up writing extra lines just to see if a value is given and then set a default yourself if it’s not.
It’s not a big issue, but it does make coding a bit more work than it needs to be. The upside is there are ways to deal with this that are okay. They’re not as clean as built-in options, but they do the job well enough.
By the way, you might also want to check out my post on custom struct tags in Go. Another small way to make your Go coding a touch easier. We should take whatever small improvements we can to make Go coding more straightforward, right?
1. Simple wrapper
One approach is to create a separate function that calls the original one, filling in default values for any missing parameters.
For instance, if no name is provided, it defaults to “Aiden.” Here’s how you can do it with a wrapper function:
func greet(name string) string {
return "Hello, " + name
}
func greetWithDefaultAiden(name string) string {
if name == "" {
name = "Aiden"
}
return greet(name)
}
// you can have more than 1 default set
func greetWithDefaultJohn(name string) string {
if name == "" {
name = "John"
}
return greet(name)
}
Doing it this way lets you set a default for the ‘greet’ function without changing its internal code.
“Seems like overkill for a simple function?”
Yes, this method has its downsides. It means more code to write and it can make your code harder to follow.
2. Hide your argument
You can put your function’s arguments into a struct that isn’t exported, allowing the client to set up the arguments however they like.
Take a look:
type argsForGreeting struct {
Name string
Age int
}
func NewArgsForGreeting() argsForGreeting {
return argsForGreeting{
Name: "Aiden",
Age: 30,
}
}
Now let’s define our Greet function:
func Greet(args argsForGreeting) string {
return "Hello, my name is " + args.Name + " and I am " + strconv.Itoa(args.Age) + " years old."
}
So, if a client wants to use the Greet function, they need to use the NewArgsForGreeting
function first to make an argsForGreeting
struct.
“Isn’t this limiting if I’m within the same package?”
You're right, this approach really only works for calls that come from outside the package, but within same package, someone can use Greet
without using our 'NewsArgsForGreeting
'
Or, you could go a different route and use the functional options pattern. This approach lets you pass a bunch of options as arguments to a function, also it’s a bit more flexible and can make your code easier to read.
But keep in mind, it might also make things a little more complicated.
3. Functional options pattern
This pattern is pretty common in a lot of libraries and in this part, I’ll guide you through how to set it up, step by step:
First, let’s make a struct to keep our arguments, which has two fields:
Name
andAge
.
type GreetingOptions struct {
Name string
Age int
}
2. Next, we shape the greet
function to accept this new struct as an argument while keeping it un-exported:
func greet(options GreetingOptions) string {
return "Hello, my name is " + options.Name + " and I am " + strconv.Itoa(options.Age) + " years old."
}
3. Here comes the fun part, we create functional options for each of the struct’s fields:
type GreetingOption func(*GreetingOptions)
func WithName(name string) GreetingOption {
return func(o *GreetingOptions) {
o.Name = name
}
}
func WithAge(age int) GreetingOption {
return func(o *GreetingOptions) {
o.Age = age
}
}
4. Now, let’s make a wrapper function that uses these new GreetingOption
:
func Greeting(options ...GreetingOption) string {
opts := GreetingOptions{
Name: "Aiden",
Age: 30,
}
for _, o := range options {
o(&opts)
}
return greet(opts)
}
What this Greeting
function does is set the initial default values for the Name ("Aiden") and Age (30), then applies any options you pass in as arguments.
Finally, it calls the Greet
function with the modified struct as a parameter.
To wrap it up, you use it by calling Greeting
and passing in any options you want to change:
greeting := Greeting(WithName("Alice"), WithAge(20))
// "Hello, my name is Alice and I am 20 years old."
it’s widely used in a bunch of libraries like mongodb, aws-sdk-go, gorm, and cli, to name a few.
By the way, you can use this pattern in different scenarios where you offer a feature and allow users to tweak its settings.
Nice write-up. There is something I don't quite get: the Age and Name field are exported, so what prevents me from directly setting values without using the GreetingOption mechanism?