| # Go 1.2 Field Selectors and Nil Checks |
| |
| Author: Russ Cox |
| |
| Last updated: July 2013 |
| |
| Discussion at https://go.dev/issue/4238. |
| |
| Originally at https://go.dev/s/go12nil. |
| |
| Implemented in Go 1.2 release. |
| |
| ## Abstract |
| |
| For Go 1.2, we need to define that, if `x` is a pointer to a struct |
| type and `x == nil`, `&x.Field` causes a runtime panic rather than |
| silently producing an unusable pointer. |
| |
| ## Background |
| |
| |
| Today, if you have: |
| |
| ```Go |
| package main |
| |
| type T struct { |
| Field1 int32 |
| Field2 int32 |
| } |
| |
| type T2 struct { |
| X [1<<24]byte |
| Field int32 |
| } |
| |
| func main() { |
| var x *T |
| p1 := &x.Field1 |
| p2 := &x.Field2 |
| var x2 *T2 |
| p3 := &x2.Field |
| } |
| ``` |
| |
| then: |
| |
| * `p1 == nil`; dereferencing it causes a panic |
| * `p2 != nil` (it has pointer value 4); but dereferencing it still |
| causes a panic |
| * p3 is not computed: `&x2.Field` panics to avoid producing a pointer |
| that might point into mapped memory. |
| |
| The spec does not define what should happen when `&x.Field` is evaluated |
| for `x == nil`. |
| The answer probably should not depend on `Field`’s offset within the |
| struct. |
| The current behavior is at best merely historical accident; it was |
| definitely not thought through or discussed. |
| |
| Those three behaviors are three possible definitions. |
| The behavior for `p2` is clearly undesirable, since it creates |
| unusable pointers that cannot be detected as unusable. |
| hat leaves `p1` (`&x.Field` is `nil` if `x` is `nil`) and `p3` |
| (`&x.Field` panics if `x` is `nil`). |
| |
| An analogous form of the question concerns `&x[i]` where `x` is a |
| `nil` pointer to an array. |
| he current behaviors match those of the struct exactly, depending in |
| the same way on both the offset of the field and the overall size of |
| the array. |
| |
| A related question is how `&*x` should evaluate when `x` is `nil`. |
| In C, `&*x == x` even when `x` is `nil`. |
| The spec again is silent. |
| The gc compilers go out of their way to implement the C rule (it |
| seemed like a good idea at a time). |
| |
| A simplified version of a recent example is: |
| |
| ```Go |
| type T struct { |
| f int64 |
| sync.Mutex |
| } |
| |
| var x *T |
| |
| x.Lock() |
| ``` |
| |
| The method call turns into `(&x.Mutex).Lock()`, which today is passed |
| a receiver with pointer value `8` and panics inside the method, |
| accessing a `sync.Mutex` field. |
| |
| |
| ## Proposed Definition |
| |
| If `x` is a `nil` pointer to a struct, then evaluating `&x.Field` |
| always panics. |
| |
| If `x` is a `nil` pointer to an array, then evaluating `&x[i]` panics |
| or `x[i:j]` panics. |
| |
| If `x` is a `nil` pointer, then evaluating `&*x` panics. |
| |
| In general, the result of an evaluation of `&expr` either panics or |
| returns a non-nil pointer. |
| |
| ## Rationale |
| |
| The alternative, defining `&x.Field == nil` when `x` is `nil`, delays |
| the error check. |
| That feels more like something that belongs in a dynamically typed |
| language like Python or JavaScript than in Go. |
| Put another way, it pushes the panic farther away from the problem. |
| |
| We have not seen a compelling use case for allowing `&x.Field == nil`. |
| |
| Panicking during `&x.Field` is no more expensive (perhaps less) than |
| defining `&x.Field == nil`. |
| |
| It is difficult to justify allowing `&*x` but not `&x.Field`. |
| They are different expressions of the same computation. |
| |
| The guarantee that `&expr`—when it evaluates successfully—is always a |
| non-nil pointer makes intuitive sense and avoids a surprise: how can |
| you take the address of something and get `nil`? |
| |
| ## Implementation |
| |
| The addressable expressions are: “a variable, pointer indirection, or |
| slice indexing operation; or a field selector of an addressable struct |
| operand; or an array indexing operation of an addressable array.” |
| |
| The address of a variable can never be `nil`; the address of a slice |
| indexing operation is already checked because a `nil` slice will have |
| `0` length, so any index is invalid. |
| |
| That leaves pointer indirections, field selector of struct, and index |
| of array, confirming at least that we’re considering the complete set |
| of cases. |
| |
| Assuming `x` is in register AX, the current x86 implementation of case |
| `p3` is to read from the memory `x` points at: |
| |
| ``` |
| TEST 0(AX), AX |
| ``` |
| |
| That causes a fault when `x` is nil. |
| Unfortunately, it also causes a read from the memory location `x`, |
| even if the actual field being addressed is later in memory. |
| This can cause unnecessary cache conflicts if different goroutines own |
| different sections of a large array and one is writing to the first |
| entry. |
| |
| (It is tempting to use a conditional move instruction: |
| |
| ``` |
| TEST AX, AX |
| CMOVZ 0, AX |
| ``` |
| |
| Unfortunately, the definition of the conditional move is that the load |
| is unconditional and only the assignment is conditional, so the fault |
| at address `0` would happen always.) |
| |
| An alternate implementation would be to test `x` itself and use a |
| conditional jump: |
| |
| ``` |
| TEST AX, AX |
| JNZ ok (branch hint: likely) |
| MOV $0, 0 |
| ok: |
| ``` |
| |
| This is more code (something like 7 bytes instead of 3) but may run |
| more efficiently, as it avoids spurious memory references and will be |
| predicted easily. |
| |
| (Note that defining `&x.Field == nil` would require at least that much |
| code, if not a little more, except when the offset is `0`.) |
| |
| It will probably be important to have a basic flow analysis for |
| variables, so that the compiler can avoid re-testing the same pointer |
| over and over in a given function. |
| I started on that general topic a year ago and got a prototype working |
| but then put it aside (the goal then was index bounds check |
| elimination). |
| It could be adapted easily for nil check elimination. |