Option<T> for a beginner, help

Yes, I am reading a Rust learning book, and I looked into the Rust specifications of “Option”, and I googled, and I looked into other topics here.

And to add, I think I am not entirely stupid :smiley:

But can someone please make me understand what this Option exactly is, and why you would use it? If I am not over asking, could someone explain it with similarity to C?

Option<T> represents a nullable type whose concrete type we don't know until compile time, but it has an alias of T, so that we can refer to it. It's nullable in the sense that you might have something simple like Option<bool> which represents a tristate; true, false, or None. In the true/false case, the bool is wrapped in Option::Some, and the None-case is simply Option::None.

What it is exactly is just a normal enumeration. Why would you use it? Because null references are Tony Hoare's "billion-dollar mistake":

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years. [1]

There isn't really anything similar in C, except you would use Option in the same kinds of places where one might have returned a null reference in C. The value is still nullable in a similar way, but Option::None cannot be dereferenced as a null pointer. That makes it a memory-safe nullable value.


  1. Null References: The Billion Dollar Mistake (infoq.com) ↩︎

5 Likes

Lots of Rust code uses the existence of a value with a certain type as a compile-time proof that something is true, for example:

  • If you have a reference &T, you know that it points to a valid T
  • If you have a MutexGuard<T>, you know that you are holding the lock that protects the T
  • If you have a File, then you know that the file is open and ready for I/O

Sometimes, however, you can't know at compile time that these things are true. In those cases, you need a way to tell the compiler (and readers of your code) that you might have one of these things, but it isn't guaranteed. This is what Option is for.

Taking the last example, you might have an Option<File> that represents where debug logging should go: If it's None, logging is disabled and if it's Some(...) it's the open file you should be logging to.

In C, you might do the same job with a null pointer. Option has a couple of advantages here:

  • It doesn't require the optional thing to be behind a reference.
  • It distinguishes quite explicity the items that are guaranteed to be present from the ones that might not be there. As an added benefit, the compiler can verify that you've specified what should happen in the null case whenever it might occur.
9 Likes

On top of what parasyte and 2e71828 have already said, I think it's worth mentioning that constructions such as Option also provide you with a safe computational context.

In other languages, you might be familiar with the burden of having to deal with nullable types, and your codebase turns into spaghetti due to all the horrible !isNil checks everywhere.

Well, in Rust you don't have to deal with that thanks to the Option type. You can do things like this, for example:

fn getSomethingElseFromUser(maybeUser: Option<User>) -> Option<SomethingElse> {
  maybeUser.map(|user| {
    // Do something with the user here. It's totally safe, the type system has got your back!
  })
}

And then you can do some really nice function composition upstream:

  async fn someFunc(userId: UserId) -> Option<SomethingElse> {
    getUser(userId).await().map(getSomethingElseFromUser)
  }

This is what in functional programming is known as composition of functors. In the future you'll find another, more interesting type in Rust called Result which also behaves like a functor and brings more goodies with it :slight_smile:.

3 Likes

If what you want is really a C analogy, then you may think of it as

struct OptionT {
  bool has_value;
  T value;
}

where either has_value is true and value is initialized to a valid T value, or has_value is false and value may be uninitialized.

However, it's more efficient than this, since Rust guarantees the "null pointer optimization", by which Option<&T> (option of a reference type) can be optimized to something that in C would be just *T, i.e., either NULL (and you shouldn't dereference it!), or a valid pointer that you can dereference.

2 Likes

:slight_smile: looking forward to it

(But really, I must say it is a beautiful language, but hard to learn all the new concepts)

Rust's size optimizations for Option and similar are actually even better than this. If you have some enumeration that doesn't use every possible value of its discriminant, an optional value will automagically use one of the unused discriminants to mark out an option value.

enum Foo {
    Bar,
    Baz,
}

assert_eq!(std::mem::size_of::<Foo>(), std::mem::size_of::<Option<Foo>>());
2 Likes

Here another Rust beginner. As I understand it:

T is short for data Type. Examples: i32, String and Foo: Struct.

Option some notation for might not have a value.

2 Likes

T is just a naming convention for generic type parameters.

For example, if you have a function that receives a parameter which is generic, the convention is to name that generic type T:

fn myGenericFunction<T>(arg: T) -> () {
  // Do something with arg here
}

The same applies for other constructions, such as structs:

struct User<T> {
 metadata: T
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.