The actual difference is the reliance on pattern matching and the compiler enforcing coverage on those languages.
I agree. In Java at least the semantics are practically identical.
I can't comment on Rust. I suspect it does a slightly better job.
Crystal is another language where there is no null. It's quite cool.
Java and Rust are practically the same regarding the differences between Optional<T> and T (java), and Option<T> and T (rust). Optional<String> in Java has the `.get()` method which corresponds to the `.unwrap()` method in Rust. And in both are different different types than String in their respective languages.
But in Java you can have an Optional<String> that's null, not just empty. So that's three states - null, wrapping null, or wrapping a value.
I'd say the big difference is that in Rust you can have places that refuse nulls (e.g. using T instead of Option<T>), so you have well defined points of control/checking. That, combined with the difficulty in recovering from a panic (the equivalent of a null pointer exception when trying to unwrap a value that's missing) and the Result type, are Rusts real strength wrt. missing values.
This doesn't require an option type. Option types are actually quite awkward compared to language integration. Kotlin doesn't use an Option type yet still delivers all the same benefits as Rust/Haskell's approach, with benefits (e.g. zero overhead, integrated syntax). Note that Option type overhead isn't merely about heavyweight syntax and the need for the compiler to successfully scalarise: using language integration around standard null pointers means the CPU spends no time on checking the 'maybe' branch. If the type system wasn't violated and a value is present, it's used immediately without any additional instructions or code bloat. If it's not then that will trigger a page fault at some point in the CPU pipeline and transfer control to an exception handler. If the type system indicates nullability and you need to actually do more than just unwrap it, then you have to take a branch of course but it's fully inlined and cheap.
Regarding unwrap: If you tell the compiler "I know what I'm doing" and you don't know what you are doing then no one can help you. It's the same as putting casts everywhere when the compiler yells at you about incompatible types: If you lie to the compiler it will do your bidding, but your code will run into errors at runtime that you could have prevented at compile time. Again: No one can help you here.
In Rust you're passed a reference to an object. Can it be null? No, it simply can't! There does not exist a magic "null" value for references. To represent the possible absence of a value, you explicitly encode it into the type system by using `Option<T>`.