Thursday, 11 January 2007

Java 7 - Null-safe types

Yesterday I published part of a proposal for enhancing null handling in Java 7. Reading the comments, this got a few people upset. Unfortunately, what I didn't make clear was that I was also documenting the other part - null-safe types for Java.

The null-safe types part of the proposal is the tougher one to write-up, and probably the tougher one to implement. I was going to try and finish the details before publishing, but lets get the discussion going anyway...

Null-safe types are similar to Option Types in Nice. The difficulty is taking the idea and blending it with Java in a way that works and doesn't break the style of Java.

So, what is the null-safe types proposal? Well, lets examine this method:

  public Person lookupPerson(String personId) { ... }

This is a fairly typical method that loks up a person from a person identifier. But what happens if we pass a null identifier into the method? And can we get a null response? The current solution to this is adding explicit documentation:

  /**
   * Find person by identifier.
   * @param personId  the person id, not null
   * @return the person, never null
   * @throws IllegalArgumentException if the person is not found
   */
  public Person lookupPerson(String personId) { ... }

Now we know how the method is to be used. But this is just documentation. There is no compile-time checking. If we don't read the documentation then we may get a NPE at runtime.

The null-safe types proposal adds a new modifier to types - hash # - that prevents that variable/parameter/return from being null.

  public #Person lookupPerson(#String personId) { ... }

Now that we are using these declarations, the compiler can check that the calling code does not pass a null String to the method.

  String badId = null;
  #Person person = lookupPerson(badId);   // compile error

  #String id = "1234";
  #Person person = lookupPerson(id);      // ok

The draft of the formal null-safe types proposal is available with more details. And there are a lot more details, and probably a lot I've missed. If you've got any suggested enhancements or fixes to the proposal, or want to help out, let me know. All feedback welcomed.

And remember that this proposal is intended to complement the null-ignore part of the proposal.

