0

Consider the following code:

enum Inflection {
    Question_NoYelling,
    Question_Yelling,
    Yelling_NoQuestion,
    Other,
}

fn is_whitespace_or_question_mark(c: char) -> bool {
    match c {
        ' ' => true,
        '?' => true,
        '\t' => true,
        '\n' => true,
        _ => false,
    }
}

fn split_message_into_words_and_punctuation(message: &str) -> Vec<&str> {
    String::from(message).split(is_whitespace_or_question_mark)
        .filter(|element| element.len() != 0).collect();
}

fn is_uppercase(word: &str) -> bool {
    if word != "?" && word == &word.to_string().to_uppercase() {
        true
    } else {
        false
    }
}

fn get_inflection(message: &str) -> Inflection {
    let mut is_question: bool = false;
    let mut is_yelling: bool = false;

    let words_and_punctuation: Vec<&str> = split_message_into_words_and_punctuation(message);

    for element in  words_and_punctuation.iter() {
        if is_uppercase(element) {
            is_yelling = true;
            break;
        }
    }

    if words_and_punctuation[words_and_punctuation.len() - 1] == "?" {
        is_question = true;
    }

    match (is_question, is_yelling) {
        (true, true) => Inflection::Question_Yelling,
        (true, false) => Inflection::Question_NoYelling,
        (false, true) => Inflection::Yelling_NoQuestion,
        (false, false) => Inflection::Other,
    }
}

fn reply_to_non_empty(message: &str) -> &str {
    match get_inflection(message) {
        Inflection::Question_NoYelling => "Sure.",
        Inflection::Question_Yelling => "Whoa, chill out!",
        Inflection::Yelling_NoQuestion => "Calm down, I know what I'm doing!",
        Inflection::Other => "Whatever.",
    }
}

pub fn reply(message: &str) -> &str {
    let message: &str = String::from(message).trim();

    match message {
        "" => "Fine. Be that way!",
        _ => reply_to_non_empty(message),
    }
}

The compiler complains about two locations:

pub fn reply(message: &str) -> &str {
    let message: &str = String::from(message).trim();

    match message {
        "" => "Fine. Be that way!",
        _ => reply_to_non_empty(message),
    }
}

and

fn split_message_into_words_and_punctuation(message: &str) -> Vec<&str> {
    String::from(message).split(is_whitespace_or_question_mark)
        .filter(|element| element.len() != 0).collect();
}

In both cases, the compiler says that String::from(message) does not live long enough. But, why does it matter if String::from(message) doesn't live long enough, when I am only using it to get either a string literal, or a Vec of string literals in the end?

For reference, an example of the compiler's complaint:

error[E0597]: borrowed value does not live long enough
  --> src\lib.rs:69:25
   |
69 |     let message: &str = String::from(message).trim();
   |                         ^^^^^^^^^^^^^^^^^^^^^       - temporary value only lives until here
   |                         |
   |                         does not live long enough
   |
   |
note: borrowed value must be valid for the anonymous lifetime #1 defined on the function body at 68:1...
  --> src\lib.rs:68:1
   |
68 | / pub fn reply(message: &str) -> &str {
69 | |     let message: &str = String::from(message).trim();
70 | |
71 | |     match message {
...  |
74 | |     }
75 | | }
   | |_^
bzm3r
  • 3,113
  • 6
  • 34
  • 67
  • 1
    Relevant question [Return local String as a slice (&str)](https://stackoverflow.com/q/29428227/2731452) – red75prime Dec 29 '17 at 04:17
  • @red75prime Hmm, so if I am understanding correctly, a `String` created using `String::from` isn't "owned" by the function within which it was created? – bzm3r Dec 29 '17 at 04:28
  • 2
    The string is "owned" by the function. That's why the string gets destroyed on the function's exit and the reference you try to return has nowhere to point to. – red75prime Dec 29 '17 at 04:34
  • @red75prime Yeah, I think I see now. Basically, instead of using references to strings, it is better to actually pass around the string itself, as the answer to the question you referenced discusses. – bzm3r Dec 29 '17 at 04:44
  • 1
    In this case, you can fix both errors by just deleting `String::from`. No need to allocate a new buffer and copy the data when you can just... *not* copy it. – trent Dec 29 '17 at 20:41
  • @trentcl but isn't the trim function implemented on `String` and not `&str`? – bzm3r Dec 29 '17 at 21:53
  • 1
    [Nope.](https://doc.rust-lang.org/std/primitive.str.html#method.trim) `trim` doesn't mutate the string; it just returns a substring. – trent Dec 30 '17 at 02:01

2 Answers2

1

The String created inside a function can only live as long that function execution. You cannot return a reference to String because the reference will be invalid once the function returns.

Malice
  • 1,457
  • 16
  • 32
1

This is what things look like from code calling reply()

'life_a: {  
    // string_a's lifetime is 'life_a
    let string_a : String = String::from("blah blah blah");
    // str_a's lifetime is inherited from string_a, so 
    // its lifetime is 'life_a
    let str_a : &str = string_a.as_str();
    // reply_result's lifetime should also be 'life_a, because that
    // is promised by reply's type.
    let reply_result : &str = reply(str_a);
    ...code happily using string_a, str_a, and reply_result goes here...
} 
// 'life_a is over, so string_a, str_a, and reply_result 
// are no longer accessible.

Here I've added in the implicit lifetime parameters that Lifetime Elision takes care of.

pub fn reply<'a>(message: & 'a str) -> & 'a str {
  'life_b: {
    // string_from_message has lifetime 'life_b.  message is 
    // completely copied, so the lifetime of string_from_message is 
    // now completely independent of message.
    let string_from_message : String = String::from(messsage);
    // message_lifeb inherits its lifetime from string_from_message
    let message_lifeb: &str = string_from_message.trim();

    match message_lifeb {
        // this case returns a & 'static str, which is fine because
        // it will live at least as long as message : & 'a str
        "" => "Fine. Be that way!",
        // this case (presumably) returns a & 'b str, based on the
        // lifetime of message_lifeb.  The compiler knows `life_b is
        // not long enough to satisfy the output lifetime requirement.
        // So the compiler complains about lifetimes.
        _ => reply_to_non_empty(message_lifeb),
    }
  } // 'life_b ends here
}
NovaDenizen
  • 5,089
  • 14
  • 28