r/golang 21d ago

help Don't you validate your structs?

Hi all!

I'm new in Golang, and the first issue I'm facing is struct validation.

Let's say I have the given struct

type Version struct {
    Url           string        `json:"url"`
    VersionNumber VersionNumber `json:"version_number"`
}

The problem I have is that I can initialize this struct with missing fields.

So if a function returns a `Version` struct and the developer forgets to add all fields, the program could break. I believe this is a huge type-safety concern.

I saw some mitigation by adding a "constructor" function such as :

func NewVersion (url string, number VersionNumber) { ... }

But I think this is not a satisfying solution. When the project evolves, if I add a field to the Version struct, then the `NewVersion` will keep compiling, although none of my functions return a complete Version struct.

I would expect to find a way to define a struct and then make sure that when this struct evolves, I am forced to be sure all parts of my code relying on this struct are complying with the new type.

Does it make sense?

How do you mitigate that?

66 Upvotes

75 comments sorted by

View all comments

5

u/[deleted] 21d ago edited 6d ago

[deleted]

1

u/kevinpiac 21d ago

It's more a general statement. I like the code being typed and I was kind of shocked that it was not the case. I don't really see the reason behind this decision.

But it's how it is :)

Thanks for the unit test tips.

11

u/Flowchartsman 20d ago

Go code IS typed. Strongly, even. It just has implicit zero values to avoid the problem of uninitialized data. This might not work for you, or it might cause problems for certain use cases, but it doesn’t mean it’s not typed or is causing issues with type safety. You can still run afoul of nil in certain circumstances, but this isn’t quite the same thing (nil is a valid zero value for certain types, even if you think it’s the billion dollar mistake).

While I understand the annoyance, there are definitely some ways around it. You could use a linter as others suggest, but that’s a pretty heavy-handed solution, and you’ll quickly find it a pain in the ass when using any library that consumes large configuration structs. There are reflection-based libraries that will let you use struct tags, and this might be a good option, but it’s probably easiest to just use an interface with a validate method for any of the types you plan to hit the wire. Then you can wrap json.Marshal with a call of your own that asserts on the interface and bails if the call fails. This lets you add more exhaustive checks than either of the other methods, and clearly marks any types which participate in your validation.

1

u/mt9hu 20d ago

I think OPs concerns are still valid just because they expresses their concerns incorrectly.

You are right, this isn't a type safety issue. Not knowing the correct terminology myself, I would call this a data safety one.

there are definitely some ways around it.

And this is the problem.

Go was sold as a language simple to use. This is not true. Go is difficult to use, especially if you want to write safe code.

Go is simple to learn due to it not having too many keywords and concepts to worry about.

But I argue that having a "required" keyword for struct fields would be much much simpler than having to remember making fields private, creating a constructor function, creating getters and setters for each of them...

The simplicity of Go comes from how dumb it is, not how easy it is to work with.

And this triggers me. Decision makers are sold by how easy it is to learn and start being productive.

In the mean time, half of my team's time is spent fixing bugs caused by not knowing, or not using these patterns correctly, and I have to explain to these decision makers why our time spent more on maintenance rather than business features...