Note that "Long" in Java can be null because it is boxed, "long" (lowercase) however cannot be null, but it also can't be Optional<long>. Java sucks :)
EDIT: I'd love to see a C# version of this.
Rust has optionals built into the language. Rust's philosophy is to be a super powerful tool, language complexity be damned.
I find it hard to say java sucks in this context. Each language is making trade offs that align with their vision.
enum Option<T> {
Some(T),
None,
}
What makes Rust fast here is that it has value types and can optimise them.The extra edge beyond not needing boxing is from niches, if T doesn't occupy all possible bit representations Rust will squeeze None into one of the unused bit values. Several standard library types have such niches, but today there is no stable way to make your own yet.
Not strictly speaking true since you can define enum types and those usually have niches?
However note that if a type has suitable niches enums will automatically take advantage of those. So that’s more of a concern with T than with a bespoke Option, that’ll work OOTB.
enum Foo<T> {
Nasty,
Nice(T),
}
... and T has a niche, it is guaranteed that Foo<T> has the same size as T and Nasty just slots into the niche.In practice, other fancier things will get optimised, but Rust doesn't guarantee exactly what will or will not be handled, e.g. if the niche has room for four things, and I make an enum with four extra plain variants, the guarantee doesn't apply but probably that'll work.
In the interest of full and strict accuracy: see the enum’s full definition at https://github.com/rust-lang/rust/blob/d610b0c514b9ccb0dad5d.... There are a few points of specialness about Option that you can’t use elsewhere:
• #[rustc_diagnostic_item = "Option"]: the compiler knows about Option for the purpose of improving its error messages. I’m not sure how this is used, and am not looking it up now.
• #[lang = "None"] and #[lang = "Some"]: lang items allow the compiler to hook things up, like knowing the + operator maps to the core::ops::Add trait. https://github.com/search?q=repo%3Arust-lang%2Frust+%28optio... suggests that these lang items are only being used by Clippy, to provide better diagnostics. So if you use your own Option type, you won’t get Clippy lints related to Option.
• I suppose there are also the #[stable] attributes, which can’t be used outside the standard library, and which cause visible changes in generated documentation. When talking about strict accuracy, I guess that counts!
Anyway: for practical purposes this is just minor diagnostics and documentation stuff, not actual functionality, about which there is nothing special.
Since you mentioned the try operator, might as well look at the trait implementations on Option too, https://doc.rust-lang.org/std/option/enum.Option.html#trait-.... There are a few things marked “This is a nightly-only experimental API.”, which you can implement yourself if you’re willing to take that stability risk; they’re all linked to try_trait_v2, which is what backs the try operator, `?`. Once it, or its successor, is stabilised, we’ll effectively be back to there being absolutely nothing special about Option, as you’ll be able to implement those traits for your own Option type as well.
Which in the Rust world probably just needs to be one byte, probably a bit in most cases, in a register for state.
I don't think Java will be that optimized with value types. Even if the heap allocation is gone, there's still probably going to be a sizable object header?
But the important part is losing identity - even if a field a few layers deep won’t be flattened, it doesn’t have to work the same way for the same data at every place - the JVM now is free to instead of copying the reference only, can choose to copy the value. This trivializes things like escape analysis as well (we can just stack allocate this value class, if it does turns out to escape, just copy to the heap).
I think using primitive types as generics is something that makes Java less ergonomic than C# (where they’re called unmanaged types), whether it is considered justified or necessary.
To say Java sucks because of this is a bit much. To say Java sucks because you can’t avoid null is definitely warranted. (You can say good things about Java, and not being able to opt out of nulls is not one of them.)
This is not Rust's philosophy at all.
GNU Trove is a collection library that focuses on optimizing for primitive types and is significantly faster that Java collections which require boxing.
But here is another option if you really want to avoid that allocation (besides of course using ByteBuffer and similar which is always a possibility): https://news.ycombinator.com/item?id=35133577
What is the reason for not making a OptionalLong a 72-bit (or larger if you care about alignment), primitive value but keeping object semantics at the language level? Someone who thinks they have an object OptionalLong is already looking at minimally 112-bits for the class pointer and value on a non-empty value or add another 96-bits onto the if it’s an `Optional<Long>`. What’s missing with the value-type is shared references to the same instance, but for an immutable optional to an immutable long, that doesn’t make much difference in practice. That’s the only drawback I can see. In practice, how often is it important to have identity properties for boxed primitives? That’s already probably caused more bugs than it’s benefits.
Except primitive types like long in this case, which are not objects. This was a performance-consistency tradeoff made in the early 90s. It made sense at the time and now doesn't make sense to some people, but that's ok. I wouldn't say Java sucks because of that either. Now type erasure, that's a different topic.
Optional.ofNullable(theField) theField == null ? Optional.empty() : Optional.of(theField);
As Optional.of will raise an NPE if given a null value.It doesn’t change the issue that the Optional itself can be null, and that null is its default value.
Everything except primitive types, functions, and arrays (of any type). The different status of arrays can be a real pain.
Ruby says the same thing, and they're even worse about functions behaving differently than objects do.
> The task was to compute a sum of all the numbers, skipping the number whenever it is equal to a magic constant. The variants differ by the way how skipping is realized:
> 1. We return primitive longs and check if we need to skip by performing a comparison with the magic value directly in the summing loop.
> 2. We return boxed Longs and we return null whenever we need to skip a number.
> 3. We return boxed Longs wrapped in Optional and we return Optional.empty() whenever we need to skip a number.
Seems pretty reasonable to me.
First having to declare the value in the one type of four that makes least sense, then praying that the compiler optimizes the allocation of not one but TWO(!) objects(!) in order to represent "maybe a number" is basically why I ragequit Java almost 20 years ago.
Java’s tradeoffs are maintainability in huge teams over multiple years with relatively fast performance even if you write your code very naively, with top notch tooling, observability, etc. In the rare case you have to optimize in the hot loops you can allow to have less readable code like I mentioned.
That was the actual reason, yeah. Basically having to make IntList and so on.
> Java’s tradeoffs are maintainability in huge teams over multiple years with relatively fast performance
When I did switch to C# in 2003, it was very young. Since then generics have been bolted on and so on, but I didn't find any hit to maintainability due to this. What I do think was sad though is that when Generics were bolted on (and value types obviously were there all along), that the APIs didn't immediately include some easy and obvious wins like Option<T>. Those have been reimplemented since ad nauseam.