Hacker News new | past | comments | ask | show | jobs | submit login

Is there any movement in the language spec to address this in the future with Nil types or something?



I really love Golang and how it focused on making the job of the reader easy. But with today’s modern programming language the existence of null pointer dereference bugs doesn’t really make sense anymore. I don’t think I would recommend anyone to start a project in Golang today.

Maybe we’ll get a Golang 3 with sum types…


If you squint really hard, the work on generics is a step toward the future.

If you don't squint, then I don't think so.


With generics, can you not make a NonNil<T> struct in Go, where the contents of the struct are only a *T that has been checked at construction time to not be nil, and doesn't expose its inner pointer mutably to the public? I would think that would get the job done, but I also haven't really done much Go since prior to generics being introduced

Otherwise, since pointers are frequently used to represent optional parameters, generics + sum types would get the job done; for that use case, it's one of two steps to solve the problem. I don't foresee Go adding sum types, though.


Every type in Go has a zero value. The zero value for pointers is nil. So you can't do it with regular pointers, because users can always create an instance of the zero value.


This is one of those things which feels like just a small trade off against convenience for the language design, but then in practice it's a big headache you're stuck with in real systems.

It's basically mandating Rust's Default trait or the C++ default (no argument) constructor. In some places you can live with a Default but you wish there wasn't one. Default Gender = Male is... not great, but we can live with it, some natural languages work like this, and there are problems but they're not insurmountable. Default Date of Birth is... 1 January 1970 ? 1 January 1900? 0AD ? Also not a good idea but if you insist.

But in other places there just is no sane Default. So you're forced to create a dummy state, recapitulating the NULL problem but for a brand new type. Default file descriptor? No. OK, here's a "file descriptor" that's in a permanent error state, is that OK? All of my code will need to special case this, what a disaster.


> Default Gender = Male is... not great

    enum Gender {
        Unspecified,
        Male,
        Female,
        Other,
    }

    impl Default for Gender {
        default() -> Self {
            Self::Unspecified
        }
    }
or:

    enum Gender {
        Male,
        Female,
        Other,
    }
and use Option<Gender> instead of Gender directly, with Option::None here meaning the same that we would mean by Gender::Unspecified


I think they are talking about the cons of Go allowing zero value. Rust doesn’t have that problem.


    type Gender int
    const (
        Unspecified Gender = iota
        Male
        Female
        Other
    )
Works the same way. Declaring an empty variable of the type Gender (var x Gender) results in unspecified.


…and now you need to check for nonsense values everywhere, instead of ever being able to know through the type system that you have a meaningful value.

It’s nil pointers all over again, but for your non-pointer types too! Default zero values are yet another own goal that ought to have been thrown away at the design stage.


> instead of ever being able to know through the type system that you have a meaningful value.

That's... not what I'm looking for out of my type system. I'm mostly looking for autocomplete and possibly better perf because the compiler has size information. I really hate having to be a type astronaut when I work in scala.

So, I mean, valid point. And I do cede that point. But it's kind of like telling me that my car doesn't have a bowling alley.


Making an analogy between a car with a bowling alley being as useful as having the ability to know you have a valid selection from a list of choices does not exactly reflect well on your priorities.


I take it you use enums fairly regularly?

I don't really use them that much, so they're superfluous for the most part. Sort of like a car having a bowling alley. I mean, I'll take them if it doesn't complicate the language or impact compile time, but if they're doing to do either of those, I'd rather just leave them.

Adding default branches into the couple of switch statements and a couple spots for custom json parsing that return errors for values outside the set doesn't seem like a bad tradeoff.


It's not optimal but one can still implement enums in userland using struct and methods returning a comparable unexported type.


More like a car without ABS and no TC. It's cheaper and you can drive just more carefully, but you're more likely to crash.


