How do I make a reference in match live long enough?


#1
fn get_home_dir<'a>() -> Result<&'a str, io::Error> {
    match env::home_dir() {
        Some(ref p) => {
            match p.to_str() {
                Some(home_path) => {
                    Ok(home_path)
                },
                None => Err(io::Error::new(io::ErrorKind::Other, "Home dir string not utf8")),
            }
        },
        None => Err(io::Error::new(io::ErrorKind::Other, "Could not read home dir.")),
    }
}

I get this error:

src/main.rs:35:14: 35:19 error: borrowed value does not live long enough
src/main.rs:35 Some(ref p) => {
^~~~~

And I really don’t know what to do about it. I get what lifetimes are for, but how do I make p live as long as the function?


#2

Your problem is that env::home_dir() is just a temporary.
By saying <'a> and -> Result<&'a str..., you are saying that whatever is returned from this function must live as long as the scope that the function was called from. There is no way a temporary in a function could live as long as the caller of that function.
When the function ends, env::home_dir() is no more. You cannot return a reference to a temporary(saved from a dangling pointer bug).
Let me think what other solution there is.


#3

I’d do it like that:

fn get_home_dir() -> Result<String, io::Error> {
    match env::home_dir() {
        Some(ref p) => {
            match p.to_str() {
                Some(home_path) => {
                    Ok(String::from(home_path))
                },
                None => Err(io::Error::new(io::ErrorKind::Other, "Home dir string not utf8")),
            }
        },
        None => Err(io::Error::new(io::ErrorKind::Other, "Could not read home dir.")),
    }
}

Note that it returns String and instead of Ok(home_path) -> Ok(String::from(home_path))

May be someone has a better solution but I think this is needed because you can’t have references to a temporary, so you need to save it in a “buffer”.


#4

Thank you for your reply. It works, but I don’t really understand why. Why does it work with String and not with &str?


#5

str is a string, it may live somewhere in memory.
&str is a reference to a str that lives somewhere in memory, &str could be in other “scopes” than the original str.
String is what you would typically associate in other programming languages a string with. If you know C++, you could compare it with std::string. It is a structure on stack that keeps a pointer to a buffer on the heap, that buffer contains a str.

Rust by default moves values(if the “variables” do not implement the Copy trait).
Let’s say func1 calls func2 -> String(which returns a String). What happens is that String is moved from the stack of func2 into the stack of func1. And by moving I mean: it makes a copy of that struct that is on the func2 stack, but not of what is on the heap, so that big str is not copied, it stays there like nothing happened. So that pointer to the buffer is copied into func1 stack and now func1 is the owner of that String that was returned.

Why &str doesn’t work is because when func2 finishes, it pops its stack, so every temporary variable that was on its stack will be dropped. If it would of worked to return a reference to one of those temporaries, you would of hit a bug where you application could of tried to access or do something with that block of memory that doesn’t exist any more.
When you try to access that memory that doesn’t exist any more, this is called undefined behavior. There might be some other data there, you could try to overwrite it, and change the behavior of the application, or there might be nothing there and the application to just crash, many bad things can happen.

Rust saves you from these kind of mistakes.


#6

Thank you for the explanation. I think I get it now, but Rust really is very different from other systems programming languages and requires a lot of learning.


#7

Yes, to enforce its safety measures it increases mental load on the user(developer).
Of course with practice this becomes easier(I think this is easier than to try learn and remember all of the issues that can arrise in C/C++ without these safety measures - you usually don’t know of most of the issues so this is why people might think that in C/C++ is easier to write code than in Rust).


#8

Don’t take it by reference here. env::home_dir() returns an Option<PathBuf> and PathBuf is owned string. You should just steal it from the return value and return it.

You can convert it to String (owned string), without reallocation, via .into_os_string().into_string() chain, but I would actually recommend returning the PathBuf, because in some environments the path exist, but not be valid unicode string, i.e. the to_str()/into_string() can fail, but when they do, a home directory still exists, it just has a weird name.


#9

Yep, this is a better solution.

@mkocs The idea is that instead of making a copy of a temporary, just take ownership of that temporary.


#10

@mkocs, I should add that this is an important difference between Rust and C++. In C++, taking something by value means by default a copy, so you are trained to take anything non-trivial by reference. But in Rust, by value means a move unless copy is trivial, and is always trivial, plus safe, because Rust prevents access to moved-from values. So it makes sense to take things by value much more often.