Description
I know the title might be confusing. I have a hard time formulating this proposal (if you can even call it that) but want to get out here as I think it's important to consider. May be somebody will be able to write it out better if it is indeed something important.
Recently I stumbled upon a question on stackoverflow that described a bug caused by sync.WaitGroup
being passed by value. As you can imagine, it caused a bug because WaitGroup
contains state and should only be passed by reference. And that made me remember that I also made that kind of error in the past. But it's not limited to wait group and I want to make it more general.
In Go values can be passed by value or by reference. Some internal types are explicitly passed by reference. Some types have useful zero value, some require you to allocate them before use. All of this might come with experience but every now and then it still bites me. Like, for example, maps. Nil is zero value for maps but you can't actually use it like that. You have to allocate it. That doesn't match, say, slices which also has nil as zero value but you can pass nil slice to append
and it will happily accept it. Doesn't seem to be a reason why maps are like that on the surface.
What I'm trying to say by this is types require certain rules on how you should use them. These rules are not enforced and can be easily broken which leads to runtime panics or even hard to debug errors like in the case with wait group. Languages like Java and C# have no problem because everything is passed by reference. Internal Go types like maps and channels already doing that. So there is a need for that.
What I propose is to extend language to be able to specify these rules explicitly. I understand that vetting might solve this but I insist on solving this on a language level to benefit all user types, not a limited subset of what vet implements. I also strongly believe that this should produce compile time error and not be a part of a separate tool that may or may not be executed. What the actual syntax might look like is not important as I don't have a fully written out proposal. But you can imagine something like this:
type *Foo struct {
}
What this would do is to force all variables of this type to be references. Dereferencing is disallowed. Embedding is allowed but enclosing type would probably be required to also be reference only. Method receivers reference only. Any violation would result in a compile time error.
Zero values may warrant a separate proposal but it kinda fits into this general proposal to specify rules on how to use types. In this case it should be possible to specify whether zero value is actually usable or you're required to allocate (assign) variable before use.
The goal here is to produce compile time error saying that you're trying to use a variable before assignment. So if you write var m map[int]int
and try to insert something into it compiler would detect that and produce compile time error. The same goes for any user type. It's probably impossible to catch all cases as value may come and go from any place in the program but it would catch obvious errors.
Or, alternatively, this could be limited only to maps so that trying to insert a value into a nil map would not panic but allocate map for you implicitly. But that's definitely a different proposal.
Activity
[-]Proposal: add type constraints like reference only, assign before use[/-][+]proposal: Go 2: add type constraints like reference only, assign before use[/+]ianlancetaylor commentedon Feb 21, 2018
Related to #23764.
Frankly, right now, this is too vague. I think I understand the problem you are describing. But for a proposal to be useful, we need some kind of solution.
You may want to consider adding a user experience report to https://golang.org/wiki/ExperienceReports.
creker commentedon Feb 21, 2018
@ianlancetaylor I agree, it's too vague and all over the place. I just wanted to get out for discussion. Maybe something useful would come out of it or it turns out I'm asking for something ridiculous. Sorry if it's a dup. I tried searching but didn't find anything. #23764 sounds a lot like what I'm proposing. At least in regards to reference only part.
cznic commentedon Feb 21, 2018
Nit: To 'pass by reference' has no well agreed-upon meaning in Go because the language specification does not define it. It's just to 'pass a pointer'.
creker commentedon Feb 21, 2018
@cznic didn't know that. What I mean by that is what channels, for example, already do. You can't pass them by value. So they're protected from kind of error I described. I would like to allow that functionality for any user-define type.
sync.WaitGroup
would definitely benefit from it.cznic commentedon Feb 21, 2018
Everything in Go is passed by value. It's the only way available. (Just like in C modulo array decay.)
creker commentedon Feb 21, 2018
@cznic specification doesn't say it but we all understand what I mean by that. Maps, slices, channels, functions - they're all reference-only types. And that means that passing them as arguments will pass that specific instance. Not make a copy of an opaque struct that underlies the type.
cznic commentedon Feb 21, 2018
For example, slice is a small, 3-word structure and is always passed by value in the same way like an
int
value is - unless the address of the slice is explicitly taken and then the resulting pointer is passed (by value again, of course).PS: Wrt 'specification doesn't say it but we all understand what I mean by that.' and previous 'no well agreed-upon meaning in Go': Q.E.D.
jba commentedon Feb 26, 2018
It's easy enough to make a value non-copyable:
(That is from https://golang.org/src/os/types.go#L16.)
You can take its length and read from it. That can be quite useful. For example, a map might be seldom modified, so you want to lazily allocate it.
Too much magic for Go. Also, what do you do for something like
creker commentedon Feb 26, 2018
What if you're in the same package with
file
? It would still by copyable.Slices have even more magic in them. Ether way, that was just a though. My main concern is with type restrictions.
bcmills commentedon Mar 2, 2018
See also #21538 (comment) and #21538 (comment).
ianlancetaylor commentedon Apr 24, 2018
Adding some sort of restrictions on how types can be used would certainly be useful. But this proposal only suggests one specific restriction, and not even the most commonly suggested one (which is some version of the C
const
qualifier). More generally, any such language construct should be able to cover a large range of constraints, but this proposal doesn't indicate what that might look like. The exact rules of what can be permitted, and an exact syntax for that, are essential.