Signatures of Functions with Borrowed Return Type Value Lifetimes, and Mutability of String Literals

Why does the following code give an error message error missing lifetime specifier for the return value (i.e -> &str) of the try_strings() method?

It also gives a help message this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `s_l`, one of `s_m`'s 0 elided lifetimes, or `s_m_ref` . What does this mean?

If I remove the , &stringInMemory argument from being sent to try_strings(), and remove this argument , s_m_ref: &str so it's not expected to be received by try_strings(), then the error disappears.

Furthermore, noting that a Rust &str type of (aka String Slices / String Literals) have a fixed size and CAN NOT be mutated, whereas the String Type (aka In-Memory Strings) CAN be mutated, is my understanding correct that it is legal for stringLiteral's binding to be mutable (the reason why the return value of type &str overwrites the preexisting value of stringLiteral, whilst it is illegal to mutate the binding that stringLiteral points to, which is the reason why performing stringLiteral.trim() afterwards will not remove the new line characters (i.e. \n\n\n) from the end of the returned string? (credit to steveklabnik for previously helping me with this)

use std::old_io;

fn main() {
    let mut stringLiteral: &str = "String Slice / String Literal";

    let mut stringInMemory: String = "In-Memory String".to_string();
    stringInMemory.push_str(" Modified");
    
    stringLiteral = try_strings(stringLiteral, stringInMemory, &stringInMemory);

    println!("stringLiteral trim() is {}", stringLiteral.trim() );
}

fn try_strings(s_l: &str, s_m: String, s_m_ref: &str) -> &str {

  let mut _s_m: String = s_m; // Declare s_m as a mutable local variable _s_m

  let mut _s: String = s_l.to_string();
  _s.push_str(", is a growable string and is guaranteed to be UTF-8");
  println!("{}", _s);

  // s_m_ref is a reference to a String that has been automatically coerced into String Slice
  let mut _s_m_ref: String = s_m_ref.to_string(); 

  // Function to Convert Type &str into Type String (cheap solution that allocates it to memory)
  fn convert_to_string_in_memory_taking_string_in_memory(memory_string: String) {
    let memory_string_slice: &str = memory_string.as_slice();
    println!("Converted Type &str (String Slice / Literal) into Type String (In-Memory String): {}", memory_string_slice);
  }

  // Coerce Type &str into Type String
  convert_to_string_in_memory_taking_string_in_memory(_s_m);

  // Function to Convert Type String into Type &str
  fn convert_to_string_literal_taking_string_slice(slice: &str) {
    println!("Converted Type String (In-Memory String) into Type &str (String Slice / Literal): {}", slice);
  }

  // Coerce Type String into Type &str by prefixing with an & symbol
  convert_to_string_literal_taking_string_slice(&_s);

  return "Test String to override the non-mutable value of the String Slice / Literal\n\n\n";
}

Since you return a static string slice, you can write the return type as &'static str.

The reason why this error occurs when you add a second reference parameter is that by default there is something called lifetime inference: The function definition

fn foo(reference: &u32) -> &u32

is implicitely interpreted as

fn foo<'a>(reference: &'a u32) -> &'a u32.

With two parameters it does not know which parameter the lifetime of the return value belongs to.

2 Likes

I think this is a significant ergonomics issue for newtypes.

Could you be more specific? It's not obvious to me what "this" is in relation to newtypes.

1 Like

Oops, I replied in the wrong thread, sorry! My comment was about the proposal to (re-)allow destructuring self arguments in the argument list, and "this" is the current state of things.