Skip to content

proposal: Go 2: explicit nil and pointer param #36884

Closed
@atishpatel

Description

@atishpatel

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.

func example(param1 *Type1, param2 *Type2) (*Result, error) {
  if (param1 == nil) {
  // or perhaps better to panic so a developer isn't caught by a runtime error in prod
   return nil, fmt.Errorf("Param1 cannot be nil")
 }
  if (param2 == nil) {
  // or perhaps better to panic so a developer isn't caught by a runtime error in prod
   return nil, fmt.Errorf("Param2 cannot be nil")
 }

// actual function logic
} 

Goal

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)
}

var newVar *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)
}

Proposal 1 - non-nil pointer character - backward compatible

Introduce a character that implies a non-nil-able pointer. In this example, the character is #.

func example(param1 *Type1, param2 #Type2) (*Result, error) {

In this case, param1 could be nil but param2 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. 🤷‍♂

func example(param1 *Type1 , param2 *Type2 | !nil) (*Result, error) {

In this case, param1 could be nil but param2 would give a compile time error if someone passed in nil.


Template

  • Would you consider yourself a novice, intermediate, or experienced Go programmer?
    • intermediate
  • What other languages do you have experience with?
    • C++, Typescript, HTML, CSS, Python, SQL
  • Would this change make Go easier or harder to learn, and why?
    • A little harder because the would need to learn # is a non nil pointer.
    • Little easier because they could write more safe code.
  • Has this idea, or one like it, been proposed before?
    • An ultimate solution to change fundamentally how nils work in Golang has been suggested, but I didn't see anything that was limited in scope and would provide much value such as this proposal.
  • If so, how does this proposal differ?
    • This change is limited in scope and focuses on keeping Go 1 compact while delivering of a better developer experience.
  • Who does this proposal help, and why?
    • Developers. It moves runtime issues to compile time saving time and forcing them to handle nils better.
  • Is this change backward compatible?
    • Yes
  • Breaking the (Go 1 compatibility guarantee)[https://golang.org/doc/go1compat] is a large cost and requires a large benefit.
    • Does not break Go 1 compatibility / compat
  • Show example code before and after the change.
    • Shown above.
  • What is the cost of this proposal? (Every language change has a cost).
    • Compiler would need to understand if a variable can be nil or not based on if a nil check as been done beforehand.
  • How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
    • My understanding of these tools are limited. From my understanding as long as the underlying AST parser is updated to support the character, it shouldn't be major impact for many tools. But, there would need to be changes to how the compiler works.
  • What is the compile time cost?
    • I don't know. Though this will increase compile time as a trade off to prevent compile time errors.
  • What is the run time cost?
    • none
  • Can you describe a possible implementation?
    • Described above.
  • Do you have a prototype? (This is not required.)
    • I do not.
  • How would the language spec change?
    • # would mean a pointer type that is not nil
  • Orthogonality: how does this change interact or overlap with existing features?
  • Is the goal of this change a performance improvement?
    • No
  • If so, what quantifiable improvement should we expect?
  • How would we measure it?
  • Does this affect error handling?
    • No
  • If so, how does this differ from previous error handling proposals?
  • Is this about generics?
    • No
  • If so, how does this differ from the the current design draft and the previous generics proposals?

At the core of this is a better developer experience by giving compile time error instead of runtime errors.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions