Nullable<T> instead of Option<T>

I was just wondering... Wouldn't Nullabe<T> be a better name than Option<T>? Something like this:

enum Nullable<T> {
    Null,
    Value(T),
}

Option really sounds awkward to me for this usecase :grin:

Edit (added later):

Given this function signature:

fn plus_one(x: Option<i32>) -> Option<i32> {
}

In my opinion, there is zero chance someone who has not read the rust documents can tell what this function does. But this function:

fn plus_one(x: Nullable<i32>) -> Nullable<i32> {
}

I'd say any programmer can tell this function is a function that is able to handle null values. In my opinion Rust really has weird syntax sometimes. For example:

let config_max = Some(3u8);
if let Some(max) = config_max {
        println!("The maximum is configured to be {}", max);
}

Couldn't we use a syntax like this instead?

if config_max match Some(max)  {
        println!("The maximum is configured to be {}", max);
}

Yes, That's a very good point. I shouldn't have inverted it. pattern is like a variable and it should be on the left side. Maybe if let original syntax is not that bad. Anyway some alternatives could be:

if match Some(max) with config_max {}

or

if Some(max) match config_max {}

or

if Some(max) matches config_max {}
1 Like

“Better” is not really a quality one can objectively use here. Naming is inherently subjective. With a background in languages where “null” values are commonly used to achieve (somewhat) comparable functionality, such a naming scheme might appear “better”. Although “better” in this case actually only means “more familiar”.

Given that there are significant differences between “null” values in typical OOP languages, and Option in Rust, perhaps a different naming is somewhat beneficial even (e.g. Option can be nested, Option can be used with every type including primitive integers; not just “objects”, Option does never implicitly “unwrap” to give you access to its contents, and Option is its own fully fledged type with its own [pretty large] API, etc…)

By the way; with my personal background involving lots of Haskell, the naming scheme I would have been most familiar with would be

enum Maybe<T> {
    Nothing,
    Just(T),
}

and arguably, Haskell’s algebraic data types are a lot more similar to Rust’s enums than nullable objects are. Neither of these naming schemes won and made it into Rust. (If there’s something to “win” in something like this in the first place.)

Looking even further into other programming languages that neither me nor you might be very familiar with, e.g. in this nice collection of examples on Wikipedia, we can determine that Rust apparently had predecessors that chose that exact naming scheme before.

To name just one example from a language that I’m a little bit familiar with that I spotted in that Wikipedia article, here’s a type in Coq, and here’s the same Website in 2002 already containing a type with that exact naming scheme.

(On the Website from today)

Inductive option (A:Type) : Type :=
  | Some : A -> option A
  | None : option A.

(On the Website from 2002)

Inductive option [A:Set] : Set := Some : A -> (option A) | None : (option A).

I wouldn’t know how to find out the origin of this naming scheme, perhaps it was even re-invented a few times independently, Coq was certainly not the first either…

19 Likes

Ah, look at that, Coq is implemented in OCaml, and as it happens, the first Rust compiler was written in OCaml, too. Guess what OCaml has? Correct, an algebraic data type with the same naming scheme!

Here’s a website proving its existence in 2001.

And here’s a website indicating its existence in 1997. (Search for “None | Some” in the file.)

Digging through the oldest file in this directory reveals that the naming scheme was already used in 1991 or 1992 (in a predecessor to OCaml, AFAICT).

5 Likes

The oldest one I know of that uses the "option" terminology is Standard ML.

I would be curious why Haskell (or was it perhaps Miranda, the precursor to Haskell) diverged from Standard ML.

10 Likes

Thank you for your interesting answer.

However, I think this is more about average programmers, like me, and what they are familiar with. It is about readability and simplicity. Given this function signature:

fn plus_one(x: Option<i32>) -> Option<i32> {
}

In my opinion, there is zero chance someone who has not read the rust documents can tell what this function does. But this function:

fn plus_one(x: Nullable<i32>) -> Nullable<i32> {
}

I'd say any programmer can tell this function is a function that is able to handle null values.

1 Like

Rust has a lot of things the “average programmer” (however that’s supposed to be measured) won’t be familiar with. Option is probably one of the lesser concerns.

By the way: Just to be clear, in case your point was that the name of Option should be changed. Maybe that wasn’t your point at all. But if it was… not going to happen; Rust is a stable language and does not do any breaking changes, so you’ll always be able to call it Option. And adding an alias would lead to more confusion.

Note that, especially in the “systems programming” domain, there’s – in my opinion – actually some additional value in not mixing up optional values with “null pointers”. This way, the documentation of Option can talk about things like the “null pointer optimization” more easily, and functions or types that do contain the word “null” such as std::ptr::null[_mut] or …::NonNull can consistently be things that do talk about literal null pointers. Ah well… and then there’s std::process::Stdio::null which is named after “/dev/null” instead :sweat_smile:.

20 Likes

I think the point was, what "average programmers" are familiar with depends (in part) on which languages they have used in the past. Option will be familiar to some, and unfamiliar to others; nevertheless it has a pretty intuitive meaning that ought to be fairly clear even to those who have not encountered the terminology before.

I also somewhat struggle with the notion that a programmer would believe they could understand code written in a language with which they are unfamiliar without reading its documentation. Sure they might be able to guess at the meaning, but until they read the docs they won't know what the code is supposed to do: and one can guess at Option just as well as one can guess at Nullable.

Finally, it's such a core type whose use is completely endemic throughout the ecosystem that I cannot imagine it ever now being changed. That doesn't mean you can't create a Nullable type to use in your own code, however—or indeed alias Option as Nullable if you wish (but I'd suggest that doing so will make your code harder, not easier, for other Rustaceans to understand).

6 Likes

To emphasize on this point… it’s really not clear to me at all. What is your view of “average programmer”? Presumably with a JavaScript view, the question for “null” makes sense. Though they don’t have types per se, so it’s just the “null” value.

Other “most popular” languages (i.e. what your “average programmer” could use) would include Python. They don’t have a “null” but a “None”, and furthermore their optional type annotations allow you to use “Optional[X] for a type that could be X or None.

Going through more languages would probably only run into even more variation, e.g. some write nil for “null” for all I can tell.

7 Likes

I don't think adding an alias is a bad idea. This alias could be added to some language level (i.e version) and you should be able to select your language level for every project. This way we can also introduce new syntax. This is really important for a language if it wants to stay alive over time.

You couldn't change the variant names. Nullable::Some sounds like there is some nullability, where Nullable::None sounds non-nullable, but the meaning is actually the opposite.

5 Likes

In my opinion Rust really has weird syntax sometimes. For example:

let config_max = Some(3u8);
if let Some(max) = config_max {
        println!("The maximum is configured to be {}", max);
}

Couldn't we use a syntax like this instead?

if config_max match Some(max)  {
        println!("The maximum is configured to be {}", max);
}

That's a very good point. However, I think we should be able to change variant names too. For example Nullable::Null could be an alias for Option::None and Nullable::Value an alias for Option::Some. I think that should be doable. Aliasing usually is flexible.

It seem to me like null is usually a non-element placeholder for any type T and thus could stand in place of a valid value of type T -- kinda like a null set or a null hypothesis. In programming, examples are null pointers in C or null references in Java or Javascript. Whereas Option<T> is a different type altogether. It cannot be used where T can be used, but null can in C/Java/Javascript (though it'll throw an exception if actually used at runtime).

Objective-C has NULL, nil, Nil and [NSNull null]. NULL is like in C (that is (void*)0). nil is a null pointer for any object (that is (id)NULL), Nil is like nil except for classes and [NSNull null] is an object representing a null value. [NSNull null] is necessary as you can't insert nil into a collection object.

4 Likes

The Book says they are the way rust defines nulls:

https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html#the-option-enum-and-its-advantages-over-null-values

1 Like

It says that Option is the way to solve the problem that nulls are trying to solve:

As such, Rust does not have nulls, but it does have an enum that can encode the concept of a value being present or absent.

A null value is not typed. None is.

7 Likes

Well, but it also shows that null is a distinct concept from Option<T>. I believe it might be confusing to conflate the two concepts in the naming of the enum.

2 Likes

And if Option were called Nullable that solution would not be good anymore?

Indeed it wouldn't. The idea of Nullable<Nullptr> for some type representing a nullptr is not exactly sensical. For example, what does Nullable::Some(nullptr) mean vs Nullable::None? If they're different concepts, how are they different conceptually? Why do they both need to exist? And if they're the same, well, having multiple representations of the same thing is asking for trouble.

In your example nullptr is a correct value for your type: Ptr (shouldn't be Nullptr you should use Nullable<Ptr>), but Null represents no value. Actually that completely makes sense nullptr usually represents 0 and is a value. Notice that I can create similar example for you, by using None and Option. Option<ExampleOption>

The thing is we want to say we have a type T which could be None, void, empty, nil, absent or null or it may have a value. You can say there is an option which could be something or none or you can say there is some nullable value which could be null or have a value. I'd say the latter is stating my intention more clearly. It is exactly like when you have better names for your variables.