What is the preferred way to return Option fields?

A reference to the Option or a reference to the option's value wrapped in a new option?

struct S {
  field: Option<String>,
}

impl S {
  fn option1(&self) -> &Option<String> {
    &self.field
  }

  fn option2(&self) -> Option<&String> {
    match &self.field {
      Some(field) => Some(field),
      None => None
    }
  }
}

If you had a mut function too, you would pick whatever is constant with that.

Note: your match can be done using self.field.as_ref()

2 Likes

Thanks for as_ref tip, didn't know about that for this case.

And for matching consistency with setter... sorry to be clueless, but is this the matching case?

struct S {
  field: Option<String>,
}

impl S {

  fn field(&self) -> &Option<String> {
    &self.field
  }

  fn set_field(&mut self, value: Option<String>) {
    self.field = value;
  }
}

Option<&T> is always better than &Option<T>

6 Likes

And Option<&str> is even better than Option<&String>.

4 Likes

Thanks for the absolute! :slight_smile:

Is there some more general rule here? I thing Result is probably the same: Return Result<&T, &E> instead of &Result<T, E> right? But for most types I think I'm supposed to a simple reference like &self.field. I'm unsure when to use that default rule, and when to use some more specialized rule.

Well, I'd say that if you really need to return a result with references, then I'd return a Result<&T, E> instead because there is no reason to keep a reference to the error, as at some point it would either be dropped (In case of execution change) or cause a panic (At which point dropping a String or a small error struct doesn't really matter)

2 Likes

The case I'm talking about is where I'm storing (caching) the result in a field. Here's the actual code:

I was thinking more in terms of reference that an explicit setter function, The matching functions would be;

  fn option1_mut(&mut self) -> &mut Option<String> {
    &mut self.field
  }

  fn option2_mut(&mut self) -> Option<&mut String> {
    self.field.as_mut()
  }

The first allows switching between Some and None but not the second.

2 Likes

Ahh I see what you mean. Thanks.

A little clarification for beginners. This one:

fn option1_mut(&mut self) -> &mut Option<String> {
    &mut self.field
}

lets you modify the option itself, so you can call take() on it, leaving None in the field, and the next call to option1_mut() will return None.

In contrast, option2_mut() only lets you modify the value inside the option, so every consecutive call to option2_mut() will return the same Option containing whatever value you set previously (or the original one, if you didn't actually modify it).

Choose wisely the option (unintentional pun) than makes sense for this particular field and this particular data structure that you're writing. For example, it makes sense to return a mutable reference to the option itself, if it's an optional configuration parameter that can be changed or "removed" any time. But it wouldn't in case of Vec::last_mut(), because that would mean that you could remove an element from a vector by calling last_mut(), which sounds crazy. Side note: it's actually slice::last_mut(), but you can call it on a Vec thanks to deref coercion.

4 Likes

I have to concur that Option<&T> is always better than &Option<T>.

For one thing, it's a bit more efficient. The &Option<T> gets represented as a pointer to the tagged union, while the Option<&T> gets optimized to a nullable pointer. Checking &Option<T> for None requires a deference, while checking Option<&T> for None can be done with just the comparison, and digging out the Some value is reduced from a pointer deference with an offset to just a deference with no offset.

For another thing, it's always possible to convert an &Option<T> into an Option<&T>, while it's impossible to go the other way. This means most other libraries expect Option<&T>, because they know it's the most flexible. This includes most of the built-in Option combinators.

4 Likes

Thanks for all the great info in this thread.

Would the general rule be to use self.field.as_ref() as your default choice if the type implements as_ref? And if not use &self.field.

And for cases where the type in the option also wants something other then &T (such as String)r is this the simplest way to make that conversion? Seems a bit long to me, but I couldn't find a better way that compiler liked.

struct MyStruct {
  name: Option<String>,
}

impl MyStruct {
  pub fn name(&self) -> Option<&str> {
    self.name.as_ref().map(|name| name.as_ref())
  }
}

Well, you can use a more functional approach and pass the conversion function directly:

self.name.as_ref().map(String::as_str)

Other than that, I don't see how you can make it shorter.

4 Likes

The difference between &Option<T> and Option<&T> can be somewhat likened to the difference between &String and &str: the former can be turned into the latter, so if you want to give the caller maximum flexibility, your APIs should accept the latter and return the former (unless doing so would be impossible or inefficient, say because it would require an allocation).

Except that returning the former might be not possible in many cases, since there's no external Option<T> to start with. Did you mean that API should return owned value (such as String or Option<T>)?

Right, you should accept a String if you really need to mutate it, and you should return a &str if you don't have a String to return in the first place. My point is just that you shouldn't weaken those guarantees for no reason.

There's another reason, which is to enable changes in the underlying implementation. Maybe you'll find you want to change it to an Option<Rc<str>> or something.

It's hard for me to think of cases where returning &String or &Option<String> makes sense. The advantages of these types over &str and Option<&str> are so vanishingly small they might as well not exist! If you're willing to expose that much information, you might as well make the field public.

2 Likes