The `?` operator, `Option` and borrowing


#1

I posted to Reddit a while back about the ? operator and the Option type, wherein I learned this is a valid but somewhat under documented thing to do in Rust.

I’m trying to get to Swift-like semantics, where it’s easy to traverse a chain of Options using ? as a quasi-monadic operator.

Alas, it’s not so easy in Rust b/c ownership. As an example, the following playground provides two function definitions which I’d expect to be equivalent but are not:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=3ab8abd03d0787dfe0735797667fb62a

I suspect the difference between the two is my use of ref h. The following example demonstrates a similar set of issues to the ? operator although the exact errors are different:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=457a5460c47ca4c5acff5315a4087c61

I don’t know if this is a bug with the ? operator, or an expected limitation of the operator, or something else entirely. I suspect defining ? in terms of reference taking might address my issue, but it might well cause others that I’m completely blind to.

BTW, the error suggesting &'a mut self is a complete red herring.


#2

(As an aside, this code also demonstrates some of the infelicities of the String and str types. I’d like to pass an &str to the get function but instead I’ve got to construct a String and pass a reference; I’d like to return &str and I could with some additional code, but whereas there’s automatic coercion of &String to &str in the parameter to a function, there doesn’t appear to be coercion in the return value.)


#3

As for the first one, calling ? on a value ends up consuming the value, meaning that you’re consuming self's member which is not okay.
And for the third function in the second link, you end up moving the value into the function that ? calls, consuming self's value, which again, isn’t okay.


#4

This can be fixed by using

self.vals.as_ref()?.get(&String::from(key))

to transform the &Option<...> to an Option<&...>.


#5

These appear to work:

impl Example {
    fn get_val<'a>(&'a self, key: &str) -> Option<&'a String> {
        if let Some(ref h) = self.vals {
            h.get(key)
        } else {
            None
        }
    }
    
    fn get_val_qn<'a>(&'a self, key: &str) -> Option<&'a String> {
        self.vals.as_ref()?.get(key)
    }
}

Not sure why you were calling String::from here… The HashMap::get method doesn’t actually require &Strings, so &str should work just fine.


#6

doh. I thought I had a type mismatch at some point.

So, to conclude, sprinking as_ref() is necessary to avoid ? from consuming the input.

Thanks!


#7

I’d not phrase it this way. ? will always consume your Result, and in the “good” case return what’s inside, and in the “bad” case propagate the error from the function. Your problem was, you did not have a Result, but a &Result, which can’t be consumed. Same for Option, which you were using in this case.