Question about the zero-value design: Why pointers zero value is not the zero value of what they points to?

404 views
Skip to first unread message

klos...@gmail.com

unread,
Feb 14, 2020, 9:28:47 AM2/14/20
to golang-nuts
This is a question to get some information before thinking about a possible proposal regarding zero values.

Could you please let me know the reasons why the zero value of a pointer is `nil` instead of a pointer to the zero value of what it points to?

Is it because of performance/memory? Simplicity in the runtime?

Example:
type Pet struct {
 name
string
 kind
string
}
type
Person struct {
 name    
string
 age    
int
 friends
[]Person
 pet    
Pet
}


// Current Go
var person1 Person // { name: "", age: 0, friends: [], pet: <nil> }
var person2 *Person // <nil>


// Assuming the zero value of a pointer is a pointer to the zero value of what it points to, then:
var person1 Person // { name: "", age: 0, friends:[], pet: &{ name: "", kind: "" } }
var person2 *Person // &{ name: "", age: 0, friends:[], pet: &{ name: "", kind: "" } }

Thanks!

Sam Whited

unread,
Feb 14, 2020, 10:01:52 AM2/14/20
to golan...@googlegroups.com
On Fri, Feb 14, 2020, at 09:16, klos...@gmail.com wrote:
> *Could you please let me know the reasons why the zero value of a
> pointer is `nil` instead of a pointer to the zero value of what it
> points to?*
>
> Is it because of performance/memory? Simplicity in the runtime?

