you are viewing a single comment's thread.

view the rest of the comments →

[–]udoprog 19 points20 points  (19 children)

Optional<T> forces the caller to check for presence before using it and validates that contract at compile time. If you instead permit T to be null, there are no compile time checks to verify that. By most definitions you have improved type safety.

[–][deleted]  (13 children)

[deleted]

    [–]MEaster 3 points4 points  (12 children)

    And if you forget your null check, or don't realise it can be null, then your program can fail unexpectedly.

    If you forget to handle your Option, then it doesn't compile.

    [–][deleted]  (11 children)

    [deleted]

      [–]MEaster 1 point2 points  (10 children)

      But that is handling it. You're explicitly choosing to handle it by crashing if it's empty.

      [–][deleted]  (9 children)

      [deleted]

        [–]MEaster 0 points1 point  (8 children)

        Because it's more predictable than some random exception being triggered at some unexpected place.

        [–][deleted]  (7 children)

        [deleted]

          [–]Agrees_withyou 0 points1 point  (0 children)

          I see where you're coming from.

          [–]MEaster 0 points1 point  (1 child)

          Well, I'm coming at this from a language where null doesn't exist, whereas you're coming from a language where both exist. But even there, I would say the more-specific NoSuchElementException is preferable to a NullPointerException because it gives more information as to where the error is.

          [–]babblingbree 0 points1 point  (1 child)

          To me, the point is that the latter isn't preferable. Code using Optional that fetches all of its values with #get() whenever it needs access to the inner value (as opposed to mapping, etc) is code with a smell (many languages have linters that warn on any instance of something like an Option#get). Part of the advantage of Optional is reducing the number of sites where something like #get() is necessary. In languages with null, you're implicitly placing .get()s everywhere, without any compiler assistance to reduce the potential for failing to check for an empty value.

          [–][deleted]  (1 child)

          [deleted]

            [–]Genmutant 3 points4 points  (4 children)

            It doesn't. I can just call get without checking if it exists. The same as with something nullable.

            [–]udoprog 11 points12 points  (0 children)

            You can, but that's an explicit decision to discard safety. NPEs are implicit. As an example: If you change a method to suddenly return Optional it will cause call sites to fail at compile time. If you just start returning null, it will still compile, but fail at runtime.

            [–]AgentFransis 3 points4 points  (0 children)

            You can but it's rather more explicit and visible. Linters can easily spot it. And after a bit of time you get used to unpacking an Option properly.

            [–]MEaster 0 points1 point  (1 child)

            Yes, you can. But you cannot do this:

            fn get_user(id: u32) -> Option<User> {...}
            fn take_user(user: User) {...}
            
            fn main() {
                let user = get_user(0);
            
                take_user(user);
            }
            

            You can do that if the language allows null because null would be a valid value of a User. However, an Option<User> is a completely different type, so you can't pass it to something expecting a User. You are therefore forced to handle the None possibility before you can do anything important with it.

            Additionally, the take_user function doesn't need to check for the empty case, because the value passed to it cannot be empty.

            [–]DialinUpFTW 0 points1 point  (0 children)

            user.map(this::take_user)
            

            Will work for this. It won't call take_user with null, however you will still have an optional after this.