Delight

Aug 2009, Thomas Leonard

NullPointerException

One of my biggest problems with main-stream typed languages is the handling of null. Take this D code (same applies to Java, etc):

class Foo {
	void sayHi() {
		printf("Hi!\n");
	}
}

void main(string[] args) {
	Foo foo;
	foo.sayHi();
}

If you run this, you get:

$ gdc -Wall -o sayhi sayhi.d
$ ./sayhi
zsh: segmentation fault (core dumped)  ./sayhi

What's the point carefully adding type annotations everywhere if your program's just going to segfault anyway? You might as well save yourself some typing and use Python.

In Delight, object references cannot be null by default. Here's the equivalent:

import dlt.io: Printer

class Foo:
	in Printer stdout

	void sayHi():
		stdout.write("Hi!\n")

class Main:
	void main():
		Foo foo
		foo.sayHi()

It won't compile:

$ delic -o sayhi sayhi.dlt
hi.dlt:11: variable hi.Main.main.foo non-null, but missing initialiser

OK, let's initialise it:

class Main:
	void main():
		Foo foo = null
		foo.sayHi()

Nope:

sayhi.dlt:11: Error: cannot implicitly convert expression (null) of type
void* to sayhi.Foo

Or even:

class Main:
	void main():
		Foo foo = new Foo(null)
		foo.sayHi()

Nope:

sayhi.dlt:11: Error: cannot implicitly convert expression (null) of type
void* to dlt.io.Printer

If you really want a variable that can be null, add ? to the type to make a maybe Foo type. e.g.

class Main:
	void main():
		Foo? foo = null
		foo.sayHi()

The program still won't compile, though:

sayhi.dlt:12: Error: attempt to access property 'sayHi' for 'foo' of type
'Foo?', which may be null.

I first saw this a few years ago in a language called Nice.

Downsides

You can get false positives in two cases:

  • A method can sometimes return null, but not with the arguments you're passing now.
  • You're trying to call a function from a language without null/not-null annotations.

The second case isn't a big problem for Delight, because the lack of global state requires wrapping all external libraries anyway.

The first problem can happen in cases like this:

interface AddressBook:
	# Returns null if name isn't known
	Details? lookup(string name)
	...

for name in addressBook:
	# Name must be known
	Details p = addressBook.lookup(name)

Two general options are:

  • Have lookup (or a second new method) throw an exception instead of returning null.
  • Use the built-in notnull() function to convert it. This results in a run-time check rather than a compile-time one.

In this case, a better option would be to use the loop-over-dictionary syntax:

for name, details in addressBook:
	...

False-negatives can occur in constructors. You have to assign to all non-null fields in your constructor, but Delight doesn't stop you from accessing them before you've set them (or calling another method which does that).

Dynamic Arrays

Note that dynamic arrays (including string) are not pointers in Delight (or D) and therefore cannot be null. An array is a structure containing a pointer and a length. It is passed by value (though the pointer is shared).

An empty array may or may not contain a null pointer, depending on how it was allocated, but Delight (unlike D) treats them the same either way.

D allows you to use null for an empty array, which is rather confusing. Delight only accepts [] for this.

Removing the Maybe

You can turn a maybe type into a regular non-null type using an if statement:

if Details details = addressBook.lookup(name):
	return details.extractData()
else:
	throw new Exception("Name " ~ name ~ " not known!")

When assigning the result of the condition, the variable can be declared non-null, even if the condition has a maybe type. This is because the new variable (details) is only in scope in the "then" part of the statement, where it cannot be null (because null is false).