The zero value is a zeroed chunk of memory with the given type. If
the zero value of the pointer were a pointer to something else, that
something else would have to be allocated and pointed too and the
pointer itself would lose the nice property of being a zeroed chunk
of memory (it wouldn't be a zero value).

—Sam

Brian Candler

unread,
Feb 14, 2020, 10:52:07 AM2/14/20
to golang-nuts
In addition, consider:  how would you implement the zero default when the type is self-referential? For example:

type Treenode struct {
  left *Treenode
  right *Treenode
}

var Tree1, Tree2 *Treenode

Also consider deeply nested types which include pointers to structs which contain pointers etc.

You can design a language where pointers can never be nil - but then you have to deal with "does not point to anything" some other way (e.g. Maybe values).  That ends up with a very different language.

klos...@gmail.com

unread,
Feb 15, 2020, 8:07:49 AM2/15/20
to golang-nuts
Thanks for the response!

and the pointer itself would lose the nice property of being a zeroed chunk
of memory (it wouldn't be a zero value).

I see it is a nice property, but I'd say only for people writing the compiler. I adventure to say that people using the language won't care too much about this property.
Having a useful zero-value (in this case, an initialized pointer),  like we have with strings for example, would be a very nice property for language users

klos...@gmail.com

unread,
Feb 15, 2020, 8:13:40 AM2/15/20
to golang-nuts
Woah! that's a killer reason, the one I was searching for. Thanks for pointing it out, as you have saved me a lot of time discarding the proposal I had in mind.

I will need to go in other direction.

Tristan Colgate

unread,
Feb 15, 2020, 8:51:32 AM2/15/20
to klos...@gmail.com, golang-nuts
It's also worth pointing out that a nil pointer value can still be useful. You can still call methods on it.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/e2969605-f82f-4b38-a390-87539c9f3041%40googlegroups.com.

Jake Montgomery

unread,
Feb 15, 2020, 2:03:55 PM2/15/20
to golang-nuts
I see it is a nice property, but I'd say only for people writing the compiler. I adventure [sic] to say that people using the language won't care too much about this property. Having a useful zero-value (in this case, an initialized pointer),  like we have with strings for example, would be a very nice property for language users

I think that Tristan was pointing out that nil can be a useful zero value, since you can still call methods on a nil struct, if the method was written to handle it. Likewise nil slices are pretty much as useful as empty slices, etc.  

klos...@gmail.com

unread,
Feb 17, 2020, 5:18:25 AM2/17/20
to golang-nuts
I thought of this at the beginning, but when I tried to make it work I realized that I needed to have the following if:
if s != nil {
 
/* initialize struct or inform it is nil */
}
 


on every struct method, which eventually defeated the purpose for my use case.

Out of curiosity: Could you please tell when calling methods on nil pointers is useful?


El sábado, 15 de febrero de 2020, 13:51:32 (UTC), Tristan Colgate escribió:
It's also worth pointing out that a nil pointer value can still be useful. You can still call methods on it.

On Sat, 15 Feb 2020, 13:13 , <klos...@gmail.com> wrote:
Woah! that's a killer reason, the one I was searching for. Thanks for pointing it out, as you have saved me a lot of time discarding the proposal I had in mind.

I will need to go in other direction.

El viernes, 14 de febrero de 2020, 15:52:07 (UTC), Brian Candler escribió:
In addition, consider:  how would you implement the zero default when the type is self-referential? For example:

type Treenode struct {
  left *Treenode
  right *Treenode
}

var Tree1, Tree2 *Treenode

Also consider deeply nested types which include pointers to structs which contain pointers etc.

You can design a language where pointers can never be nil - but then you have to deal with "does not point to anything" some other way (e.g. Maybe values).  That ends up with a very different language.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golan...@googlegroups.com.

klos...@gmail.com

unread,
Feb 17, 2020, 5:25:42 AM2/17/20
to golang-nuts
Nil slices are an example of an ideal zero value. When you use them, you don't need to check for nil -> its zero value is already usable.

Exactly the "slices" case (and the "string" type) is the one inspiring me to search for more useful zero values for certain types. The two I am thinking more of lately are:
  • Pointers, the reason for this thread
  • Maps.  I would love maps to have a useful zero value similar to slices. This way, and among other reasons, I would not need to write many constructor-like functions ("func NewX") I write now because my structs have a field that is a map (and hence, needs to be initialized to avoid nil pointer errors).

Brian Candler

unread,
Feb 17, 2020, 8:40:36 AM2/17/20
to golang-nuts
A "usable" nil map, i.e. being able to insert into it straight away, I sympathise with.  Remember though that you can't append "in place" to a nil slice: it returns a new slice object.  https://play.golang.org/p/dL-r74C5m_w

I presume you don't want to write the same for maps as you do for slices:

m2 := m.set(42, "hello")   // instead of: m[42] = "hello"

I suppose in principle it might be possible to pre-initialize all variables and structs which contain a map to a dynamically-allocated instance of that map type.

However, I don't think the idea extends to pointers:

1. you are asking for all pointers to be pre-initialized to some dynamically-generated object, which might need to be garbage-collected immediately the pointer is changed to point to something else.  That would be very wasteful.

2. there's the problem with self-referential pointers, e.g. A contains *B and B contains *A.

3. you lose the "nil-ness" property of pointers: the ability of pointers to point to nothing at all (such as the tree example I gave).  If you could no longer point to nothing, how could you tell the difference between an interior node and a leaf node of a tree?  You would have to change all code to use, say, a slice of pointers (of size 0 or 1) instead of a pointer.

Finally, what would you do about nil interface variables? You don't even know what *type* the value should have.

Axel Wagner

unread,
Feb 17, 2020, 9:07:11 AM2/17/20
to klos...@gmail.com, golang-nuts
On Mon, Feb 17, 2020 at 11:18 AM <klos...@gmail.com> wrote:
Out of curiosity: Could you please tell when calling methods on nil pointers is useful?

In general, linked datastructures - like linked lists or trees. As a somewhat trivial example, consider this:
Interpreting a nil-pointer as "an empty tree" makes a lot of the traversal-algorithms easier to express.
I also have an internal package that wraps the openat/mkdirat/linkat/… class of syscalls, to make it possible to still read/write a directory that has been bind-mounted over. It uses a struct to encapsulate the file-descriptor of that directory, which is used as a pointer to keep track of whether it has been closed and such. It also interprets a nil-pointer as "relative to the current directory" (it uses AT_FDCWD). I could, of course, also use the zero-value of that struct for that (in fact, I do as well), but as there is a meaningful way to interpret a nil-pointer, there's no harm in doing so as well.

In general, it's of course never *necessary* to interpret a nil-pointer a specific way. But it does sometimes make things easier to express or nicer to use.

BTW, as far as slices are concerned: They behave strikingly similar to maps, as far as the zero-value is concerned - in that all read-operations work just fine on both a nil-map and a nil-slice, it's just that write-operations panic (and again, in both cases). The main difference between them is that *non*-nil maps behave very differently. We don't tend to notice the "uselessness" of nil-slices in general, though, because we tend to either assume a certain size for them (in which case they can't be nil) or we check the length beforehand (just like checking if a map is nil) when writing to them. i.e. the read-operations are far more common with slices and we find the idea of "checking" their nil-ness via ranging over them or explicitly checking their len somewhat more natural.

Michel Levieux

unread,
Feb 17, 2020, 10:36:43 AM2/17/20
to Axel Wagner, klos...@gmail.com, golang-nuts
Hi Kloster08,

In addition to what others have already pointed out, I'd like to bring you another case that seems problematic to me concerning the construction of a "usable nil value" for maps, in the sense that a zero-value map could be written and read right away without any initialization.


Notice in this example that a nested map would not have the same behaviour as a non-nested one if they can't be allocated properly, which adds complexity to the language.
Also, how would the compiler know if the value retrieved is going to be read-only (I have a huge number of examples coming to mind where this would be the case), in which case one *does not* want the map to be allocated (the current behaviour is perfect for the use), or if it will be written at some point in the future.

Some workarounds might exist, but I think it'd either be irrelevant a win (if a win it is) and a tremendous amount of work in comparison. Let aside all code that relies on the current behaviour of nil maps, be it a "good" or "bad" practice, and that would break because of this kind of change in the language.

Hope this helps,

M.Levieux

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAEkBMfHAfA97soJy1iXea1H7Bkv_VO%3D43cLY96zi%2BZ7R0a%3DbTQ%40mail.gmail.com.

Sam Whited

unread,
Feb 17, 2020, 12:23:57 PM2/17/20
to golan...@googlegroups.com
I think you need to run your example; the behavior is the same: trying
to make an assignment to the non-nested map will panic contrary to what
your comment says.

> panic: assignment to entry in nil map

—Sam

On Mon, Feb 17, 2020, at 10:35, Michel Levieux wrote:
> Hi Kloster08,
>
> In addition to what others have already pointed out, I'd like to bring
> you another case that seems problematic to me concerning the
> construction of a "usable nil value" for maps, in the sense that a zero-
> value map could be written and read right away without any
> initialization.
>
> Here is the example: https://play.golang.org/p/t5RsM1JY3eQ
>
> Notice in this example that a nested map would not have the same
> behaviour as a non-nested one if they can't be allocated properly,
> which adds complexity to the language. Also, how would the compiler
> know if the value retrieved is going to be read-only (I have a huge
> number of examples coming to mind where this would be the case), in
> which case one *does not* want the map to be allocated (the current
> behaviour is perfect for the use), or if it will be written at some
> point in the future.
>
> Some workarounds might exist, but I think it'd either be irrelevant a
> win (if a win it is) and a tremendous amount of work in comparison.
> Let aside all code that relies on the current behaviour of nil maps,
> be it a "good" or "bad" practice, and that would break because of this
> kind of change in the language.
>
> Hope this helps,
>
> M.Levieux
>
> Le lun. 17 févr. 2020 à 15:06, 'Axel Wagner' via golang-nuts <golang-
> nu...@googlegroups.com> a écrit :
> > nuts+uns...@googlegroups.com. To view this discussion on the
> > web visit
> > https://groups.google.com/d/msgid/golang-nuts/CAEkBMfHAfA97soJy1iXea1H7Bkv_VO%3D43cLY96zi%2BZ7R0a%3DbTQ%40mail.gmail.com
> > <https://groups.google.com/d/msgid/golang-nuts/CAEkBMfHAfA97soJy1iXea1H7Bkv_VO%3D43cLY96zi%2BZ7R0a%3DbTQ%40mail.gmail.com?utm_medium=email&utm_source=footer>
> > .
>
> --
> You received this message because you are subscribed to the Google
> Groups "golang-nuts" group. To unsubscribe from this group and stop
> receiving emails from it, send an email to golang-
> nuts+uns...@googlegroups.com. To view this discussion on the web
> visit
> https://groups.google.com/d/msgid/golang-nuts/CANgi334t%3DNz_7jH7%3D1KZacHi_OfDsq94-dUFpgMf_oKnsa%3D2Mw%40mail.gmail.com
> <https://groups.google.com/d/msgid/golang-nuts/CANgi334t%3DNz_7jH7%3D1KZacHi_OfDsq94-dUFpgMf_oKnsa%3D2Mw%40mail.gmail.com?utm_medium=email&utm_source=footer>
> .

--
Sam Whited

Levieux Michel

unread,
Feb 17, 2020, 1:40:52 PM2/17/20
to golang-nuts
@Sam, hi, yes, that was the point ^^ My example was here to say "ok, so let's say the first case works because we have made uninitialized maps work, but how do we handle the second case?"
It was not made to be runnable as is, I just wanted it to be readable :)

Sorry if that was not clear

To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/929dfda3-36e3-4bd1-85cb-47f55b6b1e11%40www.fastmail.com.

Brian Candler

unread,
Feb 17, 2020, 2:51:37 PM2/17/20
to golang-nuts
That's a very good point:

* A map can contain any type of value
* map[value_not_present] is defined to return the zero value
* If the map contains other maps:
(a) you don't want a new map to spring into life every time you access a non-existent key - especially not a floating map which isn't stored in the parent map
(b) people will *test* for the zero value (v == nil)

Even if you were to panic for a non-existent key, and you forced people to use the two-value form, you still need *some* value to return when the element is missing.

v, ok := map[key]
if not ok {
    // what value does 'v' have here?
}

So nil maps actually perform an important role.

Rick Hudson

unread,
Feb 17, 2020, 4:11:44 PM2/17/20
to golang-nuts
> type Treenode struct {
>  left *Treenode
>  right *Treenode
> }

One could of course design a language where Treenode is called cons and left is called car and right is called cdr and (car nil) is nil and (cdr nil) is nil. You could implement such a language by putting 2 words of 0 at location 0 and add a write barrier or page protection to enforce nil immutable.

One could do all of this but Lisp did it a long time ago.


klos...@gmail.com

unread,
Feb 18, 2020, 6:09:28 AM2/18/20
to golang-nuts
Regarding pointers: For me, point number 2 (and 3) is the key that forces us having some way of expressing emptiness in a pointer. I would say that point 1 could be optimized by the compiler
Regarding interfaces: Yep! nil-interfaces are a different beast. That's the reason I focused on just one nillable type (pointers) this time and, especially, left nil-interfaces away.

klos...@gmail.com

unread,
Feb 18, 2020, 6:19:50 AM2/18/20
to golang-nuts
Thanks for the example, it is a really good one. At least for me, it is the first one where I see the real usefulness.

I don't know why, but eventually, I was able to see the parallelism between maps and slices. All the confusion comes because we have "append" for slices and not for maps (because of semantics). "append" handles the nil-case for us, while for maps we always have to handle the nil-case ourselves.

Jan Mercl

unread,
Feb 18, 2020, 6:20:21 AM2/18/20
to klos...@gmail.com, golang-nuts
On Tue, Feb 18, 2020 at 12:09 PM <klos...@gmail.com> wrote:
>
> Regarding pointers: For me, point number 2 (and 3) is the key that forces us having some way of expressing emptiness in a pointer.

I'd say that nil pointers are not really special. It's just a sentinel
value. And we definitely need some sentinel value to mark the end of a
linked list, for example. If a language gets rid of nil/null
sentinels, it has to chose some other sentinel value for marking the
.next field as not being actually a next item in the list. So it does
not really solve much, if anything at all. And it enables completely
new bugs to exists in exchange for avoiding the nil/null pointer
dereference exception. Additionally it loses the nice and cheap HW
support for detecting of using the sentinel value on most CPUs.

klos...@gmail.com

unread,
Feb 18, 2020, 6:44:16 AM2/18/20
to golang-nuts
Well, other languages use the optional/maybe type. It handles sentinel values pretty well and I don't think they bring new kind of bugs (while they remove the nil-pointer related bugs). 
It is true that they can be a bit cumbersome to use and maybe we lose what you said: "the nice and cheap HW support for detecting of using the sentinel value on most CPUs"

However, I normally lean a bit more towards program correctness than to speed of execution, nice compiler code, or HW support. Others might have different preferences.

The problem with nil-errors struck me hard in Go when I realized that, no matter how many "recovers" I write in my server app, if a goroutine is spawned and hits a nil pointer dereference, the whole server crashes. There is no protection inside the language against this (let me know if there is a way)

So here I am trying to gather as much information as possible to see if, first, more people are in a similar situation as me, and second, learn about the design decisions regarding this topic so that I might be able to come up with a proposal that makes sense for the current state of Go.

Volker Dobler

unread,
Feb 18, 2020, 6:58:26 AM2/18/20
to golang-nuts
On Tuesday, 18 February 2020 12:44:16 UTC+1, klos...@gmail.com wrote:
Well, other languages use the optional/maybe type. It handles sentinel values pretty well and I don't think they bring new kind of bugs (while they remove the nil-pointer related bugs). 

That is the market claim. And typically for such claims it does not hold up.

V.

Jesper Louis Andersen

unread,
Feb 18, 2020, 7:16:41 AM2/18/20
to Rick Hudson, golang-nuts
On Mon, Feb 17, 2020 at 10:11 PM Rick Hudson <r...@golang.org> wrote:
> type Treenode struct {
>  left *Treenode
>  right *Treenode
> }

One could of course design a language where Treenode is called cons and left is called car and right is called cdr and (car nil) is nil and (cdr nil) is nil. You could implement such a language by putting 2 words of 0 at location 0 and add a write barrier or page protection to enforce nil immutable.


Roughly also what happens in e.g., OCaml. If you define

type 'a tree = Leaf | Node of 'a * 'a tree * 'a tree

then the variant is represented with leaf being the integer 0 and node being a box with integer tag 1. If we follow that up with

type x = Foo | Bar

then Foo has tag 0 and Bar has tag 1, reusing the tag counters. The type system ensures we cannot accidentally use something of type x in lieu of 'a tree and vice versa.

The important part is that the representation is somewhat hidden from us as programmers. The system decides upon one, and uses it consistently. It is one of the defining characteristics of Go that this won't happen in the language (where representations are often quite explicit and chosen by the programmer. This comes with certain advantages in that the memory layout is known).

As for hash tables in OCaml, they have no "nil" representation and are plain old structs. I guess this leans much the same way as the Go representation.

Jan Mercl

unread,
Feb 18, 2020, 7:22:38 AM2/18/20
to klos...@gmail.com, golang-nuts
On Tue, Feb 18, 2020 at 12:44 PM <klos...@gmail.com> wrote:

> Well, other languages use the optional/maybe type.

The CPU does not care how humans call the abstraction. It has still to
do the same old thing: check some value for being equal/not equal to a
sentinel value or check a tag value for the same effect. That means
additional cost wrt to memory consumption. Additionally, now the
compiler may enforce the check everywhere when humans may know better.
So it buys more safety, undeniably, but as always, there are no free
lunches.

> The problem with nil-errors struck me hard in Go when I realized that, no matter how many "recovers" I write in my server app, if a goroutine is spawned and hits a nil pointer dereference, the whole server crashes. There is no protection inside the language against this (let me know if there is a way)

I'd say that's not solving the problem, but making it bigger. Do not
recover from nil panics, instead fix the nil dereference bug and avoid
the panic completely.

Jesper Louis Andersen

unread,
Feb 18, 2020, 7:28:51 AM2/18/20
to Volker Dobler, golang-nuts
If you have

type 'a option = None | Some 'a

you have a so-called "lift" where "None"  is a new value playing the role as no-information or "null". This allows you use the full range of 'a whatever that is, because you glued a new null-value to it.

However, the problem you may face is that a lot of programming *do* need a null value. So you end up having to handle these values quite a lot in your program. Of course, as you grow better at detecting this, you seek to untangle an option type early on in your program flow such that you avoid those pesky null checks, and case/switch/match splits in the program. But there are data out there for which this is hard to do correctly. Also for the fact that the rest of the world is littered with data representations in which null occurs all over the place.

The option type is theoretically stronger because the programmer can choose when they need a nullable value and when they don't. It is usually easier to add something (i.e., a null) than it is to remove it. This means it carries more information to be exploited by the compiler and users of the type. In practice, the world has miserable handling of data, and we spend a considerable amount of time trying to get it pristine for use in our programs. Thus, the value diminishes somewhat. However, if you manage to carve out a small bubble in which you have absolute control over the data, you are definitely in for a treat.


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.


--
J.

Jesper Louis Andersen

unread,
Feb 18, 2020, 7:37:11 AM2/18/20
to Jan Mercl, klos...@gmail.com, golang-nuts
On Tue, Feb 18, 2020 at 1:22 PM Jan Mercl <0xj...@gmail.com> wrote:

The CPU does not care how humans call the abstraction. It has still to
do the same old thing: check some value for being equal/not equal to a
sentinel value or check a tag value for the same effect. That means
additional cost wrt to memory consumption. Additionally, now the
compiler may enforce the check everywhere when humans may know better.
So it buys more safety, undeniably, but as always, there are no free
lunches.


This is the reason I prefer to unwrap option types as much as possible in program flows. You avoid a lot of checks which the programmer has to write. And the compiler can be dead sure things cannot be null, so it doesn't have to insert a check. But it often requires you to think hard about your data representation. After a while, you can write down a type in e.g., Haskell, Rust, or OCaml and immediately have a feel for how the program flow will be. This allows you to abort certain representations early because you know they'll be a mess to work with.

I think the best example that is similar I can give is that you can have a JSON structure with some RFC3339 date in it. In Go, you could pass that around in your program as a string, but it quickly becomes a mess. If you, on the other hand, convert that into a value of type time.Time, you immediately gain all of Go's stdlib tooling for manipulating time, and a lot of corner cases are eliminated. You simply chose a better representation. Null values follow the same scheme are not something special that needs special handling. In fact, they are just a special case of a variant/sum type.

klos...@gmail.com

unread,
Feb 18, 2020, 10:56:17 AM2/18/20
to golang-nuts
I'd say that's not solving the problem, but making it bigger. Do not 
recover from nil panics, instead fix the nil dereference bug and avoid 
the panic completely. 

It is never that easy in a medium to big application with a 10+ people team working on it. 
The problem with that reasoning is that you could use it for anything and then we would never improve any programming language or create others. Ideally, the compiler will catch all the bugs it possibly can so that the programmer can spend the thinking energy on resolving the problem. 
For example, Go could have perfectly followed the same approach as C for the arrays: rely on the user to pass the length everywhere and forget about protecting against out-of-bounds accesses. The designers decided not to do so, and thanks to that, you can forget about memory corruption and focus on what matters.
They could have thought: "Hey, fix all the memory corruption bugs and you will not have those problems". But that's something really hard to accomplish.

I would love to be able to forget about nil-pointer dereference errors. Sure! I can add checks everywhere, but this means I need to think about this, and that is precious energy that doesn't go to solving the problem I really want to solve.
Being said this, I know it is not an easy problem to solve and there are always trade-offs. But, hey!, maybe there is a good solution hidden somewhere that fits perfectly in Go. Let's find it out!

Robert Engels

unread,
Feb 18, 2020, 11:30:12 AM2/18/20
to klos...@gmail.com, golang-nuts
Code that is subject to null dereferences, it also susceptible to any incorrect dereference - (e.g. look up in a map using the the wrong key (not just type)) - the program may not crash, but produce incorrect results - which is arguably worse.

There is no silver bullet to solve bad dereferences.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Jake Montgomery

unread,
Feb 18, 2020, 2:14:07 PM2/18/20
to golang-nuts
The problem with that reasoning is that you could use it for anything and then we would never improve any programming language or create others. Ideally, the compiler will catch all the bugs it possibly can so that the programmer can spend the thinking energy on resolving the problem.
 
That is a good point. However, in reality, creating a language is about trade offs. There may be a place for a language that does not allow nil pointers, but it is a trade off that the designers of go decided not to make.
 
 
I'd say that's not solving the problem, but making it bigger. Do not 
recover from nil panics, instead fix the nil dereference bug and avoid 
the panic completely. 

It is never that easy in a medium to big application with a 10+ people team working on it.

I have to agree with Jan here, that is a bad idea. If you have a bug, it has to be fixed, not covered up. It may not always be "that easy", but it is always necessary.  And if you are finding it to be consistently difficult to find and fix nil dereference bugs, then you probably have a problem with your culture or code design that really is not about Go at all.

I have worked on large go projects with many developers, and frankly nil pointer dereference was not a significant source of bugs. If it is for your team, then maybe you need to look at the bigger picture. There are a number of things you can do, both technically and culturally to reduce this. Some general ideas are below. You may be already be doing some or all of these ...
  • On the team level, I would start with the fact that you have 10+ people on a team. If you actually have 10 programmers all working on the same code, then IMHO, you are bound to fail. Break the code up into separate projects with clearly defined boundaries, so you can have smaller teams responsible for their own code.
  • Null pointer references are often going to be the result of poor detail design. (More about the technical thing you can do later). I have found that thorough code reviews of all commits are invaluable. At least one senior engineer should be on every code review, and preferably every team member will at least look at the commit, and the code review comments. This seems like a big burden, and it is, but it pays off many fold. There will be fewer bugs, better overall design, and better low level design, like function signatures and documentation that help prevent these problems. But most importantly, everyone learns from everyone else, and code consistency and hygiene continually increases. My experience is that bugs like null pointer defer will be reduced or disappear.
  • Of course, the item above relies on each team having one or more talented programmers to lead the way. And there are simply some folks who are incompetent, and will never write reliable code. But even "middle of the road" programmers can write reliable code in go, with the proper culture and guidance.
  • Your code should have enough high level tests, so that the code is thoroughly exercised before going into production. Most nil dereference bugs should show up there.
Technically, there are some things that will help with null pointers. This is where the code reviews come in, to create a culture of good practices. A few are ...
  • To start, don't use pointers without a justification. The default should always be to pass around objects by reference, not pointers. If a structure needs to contain another, maybe include it directly, instead of using a pointer, etc. Some developers will automatically use pointers "for efficiency", but in most cases there is no benefit, or at least not significant enough to warrant the premature optimization. Of course, there are places where pointers are necessary.
  • If a structure must contain a pointer, and the code is not going to be written to allow it to be nil, then document that in the structure, and create a "New" function that must be used to create instances of the structure.
  • Any function that takes a pointer should be aware that it could be nil. If nil is not allowed, then be sure to indicate that in the function documentation. If the nil is passed it should panic early. That may mean putting a nil check, and panic at the function start, but in practice that is rarely actually needed.
For example, Go could have perfectly followed the same approach as C for the arrays: rely on the user to pass the length everywhere and forget about protecting against out-of-bounds accesses. The designers decided not to do so, and thanks to that, you can forget about memory corruption and focus on what matters.
 
Yes and no. You may not have to worry about "memory corruption", but you still need to worry about accessing out of bonds on a slice, just like C. Otherwise you get a panic. The consequence is more immediate, and less of a security threat in Go than C. But the burden on the coder is still the same ... to make sure that your access is in bounds. Go gives you the information you need; the length is available. But the coder still needs to check it. It is really the same for nil - the information is there, the code just needs to check it.

Good Luck.

klos...@gmail.com

unread,
Feb 19, 2020, 5:59:33 AM2/19/20
to golang-nuts
Thanks a lot for your response. All your points make sense and I can understand your point of view. I simply have a slightly different point of view that my experience has shaped.

I see in a programming language as my most important tool. I use it every single day to make a living. It is because of that importance that I want me (and my team) to be as efficient as possible when working with it, so every detail matters a lot. When you create small short-living applications, it is ok if the language is not perfect. But when you create applications that must work every single minute and they are so big that you need a team of more than 10 people (yes, they are split and the application uses microservices/serverless), then you care a lot about the nuances of the programming language.

Imagine you need to create a wood dog house for your dog. You are probably fine using a simple hammer and nails.
But if you create wood houses for people, you don't even think of using a hammer! You will use a much more reliable tool. Or if you use it, it will probably be the best hammer in the market, with a perfect weight balance, with an amazingly ergonomic handle, etc.

But don't get me wrong. I love Go and the trade-off the Go team has chosen. It is very refreshing seeing such a simple and useful language. There are others that already have this nil-pointer problem resolved, like Rust, Haskell, etc. but they are too complex and, as I mentioned, the thinking energy goes to "fighting" with the language, not to the problem you want to solve.

It is because I like so much the way Go approaches programming that I would love to polish those edges that, in my opinion, do make a difference. 
Maybe there is no good solution to this while keeping the actual balance Go has, but I'm willing to try.

Volker Dobler

unread,
Feb 19, 2020, 6:45:25 AM2/19/20
to golang-nuts
On Wednesday, 19 February 2020 11:59:33 UTC+1, klos...@gmail.com wrote:
[...]
But if you create wood houses for people, you don't even think of using a hammer! You will use a much more reliable tool. Or if you use it, it will probably be the best hammer in the market, with a perfect weight balance, with an amazingly ergonomic handle, etc.

Sorry, No. You still use conventional hammers.

You do not use bad tools, but nothing fancy either because
there is no such thing as "perfect weight balance" of a
hammer and no "amazingly ergonomic handle[s]" as you
and your coworker will have different hand sizes and
sometimes you will be wearing gloves and sometimes you
will be forced to grip the hammer near to its head because
you operate in confined space.

So basically you use the exactly same hammer to build
a dog house as for a three store wood house.

(There have been advances in hammer technology, e.g.
special kinds of plastic heads which do not stretch metal
and are more durable than wood, but nothing that fancy
like you seem to assume.)

Tooling must be robust. Low and medium grade tooling
is more robust than hightech tooling.

V.

Brian Candler

unread,
Feb 19, 2020, 11:32:11 AM2/19/20
to golang-nuts
On Wednesday, 19 February 2020 10:59:33 UTC, klos...@gmail.com wrote:
I see in a programming language as my most important tool. I use it every single day to make a living. It is because of that importance that I want me (and my team) to be as efficient as possible when working with it, so every detail matters a lot. When you create small short-living applications, it is ok if the language is not perfect.
... 
It is because I like so much the way Go approaches programming that I would love to polish those edges that, in my opinion, do make a difference. 

You seem to be implying that the Go language designers have not thought about these "rough edges".

Perhaps if you read some of the Go history, and the design documents, you will find that these issues *have* been considered, in very great detail, and there are very good reasons for how things were chosen today.  Not because they couldn't be bothered to think of the perfect solution, but because in fact the solutions they have come up with have been very carefully chosen indeed.

Go came from Google.  Google has thousands of programmers.  It is in Google's interest for the language to be as usable, understandable, efficient and productive as possible.

Michel Levieux

unread,
Feb 19, 2020, 12:08:32 PM2/19/20
to golang-nuts
Though I think I *do* agree with you on that particular point (zero-value design), I'd say this argument is rarely a real one. Let me explain myself: However clever Google's thousands of programmers are, there might be places for improvement and I personally want people to try and propose evolutions they consider positive for the language and everything gravitating around it, so we can discuss it together (I mean the community, of course) . That part seems to be a dead end (at least from the angle we have taken here), but if every time someone had a thought about a way to improve the language, the first thing they'd say was "Google's programmers have already thought about that so there's nothing to be done", Go wouldn't be the language it is today. There will always be "rough edges", particularly because the concept of "being a programmer" evolves in itself.

So, yes, and I honestly think you didn't mean we should fall in the other extreme behaviour, but I just wanted to state that, IMO, that is the last argument to consider. :)

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Brian Candler

unread,
Feb 19, 2020, 3:08:14 PM2/19/20
to golang-nuts
I just don't like the implication that these things *haven't* been thought about, because nobody could be bothered to polish the "rough edges".
Reply all
Reply to author
Forward
0 new messages