{
"$type": "site.standard.document",
"canonicalUrl": "https://rednafi.com/go/configure-options/",
"description": "Compare three Go option patterns: exposed structs, option constructors, and functional options. Learn when to use each for clean APIs.",
"path": "/go/configure-options/",
"publishedAt": "2023-09-05T00:00:00.000Z",
"site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
"tags": [
"Go",
"API",
"Design Patterns"
],
"textContent": "Suppose, you have a function that takes an option struct and a message as input. Then it\nstylizes the message according to the option fields and prints it. What's the most sensible\nAPI you can offer for users to configure your function? Observe:\n\nIn the src package, the function Display takes a pointer to a Style instance and a\nmsg string as parameters. Then it decorates the msg and prints it according to the style\nspecified in the option struct. In the wild, I've seen 3 main ways to write APIs that let\nusers configure options:\n\n- Expose the option struct directly\n- Use the option constructor pattern\n- Apply functional option constructor pattern\n\nEach comes with its own pros and cons.\n\nExpose the option struct\n\nIn this case, you'd export the Style struct with all its fields and let the user configure\nthem directly. The previous snippet already made the struct and fields public. From another\npackage, you could import the src package and instantiate Style like this:\n\nTo configure option fields, mutate the values in place:\n\nThis works but will break users' code if new fields are added to the option struct. But your\nusers can instantiate the struct with named parameters to avoid breakage:\n\nIn this case, the field that wasn't passed would assume the corresponding zero value. For\ninstance, Bg will be initialized as an empty string. However, this pattern puts the\nresponsibility of retaining API compatibility on the users' shoulders. So if your code is\nmeant for external use, there are better ways to achieve option configurability.\n\nOption constructor\n\nGo standard library extensively uses this pattern. Instead of letting the users instantiate\nStyle directly, you expose a NewStyle constructor function that constructs the struct\ninstance for them:\n\nIt'll be used as follows:\n\nIf a new field is added to Style, update NewStyle to have a sensible default value for\nit or initialize the struct with named parameters to set the optional fields to their\nrespective zero values. This avoids breaking users' code as long as the constructor\nfunction's signature doesn't change.\n\nIn NewStyle, we implicitly set the value of Und to false but you can be explicit there\ndepending on your needs. The struct fields can be updated in the same manner as before:\n\nThis should cover most use cases. However, if you don't want to export the underlying option\nstruct, or your struct has tons of optional fields requiring extensibility, you'll need an\nextra layer of indirection to avoid the need to accept a zillion config parameters in your\noption constructor.\n\nFunctional option constructor\n\nAs mentioned at the tail of the last section, this approach works better when your struct\ncontains many optional fields and you need your users to be able to configure them if they\nwant. Go doesn't allow setting non-zero default values for struct fields. So an extra level\nof indirection is necessary to let the users configure them. This approach also allows us to\nmake the option struct private so that there's no ambiguity around API usage.\n\nLet's say style now has two optional fields und and zigzag that allows users to\ndecorate the message string with underlines or zigzagged lines:\n\nNow, we'll define a new type called styleoption like this:\n\nThe styleoption function accepts a pointer to the option struct and updates a particular\nfield with a user-provided value. The implementation of this type would look as such:\n\nNext, we'll need to define a higher order config function for each optional field in the\nstruct where the function will accept the field value and return another function with the\nstyleoption signature. The WithUnd and WithZigzag wrapper functions will be a part of\nthe public API that the users will use to configure style:\n\nFinally, our option constructor function needs to be updated to accept variadic options.\nObserve how we're looping through the options slice and applying the field config\nfunctions to the struct pointer:\n\nThe users will use the code like this to instantiate style and update the optional fields:\n\nThe required fields fg and bg must be passed while constructing the option struct. The\noptional fields can be configured with the field config functions like WithUnd and\nWithZigzag.\n\nThe complete snippet looks as follows:\n\nI first came across this pattern in [Rob Pike's post on self-referential functions].\n\nVerdict\n\nWhile the functional constructor pattern is the most intriguing one among the three, I\nalmost never reach for it unless I need my users to be able to configure large option\nstructs with many optional fields. It's rare and the extra indirection makes the code\ninscrutable. Also, it renders the IDE suggestions useless.\n\nIn most cases, you can get away with exporting the option struct Stuff and a companion\nfunction NewStuff to instantiate it. For another canonical example, see bufio.Read and\nbufio.NewReader in the standard library.\n\nFurther reading\n\n- [Functional options for friendly APIs - Dave Cheney]\n- [Functional options pattern in Go - Matt Boyle]\n\n\n\n\n[rob pike's post on self-referential functions]:\n https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html\n\n[functional options for friendly apis - dave cheney]:\n https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis\n\n[functional options pattern in go - matt boyle]:\n https://twitter.com/MattJamesBoyle/status/1698605808517288428",
"title": "Configuring options in Go"
}