Why can I return a value referencing a local None value?

I'd like to set a struct's field to an empty &vec![], but that of course won't work inside a called function unless I pass that reference from the caller, which gets awkward with many function calls (see playground).

The cleanest work around I've found is to have that field instead be an Option, and, surprisingly, I can just set it with &None locally!

What is special about &None that lets it avoid an error returning a value referencing a local variable?

And... are there any other recommended work arounds? I know Option types are more explicit, but they can take more code to iterate and handle in general (just curious if any other best practices exist for this scenario).

Returning &None works due to RValue Static Promotion, which is the same mechanism that lets you return &42 or &[] from a function.

Combine that with the fact that a 'static lifetime can be "shortened" (the technical term is variance) to match the 'a lifetime and you've got a way to return &None.

Without knowing the actual application I'd probably side-step the issue by coming up with an alternate design that doesn't require such a convoluted flow.

You can also use methods like map(), or(), and and_then() to help simplify your resulting code's control flow. If Ob lives for a long time I would consider changing its definition to not have a lifetime (e.g. by taking ownership or receiving the filtered items as a method argument when they are used) because long-lived objects with lifetimes can cause complications (adds cognitive load, lifetimes are infectious, you need to store it as a local variable high up in the stack instead of on the heap using Box or Rc, can cause self-referential structs if you want to store it in a container, etc.).

2 Likes

None is a constant expression, which means it is subject to static promotion in an expression like &None. In short, None is "promoted" as if it were a static item and the expression gets the type &'static Option<_>.

You can manually create a static Vec and borrow it if you want to do without the Option, since Vec::new is a const fn:

fn lots_of_fn_calls<'a>(_real: &'a Vec<&'a str>) -> Ob<'a> {
    static VEC: Vec<&'static str> = Vec::new();
    filter(&VEC)
//  filter(real)
}

But this is most likely a mistake. First, &Vec<_> is a nearly useless type: you should almost always use &[_] instead, which makes the problem even easier: [] is also static-promotable just like None (example). Secondly, even so, you're probably overusing references. The smoking gun is that you have two nested 'as, which is occasionally useful, but much more frequently an error in the way you are thinking about lifetimes. I'm not sure what the case is here, but chances are very good that either (a) one of those &'as should not be a reference at all, but actually just an owned value, or more rarely an Arc or some other kind of pointer; or (b) Ob should have two lifetimes: one for the Vec and one for its contents, to avoid accidentally forcing them to be the same.

7 Likes

Thanks, I should clarify that my question is focused on the use case where you have zero or more items to store, so I thought Vec is necessary since an array must have known length in rust, otherwise I agree array is the way to go.

[&'a str] is not an array (that would be [&'a str; N] where N is some constant), but a slice. You can borrow a slice from many different types, including Vec. In fact, the playground example I gave does so in main.

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.