Closed atishpatel closed 4 years ago
func foo(a <- chan int) *Type1{
if <-a < 0 {
return nil
}
return newType1(a)
}
func example(a #Type1) {
...
}
go func(){
// read from network
ch <- fromNetwork()
}
example(foo(ch))
How would you propose to solve the issue of breaking composability as presented above.
This example shows you need to evaluate at compile time, leading to a twostage compilation thing.
(p/s: null pointer detection is undecidable in the general case)
See also #28133 and #30177.
For language change proposals, please fill out the template at https://go.googlesource.com/proposal/+/bd3ac287ccbebb2d12a386f1f1447876dd74b54d/go2-language-changes.md .
When you are done, please reply to the issue with @gopherbot
please
remove
label
WaitingForInfo
.
Thanks!
you can't assign to a non-nil pointer, or dereference it until you have proven that it has been assigned a non-nil value? ie:
Prove it with a branch...
if p == nil {
...
} else {
p.execute()
}
Or prove it with a declaration that it's not nil. But if that pointer exists in a struct, must it be assigned on struct construction?
This could get quite confusing
type P struct { *q }
type q struct {}
func (x *q) execute()
func main() { var p P p.execute() }
is this permitted? if not, what check is required? what would happen if P and q were not in the same package as main, the expression if p.q == nil would not be allowed.
@ianlancetaylor I'm not suggesting a language change that would require breaking the Go 1 compat from my understanding. I know that Go 2 is code word for it'll never happen. What i'm suggesting is this is specifically for function parameters only. Would you still like me to fill out the template?
Again, i'm not an expert, and I do not know if we can do what i'm suggesting below is possible or not.
@chewxy Great question. I'm sorry i forgot to put this in the original issue. Here is your code with what would and working give compile errors.
func foo(a <- chan int) *Type1{
if <-a < 0 {
return nil
}
return newType1(a)
}
func example(a #Type1) {
...
}
go func(){
// read from network
ch <- fromNetwork()
}
// Compile error: cannot use untype nil as type *Type1 in argument to example
example(foo(ch))
v := foo(ch)
if v != nil {
// compiles successfully
example(v)
}
newVar := new(Type1)
// Compile error: cannot use newVar (untype nil) as type *Type1 in argument to example
example(newVar)
newVar = &Type1{}
// compiles successfully
example(newVar)
func call(t *Type1) {
if t == nil {
return
}
// compiles successfully
example(t)
}
The goal would be to analyze the code to see if it is possible for the param to be nil. And if it is, at compile time, state this is invalid.
I hope you don't mind me adding this to my original comment. š
@atishpatel It's not correct to say that "Go 2 is code word for it'll never happen.'' We use the Go 2 label for every language change, including the language changes that have in fact happened. For example, see https://golang.org/doc/go1.13#language; each of those changes has an associated issue marked "Go 2".
So, yes, I would still like you to fill out the template. Thanks.
A language change that permits people to write
if t == nil {
return
}
// Here t can be assigned to pointers required to be non-nil.
requires very clear instructions for exactly when the compiler can assume that the pointer is not nil. There are multiple Go compilers, and they must all precisely agree as to which programs can be compiled.
@ianlancetaylor My apologizes. I remember Rob Pike announcing Go1.10 by saying there probably won't be a Go2, and we are here Go1.14. I didn't realize the Go2 label is used for language changes that still keep the Go 1 compact. I'll fill out the template and add it to the original comment. Thank you for your help. š
// Compile error: cannot use untype nil as type *Type1 in argument to example
example(foo(ch))
v := foo(ch)
if v != nil {
// compiles successfully
example(v)
}
This is a language change. Also, not quite as useful given that you can write a program to check for trivial nil
newVar := new(Type1)
// Compile error: cannot use newVar (untype nil) as type *Type1 in argument to example
example(newVar)
This is confusing. newVar
is not nil. it's a pointer to a value of type Type1
with the zero values of the components of Type1
Chewxy. Yes, this is a language change but i don't think it breaks go1 compat.
Yes. The goal isn't to check if something is nil or not. The goal is write and share code that is more clear with pointers.
I've written Go for 3+ years, and as my projects grow bigger and there is older code, i run into more issues with nil pointers. One could just deal with runtime errors to find out if there is a nil pointer issue. But, i'm trying to find a way where, if i know something shouldn't be nil, i can communicate that to everyone including my future self.
chewxy. You are correct. I meant var newVar *Type1
instead of newVar := new(Type1)
I've updated it in the original issue comment, but left it in the middle one. Thank you again.
@gopherbot please remove label WaitingForInfo
The problem with this idea is that not all, or not even most nuls can be checked at compile time. The compiler would have to solve the halting problem for this. Rather, if you don't want to receive a nul, see if you can't pass the struct by value in stead of by pointer.
As mentioned above in https://github.com/golang/go/issues/36884#issuecomment-580080773, in order to make code like this work
if a != nil {
F(a) // where F is defined using #
}
we need to add a notion of dataflow to the language, so that all compilers will analyze this code in the same way. That is a significant complexity that we want to avoid.
The suggested #
syntax only supports pointer types, but of course there are other kinds of types that can be nil
: slices, maps, channels, functions, interfaces.
For these reasons this is a likely decline. Leaving open for four weeks for final comments.
No further comments. Closing.
Summary
At compile time, there should be a way to specify you can't pass nil into the function call but it's still a pointer.
Pointers are awesome and there are many reason to use a pointer such as not having to copy a param, being able to mutate a param, etc. But, using pointer is makes you prone to nil pointers, and people who use your functions often try to pass nil and you have to handle this. I genuinely think there is potential here for a way to improve the developer experience by providing compile time errors for invalid nil pointers.
Side note: One of the main reasons I love Golang is because it's an opinionated statically typed language that provides a great developer experience. I get lint warnings if i don't add proper comments and that is wonderful.
Current options - landmine runtime error
Check all params for nil and panic or return error.
Goal
Proposal 1 - non-nil pointer character - backward compatible
Introduce a character that implies a non-nil-able pointer. In this example, the character is
#
.In this case,
param1
could be nil butparam2
would give a compile time error if someone passed in nil.I'm not an expert at the language so perhaps someone else can tell me if there is a better way than this.
Alternative Proposal 2 - Not Nil Union Type - backward compatible
This is more elegant but it could break people's code on library updates. For example, if you are relying on a library that updated to use this, your code would give compile time errors saying you can't use nil here. But, perhaps it is good because if the developer updates the library and you get compile time errors, you shouldn't have been passing nil into the function anyway and it saved you from a runtime error. š¤·āā
In this case,
param1
could be nil butparam2
would give a compile time error if someone passed in nil.Template
#
is a non nil pointer.#
would mean a pointer type that is not nilAt the core of this is a better developer experience by giving compile time error instead of runtime errors.