I think that's a bad analogy because I actually use ABS and TC. I don't really use an enum heavy style of programming. Maybe twice per project or so. Putting a default branch in a couple switch statements also seems to get me back the same safety (although I'd rather detect the error early and return it in a custom UnmarshalJSON method for the type).

I also imagine I'd use a bowling alley in my car ~2 times per year, tops. So that seems like a better analogy to me.

edit: I guess I should bring up that I don't use go's switch statement much either, and when I do, 99% of the time I'm using the naked switch as more of an analog to lisp's cond clause.


> I think that's a bad analogy because I actually use ABS and TC. I don't really use an enum heavy style of programming.

The point the others are making is you are using a less safe type of programming equivalent to driving without ABS and TC.

From your point of view, "TC and ABS is useful so I use it unlike enums".

From their point of view though, you are the person not using ABS and TC insisting that they offer nothing useful.


To continue the analogy, since I don't use enums, I'm also the person who isn't driving the car.

Can you explain how owning a car without ABS and not driving it is less safe?

Edit: Wait, or am I not using breaks? I think the analogy changed slightly during this whole process.


> Can you explain how owning a car without ABS and not driving it is less safe?

I don't know that it is, I'm speaking based on the implication in this thread that no-enum and no-ABS people are overconfident to a fault.

I do believe that those who claim they don't need enums, static typing, etc are probably overconfident and have a strong desire or need to feel more control.

I'm not sure though, at least for GC'd languages, how enums sacrifice control.


There are still people that swear that they can brake better than ABS. Until they do a side-by-side test.

Re: custom UnmarshalJson implementation - you still have to remember, and do it for every serialized format (e.g. sql).

A default case in a switch only solves, well, switching. If a rogue value comes in, it will go out somewhere. E.g. json to sql or whatever things are moving.


> A default case in a switch only solves, well, switching. If a rogue value comes in, it will go out somewhere. E.g. json to sql or whatever things are moving.

I mean, yeah, but eventually you have to do something with it. And the only useful thing you can really do with an enum is switch on it...


But it does not work. It looks like it would, with the indentation and iota keyword, but its just some variables that do not constrain the type. There will be incoming rogue values, from json or sql or something else.

    var g Gender // ok so far.
    if err := json.Unmarshal("99", &g); err != nil { panic(err) }
    // no error and g is Gender(99)!
Now you must validate, remember to validate, and do it in a thousand little steps in a thousand places.

Go is simple and gets you going fast... but later makes you stop and go back too much.


Default gender male not how this works in practice. Instead, you define an extra “invalid” value for almost every scalar type, so invalid would be 0, male 1 and female 2. Effectively this makes (almost) every scalar type nullable. It is surprisingly useful, though, and I definitely appreciate this tradeoff most of the time.

(Sometimes your domain type really does have a suitable natural default value, and you just make that the zero value.)


Great, now you’ve brought the pain of checking for nil to any consumer of this type too!


This is a thread about Go, not about Rust. There is a bunch of interesting computer science in this post, and if interesting new computer science is a baby seal, Rust vs. Go discussions are hungry orcas.


I write a decent amount of go - this isn't a defence of the current situation.

> All of my code will need to special case this, what a disaster.

No, your code should handle the error state first and treat the value as invalid up until that point, e.g.

    foo, err := getVal()
    if err != nil {
        return
    }

    // foo can only be used now
It's infuriating that there's no compiler support to make this easier, but c'est la vie.


Man, if only over 30 odd years of PL research leading up to Go, somebody came up with a way to do it better.


Given the choice between a (objectively) theoretically superior language like Haskell or Rust , and a language that prioritises developer ergonomics at the expense of PL research, I'll take the ergonomics thanks.

We have a 1MM line c++ codebase at work, a rust third party dependency, and a go service that's about as big as the rust dependency. Building the Rust app takes almost as long as the c++ app. Meanwhile, our go service is cloned, built, tested and deployed in under 5 minutes.


Other than compiling a bit faster, what ergonomics does Go give you that Rust doesn't?


> Other than compiling a bit faster,

It's not "a bit faster", it's "orders of magnitude faster". We use a third party rust service that compile occasionally, a clean build of about 500 lines of code plus external crates (serde included) is about 10 minutes. Our go service is closer to 5 seconds. An incremental build on the rust service is about 30s-1m, in go it's about 5 seconds. It's the difference between waiting for it to start, and going and doing something else while you compile, on every change or iteration.

> what ergonomics does Go give you that Rust doesn't

- Compilation times. See above.

- How do I make an async http request in rust and go? in go it's go http.Post(...)

In rust you need to decide which async framework you want to use as your application runtime, and deal with the issues that brings later down the line.

- In general, go's standard library is leaps and bounds ahead of rust's (this is an extension of the async/http point)

- For a very long time, the most popular crates required being on nightly compilers, which is a non-starter for lots of people. I understand this is better now, but this went on for _years_.

- Cross compilation just works in go (until you get to cgo, but that's such a PITA and the FFI is so slow that most libraries end up being in go anyway), in rust you're cross compiling with LLVM, with all the headaches that brings with it.

- Go is much more readable. Reading rust is like having to run a decompressor on the code - everything is _so_ terse, it's like we're sending programs over SMS.

- Go's tooling is "better" than rust's. gofmt vs rustfmt, go build vs cargo build, go test vs cargo test

For anything other than soft real time or performance _critical_ workloads, I'd pick go over rust. I think today I'd still pick C++ over rust for perf critical work, but I don't think that will be the case in 18-24 months honestly.


And yet it's a productive language with significant adoption, go figure.

They made trade-offs and are conservative about refining the language; that cuts both ways but works well for a lot of people.

The Go team does seem to care about improving it and for many that use it, it keeps getting better. Perhaps it doesn't happen at the pace people want but they always have other options.


> And yet it's a productive language with significant adoption, go figure.

Perl was also a successful language with significant adoption. At least back then, we didn’t know any better.

In twenty years the industry will look back on golang as an avoidable mistake that hampered software development from maturing into an actual engineering discipline, for the false economy of making novice programmers quickly productive. I’m willing to put money on that belief, given sufficiently agreed upon definitions.


I can't agree with the definitions you're insinuating. To suggest that the creators of Go do not know what the difference is between "writing software" and doing "software engineering" is plainly wrong. Much of the language design was motivated by learnings within Google. What other "modern" language that has more of a focus on software engineering, putting readability and maintainability and stability at the forefront? It's less about novice programmers and more about artificial barriers to entry.

Modern PLT and metaprogramming and more advanced type systems enable the creation of even more complex abstractions and concepts, which are even harder to understand or reason about, let alone maintain. This is the antithesis of whatever software engineering represents. Engineering is almost entirely about process. Wielding maximally expressive code is all science. You don't need to be a computer scientist to be a software engineer.


The simple existence of the Billion Dollar Mistake of nils would suggest that maybe Rob Pike et al are capable of getting it wrong.

> Much of the language design was motivated by learnings within Google.

And the main problem Google had at the time was a large pool of bright but green CS graduates who needed to be kept busy without breaking anything important until the mcompany needed to tap into that pool for a bigger initiative.

> What other "modern" language that has more of a focus on software engineering, putting readability and maintainability and stability at the forefront?

This presupposes that golang was designed for readability, maintainability, and stability, and I assert it was not.

We are literally responding to a linked post highlighting how golang engineers are still spending resources trying to avoid runtime nil panics. This was widely known and recognized as a mistake. It was avoidable. And here we are. This is far from the only counterexample to golang being designed for reliability, it’s just the easiest one to hit you over the head with.

Having worked on multiple large, production code bases in go, they are not particularly reliable nor readable. They are somewhat more brittle than other languages I’ve worked with as a rule. The lack of any real ability to actually abstract common components of problems means that details of problems end up needing to be visible to every layer of a solution up and down the stack. I rarely see a PR that doesn’t touch dozens of functions even for small fixes.

Ignoring individual examples, the literal one thing we actually have data on in software engineering is that fewer lines of codes correlates with fewer bugs and that fewer lines of code are easier to read and reason about.

And go makes absolutely indefensible decisions around things like error handling, tuple returns as second class citizens, and limited abstraction ability that inarguably lead to integer multiples more code to solve problems than ought to be necessary. Even if you generally like the model of programming that go presents, even if you think this is the overall right level of abstraction, these flagrant mistakes are in direct contradiction of the few learnings we actually have hard data for in this industry.

Speaking of data, I would love to see convincing data that golang programs are measurably more reliable than their counterparts in other languages.

Instead of ever just actually acknowledging these things as flaws, we are told that Rob Pike designed the language so it must be correct. And we are told that writing three lines of identical error handling around every one line of code is just Being Explicit and that looping over anything not an array or map is Too Much Abstraction and that the plus sign for anything but numbers is Very Confusing but an `add` function is somehow not, as if these are unassailable truths about software engineering.

Instead of actually solving problems around reliability, we’re back to running a dozen linters on every save/commit. And this can’t be part of the language, because Go Doesn’t Have Warnings. Except it does, they’re just provided by a bunch of independent maybe-maintained tools.

> enable the creation of even more complex abstractions and concepts

We’re already working on top of ten thousand and eight layers of abstraction hidden by HTTP and DNS and TLS and IP networking over Ethernet frames processed on machines running garbage-collected runtimes that live-translate code into actual code for a processor that translates that code to actual code it understands, managed by a kernel that convincingly pretends to be able to run thousands of programs at once and pretends to each program that it has access to exabytes of memory, but yeah the ten thousand and ninth layer of abstraction is a problem.

Or maybe the real problem is that the average programmer is terrible at writing good abstractions so we spend eons fighting fires as a result of our collective inability to actually engineer anything. And then we argue that actually it’s abstraction that’s wrong and consign ourselves to never learning how to write good ones. The next day we find a library that cleanly solves some problem we’re dealing with and conveniently forget that Abstractions Are Bad because that’s only something we believe when it’s convenient.

Yes, this is a rant. I am tired of the constant gaslighting from the golang community. It certainly didn’t start with “generics are complicated and unnecessary and the language doesn’t need them”. I don’t know why I’m surprised it hasn’t stopped since them.


I doubt we can have a productive debate here as you're harping on issues I didn't even reference. Compared to peers, the language is stable, is readable and maintainable, full stop.

- The language has barely changed since inception

- most if not all behavior is localized and explicit meaning changes can be made in isolation nearly anywhere with confidence without understanding the whole

- localized behavior means readable in isolation. There is no metaprogramming macro, no implicit conversion or typing, the context squarely resolves to the bounds containing the glyphs displayed by your text editor of choice.

The goal was not to boil the ocean, the goal was to be better for most purposes than C/C++, Java, Python. Clearly the language has seen some success there.

Yes, abstractions can be useful. Yes, the average engineer should probably be barred from creating abstractions. Go discourages abstractions and reaps some benefits just by doing so.

Go feels like a massive step in the right direction. It doesn't have to be perfect or even remotely perfect. It can still be good or even great. Let's not throw the baby out with the bath water.

I think for the most part I'm in agreement with you, philosophically, but I don't get the hyperfocus on this issue. Most languages you consider better I consider worse, let's leave it at that.


> Go feels like a massive step in the right direction

I agree. Go has it's warts, but given the choice between using net/http in go, tomcat in java, or cpprestsdk in c++, I'll pick Go any day.

In practice: - The toolchain is self contained, meaning install instructions don't start with "ensure you remove all traces of possibly conflicting toolchains" - it entirely removes a class of discussion of "opinion" on style. Tabs or spaces? Import ordering? Alignment? Doesn't matter, use go fmt. It's built into the toolchain, everyone has it. Might it be slightly more optimal to do X? Sure, but there's no discussion here.

- it hits that sweet spot between python and C - compilation is wicked fast, little to no app startup time, and runtime is closer to C than it is to python.

- interfaces are great and allow for extensions of library types.

- it's readable, not overly terse. Compared to rust, e.g. [0], anyone who has any programming experience can probably figure out most of the syntax.

We've got a few internal services and things in Go,vanr we use them for onboarding. Most of my team have had PR's merged with bugfixes on their first day of work, even with no previous go experience. It lets us care about business logic from the get go.

[0] https://github.com/getsentry/symbolicator/blob/master/crates...


> In twenty years the industry will look back on golang as an avoidable mistake

And here is my opinion:

I think in 20 years, Go will still be a mainstream language. As will C and Python. As will Javascript, god help us all.

And while all these languages will still be very much workhorses of the industry, we will have the next-next-next iteration of "Languages that incorporate all that we have learned about programming language design over the last N decades". And they will still be in the same low-single-percentage-points of overall code produced as their predecessors, waiting for their turn to vanish into obscurity when the next-next-next-next iteration of that principle comes along.

And here is why:

Simple tools don't prevent good engineering, and complex tools don't ensure it. There are arcs that were built in ancient Rome, that are still standing TODAY. There are buildings built 10 years ago that are already crumbling.


> I think in 20 years, Go will still be a mainstream language. As will C and Python. As will Javascript, god help us all.

And yet the mainstream consensus is that C and JavaScript are terrible languages with deep design flaws. These weren’t as obvious pr avoidable at the time, but they’re realities we live with because they’re entrenched.

My assertion is that in twenty years, we’ll still be stuck with go but the honeymoon will be over and its proponents will finally be able to honestly accept and discuss its design flaws. Further, we’ll for the most part collectively accept that—unlike C and JavaScript—the worst of these flaws were own goals that could have and should have been avoided at the time. I further assert that there will never be another mainstream statically-typed language that makes the mistake of nil.

For that matter I think we’ll be stuck with Rust too. But I think the consensus will be that its flaws were a result of its programming model being somewhat novel and that it was a necessary step towards even better things, rather than a complete misstep.


> And yet the mainstream consensus is that C and JavaScript are terrible languages with deep design flaws.

Oh, they all have flaws. But whether these make them "terrible" is a matter of opinion. Because they are certainly all very much usable, useful and up to the tasks they were designed for, or they would have vanished a long time ago.

> and its proponents will finally be able to honestly accept and discuss its design flaws

We are already doing that.

But that doesn't mean we have to share anyones opinion on what is or isn't a terrible language, or their opinions about what languages we should use.

And yes, that is all these are: opinions. The only factually terrible languages are the ones noone uses, and not even all languages that vanished into obscurity are there because people thought them to be "terrible".

Go does a lot of things very well, is very convenient to use, solves a lot of very real problems, and has a lot to offer that is important and useful to us. That's why we use it. Opinions about which languages are supposedly "terrible" and which are not, is not enough to change that.

An new language has to demonstrate to us that its demands on our time are worth it. It doesn't matter if it implements the newest findings about how PLs should be designed according to someones opinion, it doesn't matter if its the bees knees and the shiniest new thing, it doesn't matter if it follows paradigm A and includes feature B...the only thing that matters is: "Are the advantages this new thing confers so important to us, that we have a net benefit from investing the time and effort to switch?"

If the answer to that question is "No", then coders won't switch, because they have no reason to do so. And to be very clear about something: The only person who can answer if that switch is worth for any given programmer, is that programmer.


Perl had less competition and also suffered from being more of a "write-only" language.

Lisp, Haskell, OCaml all likely tickle your PL purity needs, but they remain niche languages in the grand scheme of things. Does that make them bad?

I think Go will be the new Java (hopefully without the boilerplate/bloat). It's good enough to do the job in a lot of cases and plenty of problems will be solved with it in a satisfactory manner.

Language wars are only fun to engage with for sport, but it's silly to get upset about them. Most languages have value in different contexts and I believe the real value in this dialog is recognizing when and where a language works and to accept one's preferred choice may not always be "the one".


One answer would be to provide something like a GetPointer() method which, if the inner pointer is nil, creates a new struct of type T and returns a pointer to it.


Turns out worse isn’t better after all. Who could have guessed?


You can make such a type and it works well in practice.

First we define the type, hiding the pointer/non-existent value:

    type Optional[Value any] struct {
        value  Value
        exists bool
    }
Then we expose it through a method:

    func (o Optional[Value]) Get() (Value, bool) {
        return o.value, o.exists
    }
Accessing the value then has to look like this:

    if value, ok := optional.Get(); ok {
        // value is valid
    }
    // value is invalid
This forces us to handle the nil/optional code path.

Here's a full implementation I wrote a while back: https://gist.github.com/MawrBF2/0a60da26f66b82ee87b98b03336e...


Even if this would be possible, it won't be idiomatic.


Without intention to offend. It's Golang, the language that famously ignored over 30 years of progress in language development for the sake of simplicity.

What answer do you expect?


Hey, can you please not do programming language flamewar (or any flamewar) on HN? We're trying for something else here: https://news.ycombinator.com/newsguidelines.html.

Partly this is out of memory of the good/bad old newsgroup days where this kind of thing somehow worked ok, until it didn't, but it definitely doesn't work on the sort of forum that HN is. We'd like a better outcome than scorched earth for this place.




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

Search: