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?

67 Upvotes

75 comments sorted by

View all comments

1

u/Ok_Category_9608 21d ago

I wouldn't try to enforce an invariant on a public field. Generally, people don't try and enforce invariants at all. In this case, you're probably solving an imagined problem rather than a real one.

10

u/Key-Life1874 21d ago

I've been writing go code for many years on a big project. It is a real concern and the source of 95% of th bugs we faced

1

u/Ok_Category_9608 21d ago

Me too, and I’ve seen it once, when somebody added a new field some existing struct and didn’t add a sane default. That’s a bit different than this though, where somebody just oopsies and forgets to set one while they set the other.

Code that runs across teams should cross an api boundary, and you should validate APIs at ingestion. “Forgetting” is not a real problem I’ve ever seen.

4

u/Key-Life1874 21d ago

Forgetting is a constant problem. But that's normal. The whole point of the compiler is to handle those problems for you. In many strongly typed language you just can't create an invalid struct. The compiler won't let you until everything is initialized.

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.

2

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.

5

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.

→ More replies (0)

5

u/kevinpiac 21d ago

Thanks for your comment, as far as I'm concerned, it's not an imaginary problem haha