Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way to return a new string in Rust

Tags:

rust

I just spent a week reading the Rust Book, and now I'm working on my first program, which returns the filepath to the system wallpaper:

pub fn get_wallpaper() -> &str {     let output = Command::new("gsettings");     // irrelevant code     if let Ok(message) = String::from_utf8(output.stdout) {         return message;     } else {         return "";     } } 

I'm getting the error expected lifetime parameter on &str and I know Rust wants an input &str which will be returned as the output because any &str I create inside the function will be cleaned up immediately after the function ends.

I know I can sidestep the issue by returning a String instead of a &str, and many answers to similar questions have said as much. But I can also seemingly do this:

fn main() {     println!("message: {}", hello_string("")); }  fn hello_string(x: &str) -> &str {     return "hello world"; } 

to get a &str out of my function. Can someone explain to me why this is bad and why I should never do it? Or maybe it's not bad and okay in certain situations?

like image 962
eiko Avatar asked Mar 28 '17 20:03

eiko


People also ask

How do you make a new string in Rust?

pub const fn new() -> String Creates a new empty String .

Can you return a string?

Strings in C are arrays of char elements, so we can't really return a string - we must return a pointer to the first element of the string. All forms are perfectly valid.

How do you return a variable in Rust?

The return keyword can be used to return a value inside a function's body. When this keyword isn't used, the last expression is implicitly considered to be the return value. If a function returns a value, its return type is specified in the signature using -> after the parentheses () .

What is return in Rust?

A return marks the end of an execution path in a function: fn foo() -> i32 { return 3; } assert_eq!( foo(), 3); return is not needed when the returned value is the last expression in the function.

How to return a &str in rust?

Rust removes the ambiguity by splitting the two concepts into two types. If you want to return a &str in Rust you have to add generic lifetime. Example: Here have a 3 rules. Each parameter that is a reference gets it's own lifetime parameter.

What are rust strings in Java scripts?

Java Script strings use UTF-16 encoding. Rust strings are UTF-8 encoded. The first type we’ll look at is called a string slice in Rust. You would see it most of the time in the form of &str or with a lifetime associated with it. &'static str or &'a str but more on that later. You’ll see that this code does not compile in the time of writing!

Why can't I return a string from a function?

You cannot return a &str if you've allocated the String in the function. There's further discussion about why, as well as the fact that it's not limited to strings. That makes your choice much easier: return the String. String s are heap-allocated and built to be mutable. String s are heap-allocated because they have an unknown length.

What is the best way to create a new string?

&str is very useful as you don't need to allocate memory but you can't really create a new string. If you sometimes need to create a string but other times can just point to (part of) an existing string, check out Cow<str>. More complicated but very cool and has a great name. It's simple: If you can return &str, return it.


2 Answers

You cannot return a &str if you've allocated the String in the function. There's further discussion about why, as well as the fact that it's not limited to strings. That makes your choice much easier: return the String.

Strings are heap-allocated and built to be mutable.

Strings are heap-allocated because they have an unknown length. Since that allocation is solely owned by the String, that's what grants the ability to mutate the string.

My function just returns a filepath for reference purposes, and I'd rather leave it up to the caller to decide if they need a heap-stored mutable string.

This isn't possible. Your function has performed an allocation. If you don't return the allocation to the caller, then the value must be deallocated to prevent memory leaks. If it was returned after deallocation, that would be an invalid reference, leading to memory safety violations.

But I can also seemingly do this:

fn hello_string(x: &str) -> &str {     return "hello world"; } 

to get a &str out of my function. Can someone explain to me why this is bad and why I should never do it? Or maybe it's not bad and okay in certain situations?

It's not bad, it just doesn't really allow you to do what you want in your original case. That "hello world" is a &'static str, a string slice that's been stored inside the code of the program itself. It has a fixed length and is known to live longer than main.

The signature fn hello_string(x: &str) -> &str can be expanded to fn hello_string<'a>(x: &'a str) -> &'a str. This indicates that the resulting string slice must have the same lifetime as the input string. A static string will outlive any lifetime, so that's valid to substitute.

This would be useful for a function where the result is based on the input string only:

fn long_string(x: &str) -> &str {     if x.len() > 10 {         "too long"     } else {         x     } } 

However, in your case, the function owns the String. If you attempted to return a reference to a String, completely unrelated to the input string:

fn hello_string(x: &str) -> &str {     &String::from("hello world") } 

You'll run into the common error message "borrowed value does not live long enough". That's because the borrowed value only lives until the end of method, not as long as the input string slice. You can't "trick" the compiler (or if you can, that's a major bug).

like image 124
Shepmaster Avatar answered Sep 29 '22 14:09

Shepmaster


If you want to return a &str in Rust you have to add generic lifetime. Example:

fn hello_string<'life>() -> &'life str {     return "hello world"; } 

or,

fn hello_string<'life>(a: &'life str, b: &'life str) -> &'life str {     return "hello world"; } 

Here have a 3 rules.

  1. Each parameter that is a reference gets it's own lifetime parameter.
  2. If there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters.
  3. If there are multiple input lifetime parameters, but one of them is &self or &mut self the lifetime of self is assigned to all output lifetime parameters.
like image 25
shariful islam Avatar answered Sep 29 '22 13:09

shariful islam