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: Safe navigation operator (?.) #42847
Comments
Please note that you should fill https://github.com/golang/proposal/blob/master/go2-language-changes.md when proposing a language change. |
A few points that I see in need of consideration for this proposal:
-- Personal opinion: I'd find this occasionally handy (e.g., I wrote this code yesterday, and I could have replaced 5 lines with just |
package main
type a struct{ *b }
type b struct{ *c }
type c struct{ d int }
func main() {
println(a{&b{&c{1}}}.d) // 1
println(a{}.d) // panic
} The goal is to replace nil pointer dereference panics with nil values of the expected type. I don't think there's any ambiguity in these scenarios about the type to which the expression should evaluate. Still seems feasible?
@gopherbot please remove label WaitingForInfo. |
The question is not what is simpler to implement, but what people reading code will naturally expect. It's hard for me to see that |
True. |
It seems I was mistaken about JavaScript. It looks like ECMA standardized that But at least according to these TypeScript docs, I agree with Ian that the JS behavior seems more natural (at least from a language implementor's perspective). But the TypeScript semantics do seem more useful, for the same reason Ian points out. (I assume you could write |
I think If the ?. proposal is passed, it would be good to realize the ternary ?: operator as well. hah |
It's worth noting that if we adopted this it would be the first use of |
Should we also add |
|
Using the current generic design draft, we could use a generic function for this, although the syntax is more awkward. func Deref[T any](p *T) T {
if p == nil {
var zero T
return zero
}
return *p
} This would be used as |
Is there a generics syntax for |
I'm not sure how serious you are, but I think it would be more consistent to do this with a double assignment, like with maps: v, _ := a[i] It's a bit more awkward to stick into the middle of a call where the zero value'll work fine, though. It requires a second line of code. Edit: You could also do this with a generic function, though: func At[T any](a []T, i int) T {
if (i < 0) || (i >= len(a)) {
var z T
return z
}
return a[i]
} This also has the benefit of automatically handling |
@mdempsky If I understand you correctly, that is |
If |
@ianlancetaylor True. |
@mdempsky Ah, OK, sorry for misunderstanding. But I don't think those semantics make sense for Go. |
Based on the discussion above, this is a likely decline. If generics are not added to the language, we can revisit. Leaving open for four weeks for final comments. |
I find eg of what compiler wants you to write: |
To make the safe navigation operator even more useful would be the addition of "nullable" types. I love how in typescript I can specify that a type can be nullable and then I'm forced to do a null check or use the safe navigation operator when using the object. If nullable types are implemented then my view of the safe navigation operator would be to return nil (in case of a nil pointer along the way) or the wanted value (when all pointers are declared), eg: type A struct {
x int
}
var obj *A | nil
println(obj?.x) // nil
nullableX := obj?.x // nullableX's type would be "int | nil" The takeaway would be that nullable types would directly impact the implementation of the safe navigation operator because it changes its return type The closest issue I could find regarding this would be #19412 |
I also want to mention, that generic return PharmacyNetworkRequest{
Title: order.GetPharmacyNetwork().GetTitle(),
Phone: strings.Join(order.GetPharmacyNetwork().GetPhones(), ", "),
Image: imageLinker(order.GetPharmacyNetwork().GetImages()),
DeliveredAtText: orderDeliveryAtFormat(order.Params.DeliveryAt),
} where methods func (o *Order) GetPharmacyNetwork() *PharmacyNetwork {
if o == nil {
return nil
}
return o.PharmacyNetwork
}
func (pn *PharmacyNetwork) GetTitle() string {
if pn == nil {
return ""
}
return pn.Title
}
func (pn *PharmacyNetwork) GetImages() []string {
if pn == nil {
return nil
}
return pn.Images
} If the navigation operator is accepted, we will be able to remove this strange code and replace it with return PharmacyNetworkRequest{
Title: order?.PharmacyNetwork?.Title,
Phone: strings.Join(order?.PharmacyNetwork?.Phones, ", "),
Image: imageLinker(order?.PharmacyNetwork?.Images),
DeliveredAtText: orderDeliveryAtFormat(order?.Params?.DeliveryAt),
} With return PharmacyNetworkRequest{
Title: Deref(Deref(order).PharmacyNetwork).Title,
Phone: strings.Join(Deref(Deref(order).PharmacyNetwork).Phones, ", "),
Image: imageLinker(Deref(Deref(order).PharmacyNetwork).Images),
DeliveredAtText: orderDeliveryAtFormat(Deref(Deref(order).Params).DeliveryAt),
} |
As some final input of mine: I think that my favorite feature of Kotlin, by far, is the |
There were additional comments, but no change in consensus. |
Hi, what about something like this ? cn, ok := order.Sender?.Addresses[0]?.ContactNumber[0]
or cn, ok := safe order.Sender.Addresses[0].ContactNumber[0] I think still better than var cn string
if (order.Sender) {
if (len(order.Sender.Addresses) > 0) {
if (len(order.Sender.Addresses[0].ContactNumber) > 0) {
cn = order.Sender.Addresses[0].ContactNumber[0]
}
}
} |
Typically you'd want to do something more along the lines of
|
Proposal
Add a new operator (?.) to support safe navigation.
Example
Current Behavior
Navigation across pointer of nil value causes runtime panic.
x.b.c
evaluates to 1y.b.c
panicsProposed Behavior
Safe navigation across pointer of nil value evaluates to nil value of same type as target property.
x.b?.c
evaluates to 1y.b?.c
evaluates to 0 (nil value of type similar toc
)Reasoning
Null property traversal is a common cause of runtime panic. Frequently developers must disrupt the flow of their program by adding
nil
checks in order to reproduce the behavior of a safe navigation operator. These nil checks add to the maintenance cost of the program by making the code less readable and introduce new opportunities for error.Current idiom
Proposed idiom
Similar features in other languages
See https://en.wikipedia.org/wiki/Safe_navigation_operator
Also known as
Go 2 language change template
Would you consider yourself a novice, intermediate, or experienced Go programmer?
Experienced
What other languages do you have experience with?
PHP, Javascript, Java, TCL, Bash, Actionscript, Erlang, Python, C++
Would this change make Go easier or harder to learn, and why?
Adds one more operator to learn which should feel familiar to people having experience with analogous operators in other languages popular among Gophers (C#, Ruby, Python, PHP, Typescript, Rust, Scala).
Has this idea, or one like it, been proposed before?
Not found in github issues. One slightly similar golang-nuts post from 2013.
If so, how does this proposal differ?
Previous discussion appears to relate specifically to null pointer receiver methods rather than nested struct traversal or command chaining.
Who does this proposal help, and why?
Developers moving to Go from languages that already support safe navigation.
What is the proposed change?
Add operator
?.
(see above).Please describe as precisely as possible the change to the language.
Add token
QUES_PERIOD = "?."
. Modify selector expression or add new safe selector expression to achieve behavior described above. Not valid on left hand side.What would change in the language spec?
Expansion of selector expression definition and operator list
Please also describe the change informally, as in a class teaching Go.
The safe navigation operator
?.
can be used to short circuit property traversal when a nil pointer is encountered, avoiding panic.Is this change backward compatible?
Yes.
What is the cost of this proposal? (Every language change has a cost).
Higher maintenance costs due to increase in number of operators and more complex selector expression code.
How many tools (such as vet, gopls, gofmt, goimports, etc.) would be affected?
Possibly several. Depending on implementation, maybe none.
What is the compile time cost?
Presumably low
What is the run time cost?
Presumably low
Can you describe a possible implementation?
Add new token
QUES_PERIOD = "?."
. Then either modifyast.SelectorExpr
or add newast.SafeSelectorExpr
Do you have a prototype? (This is not required.)
In development
How would the language spec change?
Expansion of selector expression definition and operator list
Orthogonality: how does this change interact or overlap with existing features?
Syntactically identical with
.
operator in selector expressions except where the value of the expression is nil (where the.
operator would panic)Is the goal of this change a performance improvement?
No.
Does this affect error handling?
No.
Is this about generics?
No.
The text was updated successfully, but these errors were encountered: