> A difference between Go and Java is that a null Regex in Java will not be implicitly converted to a non-null Stringable containing a null Regex.
You have it exactly backwards--you're arguing that Go should implicitly convert a not-nil interface (that is implemented by a nil pointer) to a nil interface so that Go interfaces behave more like Java interfaces, but Go isn't doing any implicit conversions (unless you consider "assigning a concrete value to an interface" to be an "implicit conversion", but Java does this too).
> implicit conversion from nil pointers to non-nil interfaces containing nil pointers
Again, Go doesn't do any such implicit conversion, and you're arguing the position that it should. An interface is just a tuple containing a pointer to the data and a pointer to the type `(pdata, ptype)`. A nil interface is `(nil, nil)`, and an interface that is implemented by a nil pointer is `(nil, <pointer-to-concrete-type>)`. You're arguing the position that the latter should be implicitly converted to the former, but this would break code that uses nil as a valid value.
The root cause of the problem isn't how Go handles nil pointers in interfaces, but rather that Go has nil pointers, interfaces, etc at all. Go gives you no way to express that a reference type can't be nil, but ideally it would have an enum system like Rust's that makes optionality an opt-in property via `Option<T>`. If an interface `Bar` is implemented by `*Foo` and neither `*Foo` nor `Bar` can be nil, then this problem largely goes away. If `Bar` is implemented by `Option<*Foo>`, then `Option<*Foo>`'s methods need to handle both the `Some(*Foo)` and `None` (i.e., nil) cases.
> that can only be checked for nil using reflection
You don't need reflection:
i, ok := (iface).(*int)
if !ok {
fmt.Println("`iface` is not an `*int`")
}
if i == nil {
fmt.Println("`iface` contains a nil pointer")
}
fmt.Printf("`iface` contains a pointer to `%d`", i)**
What do you call it when you assign a value of one type to a variable of another type and the language creates a new value, different from the one you were assigning, and stores that new value in the variable? In C++ this is called "implicit conversion." Here's the tutorial again:
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser
The last two lines involve constructing a new value for the LHS of a type that differs from the type on the RHS without any function calls, constructors, "make" keywords, etc. What word would you like to use for this?
You could just as well have a patch that changes the type of a single variable from *Vertex to Abser or changes the return type of a single function from a type that matches an interface to the interface, and that would also create an assignment that creates a new value of the new type that will panic in contexts the old one would not have panicked, even though the programmer has not added any code saying "please construct an Abser (type, pointer) tuple from my pointer."
> Go should implicitly convert a not-nil interface (that is implemented by a nil pointer) to a nil interface
I am not arguing for that.
> unless you consider "assigning a concrete value to an interface" to be an "implicit conversion", but Java does this too
I guess, but this is much less of a conversion than Go is performing because the runtime value does not change at all. It's the same pointer, not a tuple containing the pointer. Unlike in Go, this assignment may very often be correctly implemented by a sequence of 0 instructions! Importantly, the meaning of "foo == null" is unchanged. This is not because Java performs implicit conversions of interface types to specific pointer types at the null check. It's because in Java interfaces are unable to express having a concrete value of a specific class which is null, because they are just pointers. One might say "Java interfaces do not have inner and outer nil values" as a shorthand for this.
> Go gives you no way to express that a reference type can't be nil
I agree that this would solve the problem.
> You don't need reflection:
You don't need reflection if you statically build an exhaustive list of all pointer types that match the interface in the whole program but don't actually match the interface in the sense that the methods don't handle nil, then generate code to handle each of them. This just doesn't seem like a reasonable option because sometimes people make libraries so they can't necessarily do static analysis of the whole program at the point where they are writing the code. It also sort of defeats the purpose of interfaces, I guess.
> What do you call it when you assign a value of one type to a variable of another type and the language creates a new value, different from the one you were assigning, and stores that new value in the variable? In C++ this is called "implicit conversion."
Right, I addressed this when I said:
> unless you consider "assigning a concrete value to an interface" to be an "implicit conversion", but Java does this too
So let's just skip to your reply to that bit:
> I guess, but this is much less of a conversion than Go is performing because the runtime value does not change at all. It's the same pointer, not a tuple containing the pointer. Unlike in Go, this assignment may very often be correctly implemented by a sequence of 0 instructions!
Yes, Java and Go have different implementations/conceptions of "interface". I don't see what bearing this has. From the programmer's perspective, Java's interface assignment isn't more explicit than Go's even though the underlying mechanisms differ. In particular, Go needs to create a new "header" value because its interfaces are polymorphic across any type (including primitives and value types!) whereas Java's are polymorphic only across objects and Java attaches a header to every object.
> Importantly, the meaning of "foo == null" is unchanged.
Go's `foo == nil` isn't changed either. It behaves like it does for all reference types. The difference between Java and Go is conceptual, but the `==` operator is coherent within each of them.
> This is not because Java performs implicit conversions of interface types to specific pointer types at the null check.
Agreed, and to be clear, I didn't claim it did. Nor does Go, so I'm not quite sure what you're responding to here.
> It's because in Java interfaces are unable to express having a concrete value of a specific class which is null, because they are just pointers.
> One might say "Java interfaces do not have inner and outer nil values" as a shorthand for this.
Right, but we were debating whether or not Go interfaces have inner or outer nils. Consider `var x interface{} = 0`. What's the outer value? And what's the inner value? The whole point of my comment is that you're conceiving of Go interfaces as having inner and outer values, but Go interfaces are just a fat pointer. As such, you can have pointers to pointers with Go interfaces. And while you can't have pointers to pointers with Java interfaces, you can elsewhere in Java so a Java programmer should be able to reason about Go interfaces because they can understand the concept of a nullable pointer to a nullable pointer.
Ultimately, it seems that you're criticizing Go for having a different interface mechanism than Java, and you get bugs when you treat Go interfaces like Java interfaces. And Go made the right choice here, because it can express polymorphism over a wider range of types than just objects (contra Java interfaces) and indeed it doesn't have any sort of object/primitive bifurcation--from the perspective of Go interfaces, all concrete types are just data.
This is incorrect. The value of "foo == nil" in function A can be changed by a patch that only changes the return type of function B from *Vertex to Abser. If you would like to say that this is not because of implicit conversion from pointer to interface and it is not because of any semantic difference between comparing the vertex pointer to nil and comparing the Abser interface to the nil interface value, I would like to know what you think causes it.
> Right, but we were debating whether or not Go interfaces have inner or outer nils
I haven't been debating this since it's obviously true. You seem to agree that an Option<Option<T>> may take on the values None and Some(None) separately. You can debate about what to call things, but I'm still not sure what you would like me to call the difference that exists between Java and Go which causes code that does not throw NullPointerExceptions in Java to panic in Go because the inner value is nil and the user has failed to use reflection (or generate an exhaustive list of all possible pointer types that could match the interface but whose method does not handle nil and check each of them individually).
> Consider `var x interface{} = 0`. What's the outer value? And what's the inner value?
What do you mean? That's a (int, 0) tuple. The type tag is int. The inner value is 0. interface{} expresses outer-nil-ness using a special value of the type tag, so that's (nil, nil) which a different value from (*Vertex, nil).
> And while you can't have pointers to pointers with Java interfaces, you can elsewhere in Java so a Java programmer should be able to reason about Go interfaces because they can understand the concept of a nullable pointer to a nullable pointer.
Please demonstrate by declaring a pointer to a pointer in Java. Ideally, you could also show how in Java to convert a pointer to a pointer to that pointer using a single assignment, without calling any constructors or functions or making any explicit casts.
> Ultimately, it seems that you're criticizing Go for having a different interface mechanism than Java, and you get bugs when you treat Go interfaces like Java interfaces. And Go made the right choice here, because it can express polymorphism over a wider range of types than just objects (contra Java interfaces) and indeed it doesn't have any sort of object/primitive bifurcation--from the perspective of Go interfaces, all concrete types are just data.
No, I'm criticizing Go for being fundamentally broken in a novel way that no language had come up with previously. It should seriously not be possible to cause a panic in a method call that is guarded by a nil check by changing the return type of some other function from a pointer-to-struct to an interface and making 0 other changes.
If you ask the language designers on the mailing list why it's like this, they'll tell you that it's important to make the zero values of types useful, including nil pointers of specific types. They also decided to ship a stdlib in which like half of the methods with pointer receivers immediately panic when passed nil, and they would like to tell you that the right way to avoid panicking is to not store such pointers in such interfaces. The language will do nothing to help you to avoid storing these pointers in these interfaces. Even if you successfully avoid storing these pointers in these interfaces in your code, if you store them in any interface at all, other code may use "interface upgrades" to find out that the methods that panic exist and call them. Wow!
You have it exactly backwards--you're arguing that Go should implicitly convert a not-nil interface (that is implemented by a nil pointer) to a nil interface so that Go interfaces behave more like Java interfaces, but Go isn't doing any implicit conversions (unless you consider "assigning a concrete value to an interface" to be an "implicit conversion", but Java does this too).
> implicit conversion from nil pointers to non-nil interfaces containing nil pointers
Again, Go doesn't do any such implicit conversion, and you're arguing the position that it should. An interface is just a tuple containing a pointer to the data and a pointer to the type `(pdata, ptype)`. A nil interface is `(nil, nil)`, and an interface that is implemented by a nil pointer is `(nil, <pointer-to-concrete-type>)`. You're arguing the position that the latter should be implicitly converted to the former, but this would break code that uses nil as a valid value.
The root cause of the problem isn't how Go handles nil pointers in interfaces, but rather that Go has nil pointers, interfaces, etc at all. Go gives you no way to express that a reference type can't be nil, but ideally it would have an enum system like Rust's that makes optionality an opt-in property via `Option<T>`. If an interface `Bar` is implemented by `*Foo` and neither `*Foo` nor `Bar` can be nil, then this problem largely goes away. If `Bar` is implemented by `Option<*Foo>`, then `Option<*Foo>`'s methods need to handle both the `Some(*Foo)` and `None` (i.e., nil) cases.
> that can only be checked for nil using reflection
You don't need reflection: