Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Thing is, it's not an easy problem to solve while retaining the simplicity. It means that now you need to have rules in the language for definitely-initialized fields (since you can't just default-init them to null as there is no null). Which means that you need full-fledged constructors for everything, and syntax for invoking them in all cases where they may be needed - e.g. think about what should happen if you try to create an array of structs with ref fields in them.

This is all solvable - C++ and Rust both do it, for example. But it introduces a lot of complexity to the language.



> It means that now you need to have rules in the language for definitely-initialized fields (since you can't just default-init them to null as there is no null).

You don't need rules, you just don't allow for uninitialized fields.

> Which means that you need full-fledged constructors for everything, and syntax for invoking them in all cases where they may be needed

Rust doesn't have constructors. It's not needed.

> - e.g. think about what should happen if you try to create an array of structs with ref fields in them.

    struct Ref<'a> {
        x: &'a i32,
    }
    
    fn main() {
        let a = 5;
        
        let array = [
            Ref { x: &a },
            Ref { x: &a },
        ];
    }
No big deal.

This all being said, that doesn't mean that I think zero values are a mistake in Go, exactly. They make sense with the design of the language. But I do think both sides of this have tradeoffs, and I personally prefer the tradeoffs of no nulls and zero values.


> you just don't allow for uninitialized fields

It sounds simple, but it's the kind of thing that has very far-reaching effects across the language. If you start with this premise and then design everything else around it, things feel natural. If you take a language already designed around the notion of null values and bolt things on, either idiomatic code changes massively, or you leave enough loopholes in the type system to still let people write the stuff they have always done and that always worked (even though it's technically unsound).

And yes, of course it's a tradeoff. For my own part, I also think that null values and the simplicity that comes with them in some things aren't worth the trouble that they bring. I'm also not a fan of Go, to put it mildly. But given where the language is already, and given their explicit stated design goals (which I disagree with), I can totally see why on this particular issue they went with nulls just to keep things simple that were traditionally simple.


> But given where the language is already, and given their explicit stated design goals

I fully agree, which I tried to say at the end. All I was saying is that you don't need all of the stuff you're talking about to make this work.


I think there had been an easier path for Go to solve those problems. What's making C++ and Rust complicated is that they strive for much more generality than Go does.

Requiring a value to be provided at usage/definition site would go a long way. Also, of course, having to provide a default value by hand when initializing an array. Go also has easy-to-use callbacks, so having also APIs that take a callback returning a sentinel/default value should be easy enough. The problem isn't having defaults, but having pervasive defaults that are set in stone by the language itself, even in places where it doesn't make sense.

For low-level code that fiddles with uninitialized memory buffers and allocators, things might get complicated, if zero values are not allowed. (Rust's struggles around `MaybeUninit` is a poster child example of the ensuing complexity.) However, I think that a very Go-style solution would have been that the type system allows for zero-initialized types, but everything around the provided APIs and language semantics makes creating them hard. It's a similar solution with "our strings are UTF-8, but we don't check and world doesn't explode if they are not". I generally dislike this kind of "worse is better" design, but it certainly fits Go very well.

The main problem with Go, arguably, would be what to do with null pointers. They could have gone with separate reference types for "definitely not null" and "may be null", with a specialized if-like check-and-coerce-operation, without having full generics and sum types like Rust does.

Because Go has a GC and thus is capable of having arbitrary object graphs, there shouldn't be a problem of initializing multiple values with a reference to the same object, so user providing a default value and the API cloning it to fill the array should work. And in case of a "may be null" ref, having it to be null is not a problem.


Yeah - but it's not like this hasn't been researched and done before no? But adding it in retrospect is probably not easy, I agree.


You're calling designing a language with nil in 2007–2009 ridiculous. We could argue that, but that's not what I wrote.

I wrote that, after 15 years of baggage, there's nothing purposely ridiculous about stating that Go sum types must have a zero value (either nil or something else).

Either Go sum types have a zero value; or they can't be used everywhere a type can be used in Go; or you're radically and backwards incompatibly changing the language.


I wouldn't go as far as calling a language that has nil in 2007-2009 ridiculous and I didn't do so.

But adding sumtypes in 2025 and using a default nil or zero value/type to it, yeah that I do call ridiculous and I stand by it.

It might be a good decision even (I don't know), but it's still ridiculous by my understanding of that word.


Look, as I wrote: Go has zero values, and for a Go type to fully participate in the Go type system, it must have a zero value.

Additionally, for it to fit the 2025 implementation of the runtime, its representation in memory must have a fixed size, with fixed locations for any pointers, and the memory representation of the zero value must be zero.

The runtime can change, but for the runtime to change to accommodate new concepts, the change can't obliterate the expectations of 15 years of existing code.

Given those restrictions, you can either not have sum types (which is the current state of affairs, and will be for the foreseeable future), or you can pick a zero value for your sum types.

If you find having a zero value for sum types ridiculous, you're simply rejecting sum types in Go, which is fine. There is, after all, a reason the proposal wasn't accepted.

Otherwise, we're all happy to accept suggestions that meet the criteria of not breaking compatibility or wreaking havoc with existing code.


It has been done before, but then you end up with a language twice as complicated as Go. Look at all the rules around initialization - constructors, temporaries, move semantics, RVO etc - in C++ for an example. If your goal is specifically to design a simple language, that is not a good path to go down.

FWIW my personal opinion is that the resulting complexity is necessary, and we as engineers just have to bite the bullet and deal with that mental overhead (and if it means that coding becomes too complicated for some, so be it). But the market ultimately decides; and it decided in favor of lots of code that's cheap and fast to write even at the expense of bugs, so we have tools catering specifically to that.


It’s disingenuous to point to C++ as an example of essential complexity here. C++ is just extremely unnecessarily complex in every way. I’m not really aware of anywhere that lack of default zero initialization makes rust more complicated, for example.


I'm not claiming that C++ level of complexity is necessary, only that it is a widely known language with a lot of features that exist solely to enable this one thing.

Rust can do this with less complexity by embracing choices that, while conceptually simple, are very unorthodox and unintuitive, such as everything being a move rather than a copy with few exceptions (whereas in pretty much every other PL it has always been the other way around, if move semantics is supported at all).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: