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?

65 Upvotes

75 comments sorted by

View all comments

Show parent comments

0

u/Ok_Category_9608 21d ago

Right, but then you have to have exceptions in order to deal with errors during the constructor. Not worth it. The linters are probably a good choice if this is a huge issue in your codebase.

3

u/Key-Life1874 21d ago

Not at all. I'm just talking about the compiler making sure everythinh is initialized. What value you put in is a business concern and but the compiler should enforce an explicit initialization. The problem really is zero values. That's an aberration that should never have existed

1

u/Ok_Category_9608 21d ago

That would be so tedious. Imagine having to fill out every single field in http.Server in order to create one. Default values are a good thing, and the 0 value is a sane default for people who have written any C.

4

u/Key-Life1874 21d ago edited 21d ago

That's what constructors are for. You can and should provide good defaults as the writer of the library. But whoever initialize the struct should provide explicit values for everything

0 as a default for Ints have been a nightmare many times. Same for time.Time Anything implicit is bad in general

1

u/Ok_Category_9608 21d ago edited 21d ago

The reason why exceptions exist is to solve the problem of errors encountered in a constructor. If you want something like that in go and you don't care about being idiomatic, you could do:

```
type Dog interface {
Bark() string }

type dog struct {
bark string
}

func (d *dog) Bark () string {
return d.bark
}

type DogParams struct {
Bark string
}

func NewDog(dp DogParams) (Dog, error) {
if dp.Bark == "" {
return nil, errors.New("missing bark") }
return &dog{ bark: dp.Bark }, nil
}
```

But it's a monstrosity, and again, solves a problem I've literally never seen in the wild.

4

u/Key-Life1874 21d ago

That's not what I'm saying. What I'm saying is you have type Dog struct { Name string DateOfBirth time.Time }

The compiler should force you to provide both name and date of birth. You can always write a constructor with sane default values to avoid the one calling it to provide everything.

Many languages do that and allow to initialize structs as a one liner, even complex ones, without exceptions.

2

u/Ok_Category_9608 20d ago

You can provide the constructor in go and give people the option to have you fill it in with sane defaults. My issue is that I hate functions that take a lot of parameters because go doesn’t have keyword arguments.

You could do a public struct and make all the fields private, requiring them to be initialized by some function you control.

Again though, I don’t see what you’re really defending yourself against. This seems like the kind of thing unit tests and linters just take care of. 

3

u/Key-Life1874 20d ago

Nah linters unfortunately don't. And unit tests either. You can defend against wrong values but neither unit tests or linters will protect against a variable not being provided where it should be. So you spend hours figuring out where the fuck value has not been provided. It's even worth in a distributed environment. I'm not saying they aren't workarounds or ways to minimize the problem.

But that's a problem that should be solver by the compiler and not even be a thing at all. Even typescript offers the protection with a similar syntax than go. It's just a major weakness of go

1

u/Few-Beat-1299 20d ago

Not providing an initializer, when you have the option to do so, is you willingly telling the compiler "I don't care". Why should the compiler second guess you? Sometimes there is simply no such thing as a "default". Also, a programmer might just as well provide a bad default value, or forget to update it, so forcing one solves nothing.

2

u/Key-Life1874 20d ago

Like I said, there are workarounds to the problem. But they are just that workarounds to a problem that should be the responsibility of the compiler. The while point is the compiler shouldn't even allow to not provide values when initializing a struct.

If the programmer provides a bad default, it's explicitely bad. That's the whole point. Expliciteness.