A static type system does, and all the languages that Rust has to compete with in its space, have one.
Please, do explain: what does "much less expressive" mean, in technical terms? What specific data modeling can I not do in Go, and what specific bugs can be caused by that?
> and it has null pointers
Yes, so? De-Referencing a null pointer in Go crashes the program, making the bug very obvious. Go made the choice to have null pointers (which do exist in silica), and avoid the complexity of languages who pretend that null pointers don't exist.
It's a tradeoff, and a very good one at that.
This is a good illustration of how to model data using Rust's type system in a way that gives you compile-time guarantees of correct behavior:
https://docs.rust-embedded.org/book/static-guarantees/state-...
> Because we are enforcing our design constraints entirely at compile time, this incurs no runtime cost. It is impossible to set an output mode when you have a pin in an input mode. Instead, you must walk through the states by converting it to an output pin, and then setting the output mode. Because of this, there is no runtime penalty due to checking the current state before executing a function.
> Also, because these states are enforced by the type system, there is no longer room for errors by consumers of this interface. If they try to perform an illegal state transition, the code will not compile!
You're getting into the Turing tar-pit. There's nothing you can do in Rust you can't also do in Go, technically. Hell, you can do it all in Brainfuck too, if you so desire.
The big thing, though, is ADTs. Being able to say "the value is one of 3 possible values" is a lot easier than saying "here are three values, you should use the non-zero one".
> Yes, so? De-Referencing a null pointer in Go crashes the program, making the bug very obvious.
At runtime. i.e. production. You think that is just as good as solving the problem at compile time? I certainly don't.