16 comments:

  1. I am still not sure about the "#" symbol, I'd rather have a keyword. However it does add a bit of clutter as well.

    However, this is a better approach than what was proposed yesterday.

    Although implementation wise, the sample you provided is pretty trivial. However, in addition we should have warnings along the lines of.

    Warning: the parameter "p" is not guaranteed to be non-null, but must not be null when invoking Foo.barMethod(Object)

    Which we can override using annotations like usual.

    Also the following should not generate a warning.

    void foo(String mayBeNullable) {
    #String neverNullString = mayBeNullable
    }

    But it should generate a runtime error if the mayBeNullable is null.

    ReplyDelete
  2. Actually change the last example to

    #String neverNullString = (#String)mayBeNullable

    That sounds more correct rather than relying on some magic happening.

    ReplyDelete
  3. Stephen Colebourne11 January 2007 at 17:33

    @Archimedes, In the proposal document I use (#) as the cast:
    #String neverNullString = (#) mayBeNullable;

    ReplyDelete
  4. Thanks, Stephen, for your blog. Lots of interesting stuff to read, and a load of ideas.

    With the null-safe types, I am not sure, though. This kind of compile-type safetyness could also be provided by adding the proper Annotations to the JDK. For example, IntelliJ provides such annotations called @NotNull and @Nullable. Maybe, I just don't see the advantage using the # style (at least for compile-time feature).
    The previous proposal to shorten out all the null-checks sounded more reasonable. Although, in many cases one would treat nulls differently than "ignoring" and returning null. As far as I can tell, the more common situation is to handle a null value, e.g., stop processing, set to fallback, do some initialization stuff, etc.

    Cheers.

    ReplyDelete
  5. Why not use annotations, like the popular @NotNull and @Nullable sort?

    ReplyDelete
  6. Stephen Colebourne11 January 2007 at 18:40

    @Stefan,@Binkley, Why not annotations? Because null itself is a language level feature with poor support (you can only really check it for null). As a language level element, it should be addressed by a language level fix.

    Annotations are for metadata, data outside the core code that alters how the system runs. Annotations should not be used to make key changes in the type-structure.

    Finally, this feature will be extremely commonly used if implemented. In many systems, the vast majority of variables will be non-null. An annotation is simply too verbose to achieve the goals of this proposal.

    ReplyDelete
  7. Kieron Wilkinson12 January 2007 at 13:34

    I'm just imagining how many "if (param == null) throw new IllegalArgumentException" preconditions I could throw away with this... Yummy!

    ReplyDelete
  8. Finally, a JDK7 proposal that would actually be useful in Java's target work environment, helping diverse teams produce results together.

    ReplyDelete
  9. I would be much more confortable with the inverse behaviour:

    1. the hash sign implies that they *can* be null.
    2. revise the JDK so that hashes are given as needed.
    3. give people the means to easily prevent nulls, such as String.EMPTY, Integer.NaN, etc;

    the code compiled with earlier JDKs would be interpreted as "fully hashed".

    ReplyDelete
  10. Stephen Colebourne15 January 2007 at 21:04

    @Tiago, The Nice language uses ? to indicate a maybe-null field and no symbol to indicate a not-null field. This fits well with a new language, but it doesn't feel right in Java (to me ;-). I am working on the principle that Sun will basically only adopt a proposal where existing Java source files continue to compile.

    BTW, as I'm marking the non-null class, thats why I couldn't use ? (as it doesn't make sense). Hence the # sign - which has no other meaning in Java at present, and is easy to type (on an English keyboard).

    I agree with your suggestion of String.EMPTY, thats a good idea. Unfortunately, I can't see how Integer.NaN would work, as it is backwards incompatible.

    ReplyDelete
  11. As you ask of my opinion I would gladly give it..

    To be honest, I favour Tiago's idea too. So much of the code I see around, actually expects not-null behaviour, so a not-null enforcement with a not-null relaxation is more in line with a type safe language.

    However, given that the beast itself cannot be undone (so to speak), we should probably pursue your idea to the limits.

    However, it does have a sense of opposite direction to the null-ignore-invocation, so I would ask would you favour stronger typing or looser typing?

    Today I just had a trembling feeling that Java is moving rapidly away from its core, yet, with the tails solidly placed in the backward-compatibility compartment we will probably be marching towards the edge :-)

    I tried to flash new features in Java on DZone:
    http://www.dzone.com/links/10_reasons_why_java_is_drifting_away.html

    And quite a lot of comments stirred up. Now, you comment on that one :-)

    ReplyDelete
  12. Stephen Colebourne17 January 2007 at 21:42

    @Niels, I favour a strongly typed language. Thats why I believe that this proposal is the stronger of the two null proposals. After writing other blogs, especially the expression language one, I believe that the null-ignore proposal fits well when accessing nested properties, but not so well outside that use case.

    Certainly there is a desire by some of us to move Java forwards. The language itself is very cluttered, verbose and not very productive. I'll blog/javalobby about that separately soon.

    ReplyDelete
  13. Um. If they do this, I think ECMAScript 4's use of "!" would be appropriate. It's fairly clear, and it's backwards compatible:

    String badId = null;
    Person! person = lookupPerson(badId); // compile error

    String! id = "1234";
    Person! person = lookupPerson(id); // ok

    Or alternatively, a keyword or annotation might work.

    ReplyDelete
  14. Stephen,
    the spec about @Null and
    @Nullable is available here:
    http://www.jetbrains.com/idea/documentation/howto.html

    Rémi

    ReplyDelete
  15. Default not-null would be nice (and then I'd probably prefer Nice's syntax). I'd be curious to see how many errors would happen if you swapped the default with "-source 7.0". Side comment: I think full support for not-null would require VM changes or hackery (and the Java language _usually_ opts for VM changes - but not always).

    ReplyDelete
  16. I like both ideas and believe that rather than choosing one over the other, both solve different use cases.

    From an aesthetic point of view, the hash sign doesn't look appealing to me. I would clearly favor a keyword such as nonnull or nonempty instead. For a null-ignore operator, my favorite notations are ".." or "?." in that order.
    object..getProperty()
    object?.getProperty()

    There is a rationale behind the ".." operator as it might be considered to harmonize semantically with existing notations; see for an explanation http://forums.java.net/jive/thread.jspa?threadID=22049

    --

    Earlier in this thread, there was talk about the tradeoff between having real native keywords vs. annoations. It surely looks ugly to have annotations on method-argument level etc...

    What I like to do is to allow for the @ symbol and parenthesis to be absent for annotations in certain cases: I'd propose for all annotations within packages starting with java.*

    (If annotations had been around right from the beginning and with JSR308 support plus my proposed shortcut, one could have modeled all java modifiers as annotations in this way... Whatever sense this would have made ;) )

    The result would be that new java releases could contain usual annotation declarations like so:

    package java.lang;

    @Retention(runtime)
    @Target(method, field, assignment, ...)
    @interface nonnull {}


    which could be applied within code like this:


    public void method(nonnull String abc, nonempty List l) {
    nonnull String local = l.get(0);
    }


    This proposal might seem completely silly - I know - but perhaps it's not so bad at all.
    For one thing it's hard to misinterpret and hard to misuse as non-platform annotations may not use this abbreviation. Whether to also allow the abbreviated form for javax. annotations or even for all annotations are worth discussing...
    Plus, it doesn't lead to backwards compatibility issues as additional keywords do and would facilitate the work done in JSRs 305 and 308 while at the same time reconciling those who would have rather liked to have native keywords.

    ReplyDelete