Go builder generator with compile-time required field enforcement

I built a small Go CLI tool called papa-carlo:

https://github.com/AlexanderYAPPO/go-papa-carlo

The idea is simple: Go structs don’t have a built-in notion of “required fields”. Whenever a new field that is required from a business/product perspective is added, you have to manually update every place where the struct is constructed. This is easy to miss and can lead to bugs.

Constructors can help, but they don’t scale well as the number of fields grows.

This tool generates builders where required fields must be provided before Build() is available, so missing required fields become compile-time errors.

Example:

user := NewUserBuilder().
    WithName("John").
    WithAge(22).
    Build()

If Age is required and you skip it, the code won’t compile.

I wrote a short post about the motivation here: https://yappo.cc/posts/2026-02-16-papa-carlo/

Curious whether people here think this approach is useful in real Go codebases.

Moin,
the way I do it is to use a constructor.
This will print errors as shown below, so you only have to change the struct and the constructor once you make a change.

For the null value “issue” that comes along with it - causes it, as you can not define behaviours of stuff that does not exist in that language.

I use pointers, or nested key with Boolean Valid as the same pattern is used in databases ↔ Go as shown here.

And then there are lots of cases where the default value is always ok.

Remember: This is not overhead in Go compared to Python. It might look like overhead, but in truth other languages have this stuff not visible to the user for everything (even if it might not be needed for the app - leading to slow code)

So here is a possible example:

package main

import "fmt"

type NameInfo struct {
	Value string
	Valid bool
}

type Vertex struct {
	X, Y int
	Name NameInfo
}

func main() {
	a := NewVertex(1, 0) // not enough arguments in call to NewVertex
	// have (number, number)
	// want (int, int, string)compilerWrongArgCount
	fmt.Println(a)
}

func NewVertex(x int, y int, name string) Vertex {
	return Vertex{
		X:    x,
		Y:    y,
		Name: NameInfo{Value: name, Valid: true},
	}
}```
2 Likes

A few thoughts. First off this:

  • papa-carlo allows placing builders in packages different from the struct’s package. When it happens, the builder cannot work with fields that are private or that use unexported types. Therefore the tool will intentionally fail if such fields are not omitted.

… is a problem. Because nothing would stop me from from doing this:

user := NewUserBuilder().
    WithName("John").
    WithAge(22).
    Build()
// Don't tell me what to do!
user.Name = ""

Also - if you’re going to require a Build() function, you could just shift your validation into that function:

user, err := NewUserBuilder().
    WithName("John").
    WithAge(22).
    Build()

Also - almost universally, the place where this matters is when you go to actually do something with a struct:

// Doesn't matter that Name/Age is zero value
user := User{}

// Here is where it DOES matter that Name/Age is zero value. Makes sense
// to shift the onus for validation to where the validation matters. So
// this func could just validate and return an error.
func DoSomething(u User) error

Anyway, I think the idea of compile-time enforcement of required fields is interesting. But to Karl’s point, I think constructors are how most people solve this problem. And you can’t guarantee that something didn’t mutate your object after the required fields were initialized anyway, so you have to validate state where you’re using it.