Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: Go 2: add type constraints like reference only, assign before use #23968

Closed
creker opened this issue Feb 20, 2018 · 11 comments
Closed
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Milestone

Comments

@creker
Copy link

creker commented Feb 20, 2018

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.

@gopherbot gopherbot added this to the Proposal milestone Feb 20, 2018
@ianlancetaylor ianlancetaylor changed the title Proposal: add type constraints like reference only, assign before use proposal: Go 2: add type constraints like reference only, assign before use Feb 20, 2018
@ianlancetaylor ianlancetaylor added LanguageChange Suggested changes to the Go language v2 An incompatible library change labels Feb 20, 2018
@ianlancetaylor
Copy link
Member

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
Copy link
Author

creker commented 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
Copy link
Contributor

cznic commented 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
Copy link
Author

creker commented 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
Copy link
Contributor

cznic commented Feb 21, 2018

You can't pass them by value.

Everything in Go is passed by value. It's the only way available. (Just like in C modulo array decay.)

@creker
Copy link
Author

creker commented 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
Copy link
Contributor

cznic commented Feb 21, 2018

Maps, slices, channels, functions - they're all reference-only types.

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
Copy link
Contributor

jba commented Feb 26, 2018

It's easy enough to make a value non-copyable:

type File struct {
	*file
}

(That is from https://golang.org/src/os/types.go#L16.)

Nil is zero value for maps but you can't actually use it like that. You have to allocate it.

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.

trying to insert a value into a nil map would not panic but allocate map for you implicitly

Too much magic for Go. Also, what do you do for something like

func f() map[int]int { ... }
...
f()[3] = 4

@creker
Copy link
Author

creker commented Feb 26, 2018

It's easy enough to make a value non-copyable:

What if you're in the same package with file? It would still by copyable.

Too much magic for Go

Slices have even more magic in them. Ether way, that was just a though. My main concern is with type restrictions.

@bcmills
Copy link
Contributor

bcmills commented Mar 2, 2018

it should be possible to specify whether zero value is actually usable or you're required to allocate (assign) variable before use.

See also #21538 (comment) and #21538 (comment).

@ianlancetaylor
Copy link
Member

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.

@golang golang locked and limited conversation to collaborators Apr 24, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge LanguageChange Suggested changes to the Go language Proposal v2 An incompatible library change
Projects
None yet
Development

No branches or pull requests

6 participants