25 ms·
Overhead of Returning Optional Values in Java and Rust (2021)
- Yujf 4y agoWhy is the author talking about null in the intro, which implied using pointers and thus boxed objects and then running benchmarks on integers? That makes no sense to me.
- sirwhinesalot 4y agoBecause it's a benchmark on Optionals? In Java an Optional<Long> requires boxing, in Rust it does not. You'd expect a "sufficiently smart compiler" to detect this and avoid needless boxing after inlining and escape analysis but clearly that is not the case. 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.
- winrid 4y agoJava's language philosophy is simple - everything must be an object. This simplifies a lot of things. 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.
- thaumasiotes 4y ago> Java's language philosophy is simple - everything must be an object. This simplifies a lot of things. 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.
- pharmakom 4y agoThe Java compiler “sucks” (not the language) because it’s not making optimisations that are safe and a human could do themselves fairly simply.
- sshine 4y agoThe lack of optimisation here isn’t a dealbreaker. The fact that Optional<T> can also be null, because it’s a reference type, makes it a less safe implementation of optionals. That’s why the newer standard library uses static methods in a lot of places, e.g.: Optional.ofNullable(theField)
- masklinn 4y agoThat’s just a convenience for 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.
- TazeTSchnitzel 4y agoRust doesn't have optionals built-in. The language has no special support for them (beyond the try operator); just like Java, Rust's optional type is provided by the standard library, but it could be trivially implemented yourself and your implementation would have the same behaviour and performance characteristics. It's literally just: enum Option<T> { Some(T), None, } What makes Rust fast here is that it has value types and can optimise them.
- tialaramex 4y agoEven for the try operator (?) it is these days just an unstable Trait that you are welcome to implement for your own types. Once that's stabilized it's not any different from how types can implement PartialEq and get == and !=. 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.
- masklinn 4y ago> Several standard library types have such niches, but today there is no stable way to make your own yet. 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.
- tialaramex 4y agoYes, Rust's Guaranteed Niche Optimisation says if you make this: 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.
- atq2119 4y ago
- u320 4y ago> Rust's philosophy is to be a super powerful tool, language complexity be damned. This is not Rust's philosophy at all.
- sshine 4y ago> 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 :) 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.)
- jonhohle 4y agoBut `Optional` could have been a value type from the start and had effectively zero overhead, especially if it were specialized for primitive types. There are 8 primitive types, so supporting them all with a value-type optional would not have been the end of the world, even if it was only a language level optimization (e.g. optional becomes a 96-bit-128-bit type and the compiler is responsible for ensuring primitives are wrapped/unwrapped specially). GNU Trove is a collection library that focuses on optimizing for primitive types and is significantly faster that Java collections which require boxing.
- winrid 4y agoNever heard of Trove, thanks! Have only used fastutil, which looks to be about the same thing.
- kaba0 4y agoThere is OptionalLong in the standard library, though. Java just can’t do generic specialization as of yet, which is necessary for Optional<long> to be a different implementation (+ value types of course)
- jonhohle 4y agoBut `OptionalLong` is a heap allocated object. I’m suggesting a wider primitive that can be passed on the stack (value plus flags to indicate state) bypassing any need for allocation. In Java this can only be provided by the compiler (without resorting to some programming and value convention).
- kaba0 4y agoWell, Java doesn’t specify that it has to be heap allocated and it will in fact not allocate that object if the producer function can be inlined and the escape analysis deems so (which happens surprisingly often). 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 https://news.ycombinator.com/item?id=35133577
- tpm 4y ago> Java's language philosophy is simple - everything must be an object. 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.
- winrid 4y agoYeah the type erasure is pretty bad. Can be useful though :)
- komadori 4y agoRust doesn't have optionals built into the language except insofar as Option<T> is defined in the standard library. The difference is that Rust allows you to define new value-types, whereas Java has a small fixed set of "primitive" value-types.
- Inityx 4y agoAlso from the beginning of the article: > 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.
- alkonaut 4y agoAnd the only one that truly would make sense would of course be Optional<long>, i.e. the optional primitive long... 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.
- kaba0 4y ago20 years ago there were no generics, so you couldn’t have implemented it that way. You could have written a class OptionalLong { long value; boolean isSet; } at the time and that would have only a single allocation overhead. Alternatively, have an array of longs and a boolean array marking which ones are set, with a trivial wrapper object over that for essentially zero overhead. 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.
- alkonaut 4y ago> 20 years ago there were no generics, so you couldn’t have implemented it that way. You could have written a class OptionalLong 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.
- marginalia_nu 4y agoI think in general these types of benchmarks makes boxed numbers look bad only because they're being compared to adding integers, which is a ridiculously fast operation to start with. Accomplishing half that speed despite "dereferencing a pointer" is kinda insane in the light of 'Latency Numbers Every Programmer Should Know'. (The reason it works is because Java doesn't actually allocate new Longs for small numbers, it fetches them from a table of constants; it's always the same 256 objects that are being dereferenced. I don't know their memory layout, but I'd half expect them to be sequential in memory as that's would be much a low hanging fruit optimization. Optional<Long>'s performance is what you'd expect without these optimizations. Also in this scenario you really should use OptionalLong instead of Optional<Long> but that's beside the point ;-)
- ramblerman 4y agoThe table of constants leads to this fun example: System.out.println(Integer.valueOf(22) == Integer.valueOf(22)); // true System.out.println(Integer.valueOf(2200) == Integer.valueOf(2200)); // false Which is a bit confusing to say the least. I realize one should never be using == with objects, but still.
- matsemann 4y agoThat's another quality of life improvement in Kotlin, == calls the equals method on the objects, not comparing their references. Which is what most java programmers want in 99% of cases. And makes the code more readable, a.equals(b) is harder to read than a == b.
- marginalia_nu 4y agoIn most cases I'd use equals(a, b) in Java (with a static import of Objects.equals). It's both null safe and more readable.
- bzzzt 4y agoI kind of like that behaviour. You're comparing object references and get exactly the expected output instead of some magically overloaded equality operator. If you want that, just use 'equals' ;)
- MrBuddyCasino 4y agoHandling nulls by wrapping references in Optionals (at least if they can't be optimised away) is IMO strictly inferior to static analysis by the compiler as in Kotlin and forcing correct error handling. Its really all you need! The problem is not nullable references, after all Optionals can contain nulls, the problem is documenting if a value can be null via the type system and correctly handling those cases. Hoping Java will get this one day, but probably not...
- iainmerrick 4y agoI had the same thought. Kotlin and TypeScript have a good approach here. Don’t avoid using nulls at runtime, just beef up the type system so you know at compile time when they might appear. (The only wrinkle in both cases is that you might have to interact with Java/JS code that might not be null-safe.)
- MrBuddyCasino 4y agoYes, thankfully Jetbrains have gone to the trouble of solving that by considering nullability annotations and relaxing type checking when encountering platform types and failing at runtime instead. [0] [0] https://kotlinlang.org/docs/java-interop.html#null-safety-and-platform-types https://kotlinlang.org/docs/java-interop.html#null-safety-an...
- gjadi 4y agoI've never used Kotlin, but there are several systems that provide Null Analysis in Java. For example FindBugs https://findbugs.sourceforge.net/manual/annotations.html https://findbugs.sourceforge.net/manual/annotations.html Is Kotlin better because it works out of the box or are there differences in the feature set?
- cryptos 4y agoFrom my experience Kotlin's null handling works better than these external tools. Another point is that also the APIs need some support for it, to be convenient. Kotlin has methods like mapNotNull for example.
- diffuse_l 4y agoMy guess is that C# will be much better, since it has support for Value types. Wasn't Java supposed to get support for Value types some time ago?
- olavgg 4y ago[flagged]
- pharmakom 4y agoI downvoted because this is not a constructive comment and it makes sweeping generalisations.
- SideburnsOfDoom 4y agoC# dev here, and I don't bash Java, in fact I don't tend to comment on languages that I'm not an expert on, because well, not an expert. This covers the vast majority of programming languages and toolkits. Nobody know them all. Nobody knows more than a small fraction of them. I'll make one generalisation though: any dev of $langA that spends their time bashing $langB is doing pathetic insecure gatekeeping. And should give it up. This applies to what grandparent comment is talking about, and to grandparent comment itself. It's not cool to hate on an out-group, even if it's a community bonding experience. I refer the grandparent to Scott Hanselman: https://www.youtube.com/watch?v=IzhQIpT7S50 https://www.youtube.com/watch?v=IzhQIpT7S50
- alkonaut 4y agoC# does indeed produce a rather tight assembly https://sharplab.io/#v2:EYLgtghglgdgNAFxBAzmAPgAgLACgACATAMx4DeemVm+xNAjAGyYA2A9jAOaYDKArmAAU7LgH4A2gF1MCABaxOKAJQ5c1TBTXrqI7igGYAvJgAMAbkraqAMzYAnQbASYoR02ZcAeOQpQA6ABkAUy45DygAagilSyt1fTBMCOMfLhRxKEk/AAlUADUIFj4gzFFTTBAZeTSMrIKioIstK3wAdkwEpvUAXzxe3CA=== https://sharplab.io/#v2:EYLgtghglgdgNAFxBAzmAPgAgLACgACATAMx...
- orthoxerox 4y agoThe compiler takes care of this, but the idiomatic way to write the body of the loop in C# is sum += things[i] ?? 0;
- cryptos 4y agoThe issue will probably be solved this year with value types in Java. https://openjdk.org/jeps/8277163 https://openjdk.org/jeps/8277163, https://www.baeldung.com/java-valhalla-project https://www.baeldung.com/java-valhalla-project, https://blogs.oracle.com/javamagazine/post/java-jdk-18-evolution-valhalla-panama-loom-amber https://blogs.oracle.com/javamagazine/post/java-jdk-18-evolu...
- pharmakom 4y agoCan they go back and change Option though? Will we get a separate ValueOption type? This could get messy.
- cryptos 4y agoAs far as I remember from a post of Brian Goetz changing the existing Optional type is exactly the plan! So, Optional will become a zero-cost abstraction™ in Java. https://cr.openjdk.org/~briangoetz/valhalla/sov/02-object-model.html#migrating-value-based-classes-to-inline-classes https://cr.openjdk.org/~briangoetz/valhalla/sov/02-object-mo...
- Sankozi 4y agoOptional and some other classes have a warning "This is a value-based class; ..." in the documentation (since Java 8 - see https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html https://docs.oracle.com/javase/8/docs/api/java/util/Optional...). So it is documented that the way Optional is working might change in the future.
- ithkuil 4y agoRust has an interesting optimization for Option<T> when T has enough "room" for encoding a marker for "None": https://google.github.io/comprehensive-rust/std/box-niche.html https://google.github.io/comprehensive-rust/std/box-niche.ht... e.g. Option<NonZeroU64> is effectively encoded and operated on as u64, but it gives the type system a way to make sure you correctly handle the case where "0" means something special for you
- chrismorgan 4y agoJust a pity we currently only have number types with the niche at zero; something like NonMaxU32, which represents numbers in the range [0, 2³² − 2], would be useful at least as often, leaving 0xffffffff available for niche optimisations like Option::<NonMaxU32>::None. NonMinI32 could also be interesting as a symmetrical number type, representing [−2³¹ + 1, 2³¹ − 1] and leaving the bit pattern 0x80000000 for niche optimisations.
- tialaramex 4y agoEventually it will be possible to write new types like this in stable Rust, the current approach is Pattern Types. Today you can do this in nightly Rust, using a deliberately permanently unstable attribute, that's what my nook crate does to produce e.g. BalancedI8 which is a signed byte from -127 to 127. It will be nice when some day Pattern Types, or an equivalent are stabilized.
- dathinab 4y agoWhen there was a lot of discussions about niche optimizations and integers that if we had const generics we could have `NonXXX` types which use const generics to specify the niche. But back then we hadn't had const generics and it was time wise too far off to wait for const generics in anyform (including unstable rustc internal only usage of it). So if now that we have const generics somone sits downs discusses the technical details on zulip, then writes a RFC and then writes an implementation we theoretically could have it soon. Through I'm not sure how easy/hard the implementation part would be. Some problems to discuss for standardization would be: - is there any in progress work, overlapping RFC etc. (Idk. there should be older in progress work, but someone might be working on it right no idk). There could also be work on a more generic niche handling code which would happen to also cover this idk. - should multiple niches be handled and if so how with which limitations (there are no variadic generic and ways to emulate them like through type nesting likely wouldn't have pef and complexity problems) - can it be usefull for outside of optimizations to have e.g. a range limited integer - if the gap is big enough (i.e. u32 limited to a hypothetical u24), should it interact with packed representation - is there any risk of it being confusing/unexpected (should not be the case, but still needs to be evaluated) EDIT: There seem to be unstable following attributes: #[rustc_nonnull_optimization_guaranteed] #[rustc_layout_scalar_valid_range_start(...)] #[rustc_layout_scalar_valid_range_end(..)]
- gjadi 4y agoInteresting. This article resonate in me with the recent articles of Casey Muratori about non-pessimistic code: Within the realm of the module, don't use pessimistic code (avoid Boxing) _but_ that doesn't prevent you to provide a safe API. E.g. the result of the loop could be wrapped if that made sense.
- Someone 4y agoI think part of the problem is that Java’s Optional isn’t a Either[null, T] but a Either[null, boxed T, boxed null] ‘Boxed null’ is what the documentation (https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html https://docs.oracle.com/javase/8/docs/api/java/util/Optional...) calls “an empty Optional” That means that, for example, an Optional[Byte] can have 258 different values and cannot, in general, be compiled to a ”pointer to byte” because that has only 257 different values. Edit: reading https://news.ycombinator.com/item?id=35133241 https://news.ycombinator.com/item?id=35133241, the plan is to change that. I fear that, by the time they get around to that, lots of code will handle the cases null and Optional containing null differently, making that a breaking change.
- adrianmsmith 4y ago> I fear that, by the time they get around to that, lots of code will handle the cases null and Optional containing null differently, making that a breaking change. Yes, I was working with code once which wrapped string IDs into a FooId object (a good idea in principle) and all of the following had different meanings: FooId x = null; FooId x = new FooId(null); FooId x = new FooId(""); FooId x = new FooId(...an actual ID...) I think one was for not showing any content at all, one was for showing default content, another was that content was there but the user wasn't allowed to see it, etc. I'm so glad I left that company...
- marginalia_nu 4y ago[True|False|FileNotFound] ;-) For what it's worth, Java's also got this class: https://docs.oracle.com/javase/8/docs/api/java/util/OptionalLong.html https://docs.oracle.com/javase/8/docs/api/java/util/Optional... Although in practice there isn't much performance difference in my experience.
- moonchild 4y ago> reading https://news.ycombinator.com/item?id=35133241 https://news.ycombinator.com/item?id=35133241, the plan is to change that. I fear that, by the time they get around to that, lots of code will handle the cases null and Optional containing null differently, making that a breaking change The post your link links to explains exactly how they intend to avoid this problem.
- TwentyPosts 4y ago> A special “I would never write Rust like that” variant that returns the value in a Box on the heap. This is because a friend of mine, after seeing my results, told me I was cheating, because all Rust versions so far used registers/stack to return the value, and Java was at a disadvantage due to returning on the heap (by default). Uhh, okay? This sounds a bit silly to me. It's good to add the additional comparison, sure, but "cheating" is just not the right word. The point of this article is ostensibly to compare the built-in Option types, not heap allocation. The fact that Java allocates any Options on the heap is part of that comparison (and reflects badly on Java, fwiw). Either way, glad to see that Rust is doing a good job eliminating the overhead. I'm not sure if arithmetic is the right kind of benchmark here, but it'd probably be difficult to measure the performance overhead across "real" codebases, so focusing on a tight loop microbenchmark is probably fine.
- chrismorgan 4y ago(2021) Discussion at publication time: https://news.ycombinator.com/item?id=28887908 https://news.ycombinator.com/item?id=28887908
- dgb23 4y agoIt’s interesting to see how different languages deal with nulls and similar constructs. In some languages like TS, PHP or Kotlin have proper unions that you just handle with branching. Rust lets you pattern match againt a construct that holds a value or doesn’t. Option is an actual thing there that you need to unpack in order to look inside. In Clojure nils are everywhere. They tell you that “you’re done” in an eager recursion, or that a map doesn’t have something etc. Many functions return something or nil, and depending on what you’re doing you care about the value vs the logical implication. nils flow naturally through your program and it’s not something you are worried about, as many functions do nil punning. Well as long as you don’t directly deal with Java - then you have to be more careful.
- orthoxerox 4y agoWith void-returning methods and null-punning you can end up skipping critical side effects, which are more common in idiomatic Java than in idiomatic Clojure. C# tries to make this explicit for the receiver with its Elvis operator and nullable reference types. foo?.Bar(baz); and var result = foo?.Bar(baz); both do what's expected: skip the method call and return null (or void) if the receiver is null, and the compiler complains if you don't do that when foo is inferred to be nullable.
- kasperni 4y agoOptional is being being converted to a value class as part of Project Valhalla. I reran the benchmarks with the latest Valhalla branch [1] and added a test that used OptionalLong instead of Optional<Long>. Without Valhalla - OptionBenchmark.sumSimple avgt 5 328,110 us/op - OptionBenchmark.sumNulls avgt 5 570,800 us/op - OptionBenchmark.sumOptional avgt 5 2223,887 us/op - OptionBenchmark.sumOptionalLong avgt 5 1201,987 us/op With Valhalla - OptionBenchmark.sumSimpleValhalla avgt 5 327,927 us/op - OptionBenchmark.sumNullsValhalla avgt 5 584,967 us/op - OptionBenchmark.sumOptionalValhalla avgt 5 572,833 us/op - OptionBenchmark.sumOptionalLongValhalla avgt 5 326,949 us/op OptionalLong is now as fast as simple sum. And SumOptional is now as fast as SumNulls. So the overhead of using OptionalLong and Optional<Long> seems to have gone away with Valhalla. It would be great if boxing could be eliminated as well. But few people writes code like what is being benchmarked (in hot loops) in practice. [1] https://github.com/openjdk/valhalla https://github.com/openjdk/valhalla
- pestatije 4y agoThose numbers don't support your statement
- kasperni 4y agoCould you tell me exactly what is the issue? sumNullsValhalla and sumOptionalValhalla returns 584,967 us/op and 572,833 us/op respectively sumSimpleValhalla and sumOptionalLongValhalla returns 327,927 us/op and 326,949 us/op respectively
- deleted 4y ago[deleted]
- rkuska 4y agoMaybe the person missed the difference because how your numbers are formatted: - OptionBenchmark.sumOptional avgt 5 2223,887 us/op vs - OptionBenchmark.sumOptionalValhalla avgt 5 572,833 us/op At least to me after the first read it seemed like comparison of two similair 5kish values.
- jeroenhd 4y ago> A special “I would never write Rust like that” variant that returns the value in a Box on the heap. This is because a friend of mine, after seeing my results, told me I was cheating, because all Rust versions so far used registers/stack to return the value, and Java was at a disadvantage due to returning on the heap (by default). So here you are. Why would Rust be cheating here? Java cannot make these types of optimizations yet (though they are likely coming with Project Valhalla) but that doesn't mean Rust should be similarly handicapped in benchmarks. Java has many smart optimizations and advantages over Rust (being garbage collected for one, making it much easier to write code in, and runtime reflection, a blessing and a curse) and with tricks like rearranging objects to make more effective use of CPU caches you can end up writing Java that's very close in performance to native, precompiled code. However, when it comes to raw performance, you shouldn't expect the standard JVM to come close to Rust. There is inherent overhead in the way the language and the runtime are designed. There is no "cheating" here, the algorithms are the same and some languages just produce more efficient code in these scenarios. You wouldn't slow down the JVM to make the benchmark fair for a Python implementation either! A more interesting comparison may be compiling Java to native assembly (through Graal for example) so Java too can take advantage of not having to deal with reflection and using SIMD instructions. Alternatively, a Java vs C# rundown would also be more interesting, as both languages serve similar purposes and solve similar problems. C#'s language-based approach to optional values has the potential to be a lot faster than Java's OOM-based approach but by how much remains to be seen. Java vs Kotlin may also be interesting to benchmark to see if the Kotlin compiler can produce faster code than Java's Optional; both run inside the same JVM so the comparison may be even better.
- pjmlp 4y agoKotlin is only syntax sugar, so any bytecode pattern it happens to generate better than javac is also doable in Java. In fact it is mostly the opposite, all the Kotlin concepts that don't exist in Java (the language), need additional bytecodes to fake their semantics on top of JVM bytecodes optimized for Java semantics. Like functions, lazy initializations, delegation, or co-routines.
- tadfisher 4y ago
- rocqua 4y agoThe rust NonZeroU64 solution has a subtle 'bug' that happens not to matter. The function fn get_optional_non_zero(n: u64) -> Option<NonZeroU64> let i = n & 0xFF; if i == MAGIC { None } else { NonZeroU64::new(i) } } Actually returns None for n = 0 or n is any multiple of 256. The resulting usage in the sum still yields the same result, because skipping zeros in an addition doesn't matter, but it is a subtle difference between this get-function compared to all of the others. It also doubles the number of None cases the code needs to handle.
- layer8 4y agoNote that Java’s Optional was never intended to be a general-purpose Maybe type or general-purpose replacement for null, unlike Rust’s option. As Brian Goetz explains in https://stackoverflow.com/a/26328555/623763 https://stackoverflow.com/a/26328555/623763: “Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors. For example, you probably should never use it for something that returns an array of results, or a list of results; instead return an empty array or list. You should almost never use it as a field of something or a method parameter. I think routinely using it as a return value for getters would definitely be over-use.”
- chriswarbo 4y ago> For example, you probably should never use it for something that returns an array of results, or a list of results; instead return an empty array or list This also applies to null/Maybe as well: both would violate the principle of least surprise (e.g. the AWS DynamoDB SDK has queries return an 'Array<Item>'; but this is 'null' if there are no matches!). It also complicates the domain model, making two distinct forms of empty value ('None' versus 'Some(List())'; or '[]' versus 'null'), which may not have any semantic difference. > You should almost never use it as a field of something I agree, although it's often preferable to expose methods rather than fields anyway; in which case it's a return value, which seems OK. > or a method parameter Sure, that's what polymorphism/overloading is good for, e.g. instead of `foo(int arg1, Optional<String> arg2)` we can have separate `foo(int arg1, String arg2)` and `foo(int arg1)` definitions (where the latter will probably call the former with some default). > I think routinely using it as a return value for getters would definitely be over-use I agree, since that would indicate our model is too weak, and missing some domain-relevant information. For example, if many of our 'Order' methods return optional results, there's probably a finer-grained distinction to be made, like 'PendingOrder', 'FulfilledOrder', etc. which don't need the optional qualifiers. (Personally I try to avoid the term "getter": APIs should make sense without reference to their underlying implementation; whether that happens to be "getting" a field, or calling out to some other methods/objects, etc. That's the point of encapsulation :) )
- paulddraper 4y ago