I haven't written any Rust in a few years, but last I knew, it was very possible for a Rust program to crash. The guarantee is that it won't corrupt memory while doing so.
That's true, but 'null pointer exceptions' (which I suppose means expecting a Option<&T> to contain a &T when it is instead empty) are much rarer since nullability is made explicit.
Rust goals in life towards bugs are basically
- Isolate memory bugs to unsafe code which you rarely write.
- Make as many classes as bugs reasonably possible rarer by encoding as much as is reasonable in the type system and encouraging the programmer to think about all possible cases.
The first gets all the attention because it is the one you can make guarantees about, but the second is really just as important.
It is possible, but the causes and frequencies are quite different.
In Rust runtime panics are certainly possible (array out of bounds, out of memory, etc), but the ones analogous to Java's NullPointerException have to be explicit opted into (unwrapping optionals) and do not happen implicitly.
Rust lets you handle optionals in nicer ways which lets you be sure you covered the "null" cases at compile time with no runtime panics possible, if you like.
I think that is somewhat related to the article. There a class of bugs not possible in Rust, they are also not possible in my GCed langs. unwrapping an `Option` is similar to NPE. but that code does not feel idiomatic Rust. IMO there is higher chance of making NPE mistake in Java than unwrapping None in Rust for similar logic.
There are different reasons why a program can crash. Nullpointer exceptions are one of them. Out-of-memory situations are another. Rust protects you against the former, but not the latter. But at least in my experience I only encountered crashes when I disabled safety checks by calling .unwrap().
unwrap() does not disable safety checks, it just means you ignore/disregard error handling. It will panic and abort the program, but not cause a safety issue.
This is actually an interesting distinction. The way that Rust defines "safety" is a little jargon-ish. It's probably more useful than the (highly impractical) colloquial meaning, but still different. In a rust context, "safety" means that the program is protected from a very specific set of things.
One might expect that set to contain things like crashing (panic! and friends) and leaking memory (forget). It does not, and is not intended to.
I think safety is about risk managment. Having a seatbelt on in a car is safer than not having one. This is despite the fact that most regular people really don’t desire crashing their car. By putting on a seatbelt you pay a small prize to weaken the impact of a existing risk (crashing your car).
Rust’s idea of safety is similar: it tries to reduce the impact of common risks that happen during programming. It won’t save you from logical errors — just like a seatbelt won’t save you when you decide to maneuver your vehicle into a flaming pit of lava. The only thing that would save you in that situation is not maneuvering into flaming lava pits.
That beeing said rust has a very straightforward implementation for Unit Tests and Integration tests which could help you dealing with remaining risks.
Of course you can always live on the edge, ignore seatbelts and do things like calling unwrap() on results that might not retun Ok(value) at all times. But that will bite you sooner than later.
What Rust is really good at IMO, is to make risks clear at any givrn moment in time. The routes your program can take are very well represented, especially because of the ownership model. Once you understood it you can make very strong assumptions about which part of the software is manipulating which data. These strong assumptions also help mitigating risk. It is harder in Rust to do things just wrong than in most other languages and it is still fast
But the parent's point remains: a Java NPE and a panic because of unwrap are really equivalent. Not really unsafe in the C sense, but equally bad as unhandled crashes.
There's one really major distinction - you can `grep` for `unwrap`. You could grep for null, but that won't tell you if an NPE elsewhere is possible, and it won't hit every single case (like 3rd party libs returning null).
Assuming that nobody catches the NPE by accident or error (according to my experience in Java, it's a pretty big assumption), yes, they pretty much are.
The big difference is that, in Java, you typically get your NPE because you didn't know/didn't check that your pointer could be `null` – in other words, the default behavior of the language is to NPE. In Rust, the default behavior of the language is to inform the developer that they need to check. They may decide to explicitly assert that the pointer is not null, causing a crash if it is, but that's a conscious choice.
For instance, in my Rust code, pretty much every occurrence of `unwrap()` or `expect()` contains a comment explaining which invariant guarantees that the call will succeed. I don't think I have ever seen any comments associated to a member access in Java.
Not exactly "disabled safety check": `unwrap()` is an assertion check (that may sometimes be optimized away by the compiler). You're still checking safety, but you're doing it in a much more terminal manner.