Comments

edited

Apologies if this has been suggested before; I've been unable to find an issue.

Currently go has many basic types where the zero value is nil. nil is a huge source of bugs, and a potential Go 2, where backwards-incompatible changes are on the table, offers an opportunity to make the language more robust by reducing its scope.

Specifically, in some cases there is an obvious zero-value that makes more sense than nil. Off the top of my head:

The zero value for slices could be a slice with length and capacity of zero.

The zero value for maps could be an empty map.

The zero value for channels could be an unbuffered channel.

This is more in keeping with the design principle of making the zero value useful.

There isn't an obvious good zero value that I can think of for pointers and interfaces, but even this partial reduction would make it easier to build reliable software.

This comment has been minimized.

I've definitely run into bugs from nil slices (less so channels, but it has happened, and it seems like a straightforward fix). Agree that maps and (especially) pointers are a much bigger problem. Killing nil pointers would be huge but I don't know how to do it while still having zero values.

This comment has been minimized.

This comment has been minimized.

It's worth noting that nil channels have useful properties in select statements: sending or receiving on a nil channel always blocks, so nil-ing out a channel before doing a select effectively removes cases involving that channel from consideration, allowing one to dynamically enable or disable cases at runtime.

That said, it's definitely annoying to have to explicitly initialize channels.

This comment has been minimized.

One of the advantages of the current approach is that the zero values for all types is the value with all bits zero. This makes it easy to initialize variables. Of course that is not an absolute requirement; we could change that with some loss of efficiency. But it's the main reason that nil channels are not useful.

This comment has been minimized.

I don't feel as strongly about channels, and apparently I was just plain mistaken re: slices (though it would probably be more intuitive for them to be non-nillable; most of the code I've seen in the wild that cares about nil slices seems to be written under the assumption that the same problems crop up as with pointers).

Making maps non-nillable seems like it is a very big win for robustness though.

This comment has been minimized.

@networkimprov, that doesn't fix the problem; if it's an unboxed struct and you assign it, then use one copy or the other, they end up being different maps, since initializing one pointer doesn't initialize the other.

Doing up-front allocation would at least have the upside of allowing the nil check to always be omitted. Probably still a loss overall wrt perf.

This comment has been minimized.

edited

@neganovalexey
I think slice is not a reference ,but map is a reference is also a source of bug.
How about add another map like smap which is not a reference, and make it default-empty, and leave the origin map as it is.
This way may even save some allocs in some cases.

This comment has been minimized.

I think the most important thing to keep in mind for any changes to nil-able things is consistency.

One thing that is consistent about the current semantics is that all nil-able (atomic) things are in some sense a reference or pointer and vice versa: all reference or pointer (atomic) things are nil-able. If you start making exceptions to that, then there are more rules to keep in mind and more clutter of specifications.

There are of course several reasons to have and not to have nil. Sometimes nil really is needed, but at other times it is not, and I think that this proposal does a good job of illustrating a couple places where nil really isn't needed

There are of course several reasons to have and not to have nil. Sometimes nil really is needed, but at other times it is not, and I think that this proposal does a good job of illustrating a couple places where nil really isn't needed

Ok, and regarding memory use? If you for example define very BIG map of LARGE objects when you want to allocate memory? Already on definition (because it will be zeroed with this example) and you need to have CAP to put objects there or later on first insert, what will generate unnecessary delay to reserve memory and then put new object there?

What about garbage collector? When we should consider that this LARGE object (slice, map) is not needed? What if developer will declare it but will not use it? Once again shout that this has not been used but defined? With existing scenario impact will be very low. On zeroed map the memory needs to be reserved for inserting.

Finally what is faster: checking that slice, map and channels have a some length or they are NIL?

This comment has been minimized.

Others have pointed out the perf related issues with nil maps, and those are valid. The other cases are kindof moot; I'd misunderstood the semantics in the first place. Channels have useful behavior that I hadn't realized. I no longer feel strongly about this proposal either way.

Finally what is faster: checking that slice ... have a some length or they are NIL?

Per discussion above, slices already do what I suggested. The check should be exactly the same; either way you're pulling out an integer-sized field and comparing it to zero.

I think it would make sense to have nil not be something that can be compared/assigned to slices, just for clarity -- I know I'm not the only person who's misunderstood this.

This comment has been minimized.

edited

@mateuszmmeteo making nil map valid by itself does not remove the ability to make maps with required capacity. Just like with slices, you either use the default or make the one you want.

Performance is a question of implementation, not semantics. Maps could be lazily allocated to avoid unnecessary garbage when it's not used. Again, like slices. The problem is that with slices we have slice = append(slice, ...) and that maps really well to lazy allocation. With maps we have m[key] = value and it's not obvious how would you allocate new map here.

Finally what is faster: checking that slice, map and channels have a some length or they are NIL?

For slices - the same. Slice is a struct with three fields - pointer, length and capacity. Nil slice check extracts pointer and checks it for nil. Exactly the same as extracting the length and checking it for zero. Again, that's an implementation question and necessary optimization can be done there.

This comment has been minimized.

nil maps drive me nuts. They're the largest source of "constructor" functions in my code. If I really want reference semantics for a map, I could always use *map[K]V. 👍 to maps having useful zero values and, if necessary, losing their reference semantics.