Balancing Safety and Convenience

Article summary

In most languages, such as C, C++, Ruby, Python, and Java, if you ever attempt to send a message to nil (or dereference a NULL pointer), one of two things will happen: either your program will crash, or a runtime exception will be thrown, which could also lead to your program crashing.

For example, in Ruby,

account = bank.find_account(42)      #account couldn't be found, returns nil
if account.balance > amount_needed   # this line will throw an exception, since account is nil
  puts "Hooray"
end

One of my favorite languages, Objective-C, does not follow this convention: it instead treats any message to nil as a no-op and happily continues on running your program.

account = [bank accountNumbered:42];     // account couldn't be found, returns nil
if ([account balance] > amountNeeded) {  // account is nil, so [account balance] is a no-op that returns 0, which is then compared to amountNeeded
	NSLog(@"Hooray");
}

Either behavior will allow you to write quality software, so what are the design tradeoffs between them? Throwing an exception can be considered safer, because it might be dangerous to proceed if the programmer had not foreseen and explicitly handled the case where the account could not be found. On the other hand, Objective-C returns nil (or 0) as a convenience to the programmer. The assumption is that, if the programmer cares about handling the case where account is nil, he can check for it himself.

An example

Suppose that you would like to display the amount of the most recent transaction in a person’s bank account. If the person doesn’t exist, or he doesn’t have a bank account, or the account has no transactions, then you don’t need to do anything. In most languages, you need to do explicit checking:

if person
  account = person.account
  if account
    last_transaction = account.most_recent_transaction
    if last_transaction
      display_amount last_transaction.amount
    end
  end
end

When messaging nil is allowable, the following code will run correctly even if person is nil:

transaction = [[person account] mostRecentTransaction];
if (transaction) {
    [self displayAmount:[transaction amount]];
}

In my opinion, the latter is far more readable and elegant. And it’s quite frequent to find cases like this. If you have an object, display some of its info to the user. If you have a parent view, ask it whether it’s currently visible – but if you don’t have a parent view, that’s okay. Leveraging the ability to safely message nil helps keep your code simpler and easier to read.

Conclusion

I think that, although it’s true that there’s a slight loss of implicit safety, it’s well worth it for a slightly smaller and more concise codebase. I would love to see other languages also allow you to safely message nil.

Unfortunately, while some languages, such as Ruby, make it possible for you to monkey-patch this behavior, I don’t believe it’s safe to change such a fundamental assumption on an existing codebase or to defy the conventions of a language and its established community.

What are your thoughts? Would you like to see more languages with this feature?

Conversation
  • Cris Bennett says:

    One problem I find with this aspect of Objective-C is that it makes a programmer’s decision space a bit more complex. In languages less nil-tolerant, it’s fairly unambiguous in many cases that a null object subclass should be created: nils are dangerous, therefore banish them (or, if you don’t like null objects, always test for nil returns).

    In Objective-C, it’s much more a case-by-case issue. The fact that you can message nil makes nil returns OK in some circumstances, but not in others. I’m not decided whether this is a good thing or not. On the one hand, thinking through what might happen in every case where a nil object may be returned seems like a good case of avoiding programming by coincidence. On the other, the more potential problems can be obviated by policy (use null objects / always test for nil), the more thinking space and time can be allocated to higher-level things.

  • Richard Nienhuis says:

    This gets suggested for python every once in a while. It always gets shot down though as the python pattern is to always ask permission. though you might be able to squeeze something similar out of python by abusing dictionaries.

  • Aaron Day says:

    Nullsafe operators were a proposal for Java 7, but never made it.

    http://blog.joda.org/2009/01/java-7-null-default-and-null-safe_7527.html

    Over all, I prefer explicit behavior over implicit. And a nullsafe operator would show developer intent when working with nulls.

    Using the example above, if it were coded with the proposed nullsafe operator it would look something like (using nullsafe in the ‘if’ and unsafe in the display_amount call):


    if person?.account?.most_recent_transaction
    display_amount person.account.most_recent_transaction
    end

  • Job Vranish says:

    I really like the way Haskell and other functional languages solve this problem. They have a type called Maybe (aka Option) that stores the results of operations that might fail.
    It does two things:
    It forces you to check the result for null before you can get the value out of it (you can’t accidentally forget)
    It provides a nice way to chain operations that return Maybe’s so that checking for the null value is not burdensome.

    Your example in Haskell would look something like this:

    https://gist.github.com/1847835

    • Samuel A. Falvo II says:

      Looks like a formatting issue there. It doesn’t make much sense as listed. Maybe you meant something like this?

      Looks like a formatting issue there.  It doesn't make much sense as listed.  Maybe you meant something like this?
      
      
      let result = do
                     account  putStrLn “Oh noes!”
                       Just a -> putStrLn("The amount was: " ++ show $ last_amount a)
      
      • Job Vranish Job Vranish says:

        WordPress is eating my Haskell D:

        I updated my comment to point to a gist

  • Comments are closed.