Search code examples
rustreferenceconditional-statementsdefault-value

How can I conditionally provide a default reference without performing unnecessary computation when it isn't used?


I have some variable passed into my function by reference. I don't need to mutate it or transfer ownership, I just look at its contents. If the contents are in some state, I want to replace the value with a default value.

For instance, my function accepts a &Vec<String> and if the Vec is empty, replace it with vec!["empty"]:

fn accept(mut vec: &Vec<String>) {
    if vec.len() == 0 {
        vec = &vec!["empty".to_string()];
    }
    // ... do something with `vec`, like looping over it
}

But this gives the error:

error[E0716]: temporary value dropped while borrowed
 --> src/lib.rs:3:16
  |
1 | fn accept(mut vec: &Vec<String>) {
  |                    - let's call the lifetime of this reference `'1`
2 |     if vec.len() == 0 {
3 |         vec = &vec!["empty".to_string()];
  |         -------^^^^^^^^^^^^^^^^^^^^^^^^^- temporary value is freed at the end of this statement
  |         |      |
  |         |      creates a temporary which is freed while still in use
  |         assignment requires that borrow lasts for `'1````

Preventing the mut results in the same error as the previous example:

fn accept(input: &Vec<String>) {
    let vec = if input.len() == 0 {
        &vec!["empty".to_string()]
    } else {
        input
    };
    // ... do something with `vec`, like looping over it
}

The only solution I've come up with is to extract the default value outside the if and reference the value:

fn accept(input: &Vec<String>) {
    let default = vec!["empty".to_string()];
    let vec = if input.len() == 0 {
        &default
    } else {
        input
    };
    // ... do something with `vec`
}

This results in less clean code and also unnecessarily doing that computation.

I know and understand the error... you're borrowing the default value inside the body of the if, but that value you're borrowing from doesn't exist outside the if. That's not my question.

Is there any cleaner way to write out this pattern?

I don't believe this is a duplicate of Is there any way to return a reference to a variable created in a function? because I have a reference I'd like to use first if possible. I don't want to dereference the reference or clone() it because that would perform unnecessary computation.

Can I store either a value or a reference in a variable at the same time?


Solution

  • You don't have to create the default vector if you don't use it. You just have to ensure the declaration is done outside the if block.

    fn accept(input: &Vec<String>) {
        let def;
        let vec = if input.is_empty() {
            def = vec!["empty".to_string()];
            &def
        } else {
            input
        };
        // ... do something with `vec`
    }
    

    Note that you don't have to build a new default vector every time you receive an empty one. You can create it the first time this happens using lazy_static or once_cell:

    #[macro_use]
    extern crate lazy_static;
    
    fn accept(input: &[String]) {
        let vec = if input.is_empty() {
            lazy_static! {
                static ref DEFAULT: Vec<String> = vec!["empty".to_string()];
            }
            &DEFAULT
        } else {
            input
        };
        // use vec
    }