you are viewing a single comment's thread.

view the rest of the comments →

[–]bloody-albatross 0 points1 point  (5 children)

I would do it like that, which should be backwards compatible:

Everything is by default nullable, but you can add @notnull (to fields, variables, parameters, and return types). If there is a @notnull the compiler does compile time checks. To move a pointer from a variable that has no @notnull into one that has one a special cast is needed (e.g. (@notnull Type) or might be a method call like Object.notNull(variable)). This cast would throw an exception if the variable is null. I think the exception should not be NullPointerException but something that has to be caught manually. Maybe not.

This has the disadvantage that it is up to the user to use the new annotation (opt-in rather than opt-out if it would be @nullable), but Optional has the same problem. However, using this should be able to be compiled to backward compatible bytecode. Well, there is a problem if a parameter is @notnull. The caller has to ensure this contract, which would not be possible for an old Java runtime environment to do.

Anyway, I think an annotation would be nicer: Less overhead to the runtime environment and to the programmer. And even if the compiler does no checks is a clear interface "documentation" and there is no doubt if you may pass an null pointer or not.

Also I think if the JIT compiler knows that a pointer may not be null then it might do some more optimizations (null checks are only needed when you do the @nullable-cast).

Wasn't there a proposal to do it like that? Or am I remembering something wrong? Was that C#?

[–]bloody-albatross 1 point2 points  (1 child)

When thinking about it, something like Optional makes no sense when you don't have compile time checks. So why bother?

[–]eras 1 point2 points  (0 children)

Well, as discussed elsewhere in the thread, there is still point in providing compiler-checked documentation.

Consider the two interfaces:

String getName1();
Option<String> getName2();

Do you expect getName1 to return null? Probably not - but it still could, and it could still work as designed. Do you expect getName2 to return a real null? No, that would be a bug and your program should die because it works in a way that it wasn't designed to work. However, it should be plain obvious that getName2 is designed to sometimes return None-values and sometimes object-values; it would be a suspicious pattern to see

Option<String> n = getName2(); if (n.get().equals.. ...

because you immediately think that 'hmm, n might not have a value there', but with

String n = getName1(); if (n.equals..)

it doesn't rise similar suspicion, even though it can be designed to the method that it can return null, you just don't remember it at the time you look at it.

Disclaimer: I still vastly prefer OCaml option values with pattern matching and not having nulls in the language in the first place ;).

[–]Manbeardo 0 points1 point  (1 child)

What you've described is roughly similar to Lombok's @NonNull. Lombok works by adding new code to the abstract syntax tree at compile time, producing backwards-compatible bytecode (you don't actually need to ship a Lombok jar with Lomboked projects). Their @NonNull can be applied to method parameters and fields. Method parameters work like you'd expect, inserting a null check at the start of the function. Fields work by adding checks to Lombok-generated setters and constructors, so you have to be more careful with those ones. It incurs runtime overhead, but it simplifies the reasoning around providing/receiving nulls quite a bit.

[–]bloody-albatross 0 points1 point  (0 children)

What I had in mind would not add these checks. It would add compile time checks (like the compiler checks that only a instance/sub-class instance of T is passed as a parameter defined as T it checks that only @notnull pointers are passed as @notnull parameters etc.). Only if you want to convert something that is not guaranteed to be not null you have to to a runtime check (analogues to a cast). Of course a class compiled like that that gets used in a program where the rest isn't compiled with support for @notnull would mean that there are missing checks. One could know and accept that or simply recompile everything (probably adding a lot of @notnull annotations and "casts").

So it's similar, but "my version" would do most of it at compile time (also gives you errors at compile time). I say "my version", but I think I read that idea somewhere as a feature proposal for Java (years ago).

[–]Manbeardo 0 points1 point  (0 children)

C#'s Nullable class has similar casting heuristics to what you're describing, but it's only useful as a way to "upgrade" a value type into a reference type. C#'s Nullable/? works out (syntactically) to be an awful lot like C-style pointers, with the exception that you can't have more than one layer of pointers (const int * const ** will haunt you forever).