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: improve if statement #26712

Closed
gbbr opened this issue Jul 31, 2018 · 8 comments
Closed

proposal: Go 2: improve if statement #26712

gbbr opened this issue Jul 31, 2018 · 8 comments
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Milestone

Comments

@gbbr
Copy link
Member

gbbr commented Jul 31, 2018

This proposal is for considering the introduction of a new form to the if statement by allowing not only expressions, but also an interface. It would look like this:

"if" ( Expression | InterfaceType) Block

This would change the if statement's behavior in such a way that when an interface argument is provided (as opposed to an expression), the statement would evaluate it as false if the interface is nil or if its value is nil.

This could maybe solve the problem when newcomers assume wrongly about err == nil (see FAQ entry) and offer a good solution to performing this check (#22729). Example:

if !v {
    // v is an interface with value nil or holds a nil pointer
    // it is unusable
}

It also attempts simplifying the (by some considered tedious) error handling (#21161). It would become:

if err {
   // handle error
}

However I'm unsure if this has any side effects which could cause confusion or wrong assumptions.

@gopherbot gopherbot added this to the Proposal milestone Jul 31, 2018
@gbbr gbbr added LanguageChange v2 A language change or incompatible library change labels Jul 31, 2018
@DeedleFake
Copy link

DeedleFake commented Jul 31, 2018

I am against this. Go being strict about all conditionals being booleans is one of my favorite features. Treating non-boolean values as true and false causes so many annoyances in a lot of other languages.

As an alternative, how about just altering interfaceValue == nil so that it's true for either a nil interface or a nil underlying value? Or modifying some of the typing rules so that you can never have a nil underlying value, maybe? For example, right now the easiest way to get one is by sticking a nil return value into an interface variable, such as in the following:

func nilValue() *string {
	return nil
}

func main() {
	var i interface{} = nilValue()
	println(i)
}

What if this just made i a nil interface?

The argument could probably be made that interfaceValue == nil should return true if it's a nil underlying value, actually, since interfaceValue == "some string" will actually compare underlying string values against the given string. Since nil is a valid value for the underlying type, why doesn't that return true?

@LMMilewski
Copy link
Member

@gbbr are you suggesting that:

  1. !v has a different meaning than v==nil for interfaces, or that
  2. we change the meaning of v==nil
    ?

Both seem to be undesirable.

@DeedleFake
Currently:

package main

import "fmt"

func main() {
	var i interface{}

	i = (*struct{})(nil)
	fmt.Println(i == nil) // [1] currently: false

	i = ""
	fmt.Println(i == nil) // [2] currently: false
}

It would be surprising if [1] and [2] printed different values.
I think that you'd agree that it would be surprising to print true in [2].

@DeedleFake
Copy link

DeedleFake commented Jul 31, 2018

I don't think it would be that surprising. You're comparing an empty string to nil; I would expect that to be false. Why would it not be? On the other hand,

var i interface{} = ""
println(i == "")

prints true, so why doesnt

var i interface{} = (*struct{})(nil)
println(i == nil)

Now,

var i interface{} = (*struct{})(nil)
println(i == (*struct{})(nil))

does, but this seems almost like an oversight in the type system. It makes it look like a literal nil is typed, despite the fact that Go literals are supposed to be untyped.

I think this complication primarily arises from the fact that nil has multiple kinds, not just types. "" is always a string. nil is more like a number literal than a string. A literal 0 can be any of the number types, and a nil can be any of the map, pointer, or slice types. The big difference is that all of those have element types, whereas numbers do not.

Bottom line, both of the following print false, and it might make more sense for it to print true:

var i interface{} = float32(0)
println(i == 0)

i = (*struct{})(nil)
println(i == nil)

Unfortunately, this may be more complicated than it seems, as it would require the equality to recognize that it's comparing an interface against a literal and handle the literal typing at runtime.

@Azareal
Copy link

Azareal commented Aug 1, 2018

It probably is the simplest way (someone else probably mentioned it, but the issue is gargantuan) to eliminate the != nil boilerplate without inventing new keywords or confusing syntax, but I'm not really sure it would really be that consistent, unless you overhaul it for all the other cases (e.g. integers and maybe pointers), but then, you'd introduce a lot of chances for subtle bugs to sneak in and it's more rules to keep track of.

@smasher164
Copy link
Member

If making the nil check shorter is the only reason for this if statement, maybe the following alternative would work (albeit with some reflection)?

func nl(v interface{}) bool {
	if v == nil {
		return true
	}
	r := reflect.ValueOf(v)
	if r.Kind() != reflect.Ptr {
		return false
	}
	return r.IsNil()
}

The above example would be

if !nl(err) {
	// handle error
}

@bcmills
Copy link
Contributor

bcmills commented Aug 2, 2018

I'm unsure if this has any side effects which could cause confusion or wrong assumptions.

var x interface{} = false
[…]
if x {
	// x == false, but here we are!
}

@LMMilewski
Copy link
Member

@DeedleFake

var i interface{} = ""
println(i == "")
prints true, so why doesnt

var i interface{} = (*struct{})(nil)
println(i == nil)

Those two examples can't be compared. The first one compares i to a
zero value of a string. The second one compares i to an untyped
nil which is not a zero value of the underlying pointer.
(*struct)(nil) would be.

Perhaps it would help to rewrites this example as:

var i interface{} = zeroString
println(i == zeroString)

var i interface{} = zeroPtr
println(i == zeroInterface)

Now it's clear why the first one prints true and the second one false.

We could change the meaning of nil in the second example to be zeroPtr. At that point we perhaps should introduce a new keyword to distinguish nil pointer from nil interface though.

=========================

Arguably, if code requires using a function like nl from
@smasher164's post then it's a good sign that the API may require a
redesign. At least that was my experience so far (I occasionally see nl
equivalents in code reviews).

The current behavior of v == nil allows testing whether v was set.

The following API means: "provide anything that I can call Foo on, or ask me
to use a default":

type Fooer interface{
  Foo()
}

func F(fooer Fooer) {
  if fooer == nil {
     fooer = defaultFooer
  }
  // ...
}

It is not uncommon to allow calling methods on nil (e.g. protocol
buffers do that) so this is expected to work:

type s struct{}
func (*s) Foo() { fmt.Println("hello") }
F((*s)(nil))   // should not be treated as F(nil)

By changing the meaning of comparison with nil, as discussed, we'd
lose ability to express that API. Certain values (nil pointers) would
no longer work even though they implement the required interface.

That is, we lose the ability to express good APIs with simple code,
and we gain little in return.

@ianlancetaylor
Copy link
Contributor

This is in effect an implicit conversion from all interface values to bool, at least in the context of an if condition (and presumably a for condition, and perhaps elsewhere--why not?). Go avoids implicit conversions in general, other than converting values to interface types. We aren't going to add this specific implicit conversion.

@golang golang locked and limited conversation to collaborators Oct 2, 2019
@seankhliao seankhliao added the error-handling Language & library change proposals that are about error handling. label Jan 16, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
error-handling Language & library change proposals that are about error handling. FrozenDueToAge LanguageChange Proposal v2 A language change or incompatible library change
Projects
None yet
Development

No branches or pull requests

9 participants