Returning borrowed value


#1

I run into a compilation error when trying to return a value that has been borrowed. I’ve simplified the code to:

fn foo() -> String {
    let s = "Hello, World".to_string();
    let tokens: Vec<&str> = s.split(' ').collect();
    if tokens.len() > 1 && tokens[1].starts_with('W') {
        return s;
    }
    String::new()
}

(Run at https://is.gd/2IE2nt)

Here is the compilation error:

rustc 1.14.0 (e8a012324 2016-12-16)
error[E0505]: cannot move out of `s` because it is borrowed
 --> <anon>:5:16
  |
3 |     let tokens: Vec<&str> = s.split(' ').collect();
  |                             - borrow of `s` occurs here
4 |     if tokens.len() > 1 && tokens[1].starts_with('W') {
5 |         return s;
  |                ^ move out of `s` occurs here

error: aborting due to previous error

I understand that tokens borrows from s, but I can’t find an easy way to tell the compiler to destruct tokens before returning s.

Is there an easy way to fix my code to make the compiler happy? Of course I could do:

fn foo() -> String {
    let s = "Hello, World".to_string();
    let mut should_return_s = false;
    {
        let tokens: Vec<&str> = s.split(' ').collect();
        if tokens.len() > 1 && tokens[1].starts_with('W') {
            should_return_s = true;
        }
    }
    if should_return_s { s } else { String::new() }
}

But that makes the code more complicated, and is even more cumbersome in my real code where the object I want to return early is more complicated. What would be the idiomatic way in Rust to solve this?


#2

Sorry, that is the idiomatic way. I mean, you can simplify it and bit and write it like:

fn foo() -> String {
    let s = "Hello, World".to_string();
    if {
        let tokens: Vec<&str> = s.split(' ').collect();
        tokens.len() > 1 && tokens[1].starts_with('W')
    } {
        s
    } else {
        String::new()
    }
}

But non-crazy people would probably recommend you write it as:

fn foo_alt() -> String {
    let s = "Hello, World".to_string();
    let return_s = {
        let tokens: Vec<_> = s.split(' ').take(2).collect();
        tokens.len() > 1 && tokens[1].starts_with('W')
    };

    if return_s {
        s
    } else {
        String::new()
    }
}

Although, you can simplify it a bit further and get rid of the unnecessary allocations, too:

fn foo_more_alt() -> String {
    let s = "Hello, World";
    let return_s = {
        let second = s.split(' ').skip(1).next();
        second.map(|t| t.starts_with('W')).unwrap_or(false)
    };

    if return_s {
        String::from(s)
    } else {
        String::new()
    }
}

#3

You can also use something like this:

fn foo() -> String {
    let s = "Hello, World".to_string();
    if s.split(' ').nth(1).map_or(false, |s| s.starts_with('W')) {
        return s;
    }
    String::new()
}

#4

Can anyone tell if this wouldn’t be an issue if NLL were implemented?


#5

NLL should solve it, yes. The lifetime of the borrow would end after the tokens condition is evaluated.