Why can't I return an &str value generated from a String?
Asked Answered
O

2

22

I'm having some trouble trying to grasp why I can't return an &str value generated from a String (goodness, when will as_str be ready?) and I'm doing something wrong. I get this idea because nothing that I do makes the value live long enough to use.

I'm trying to implement error::Error for a custom struct:

impl error::Error for LexicalError {
    fn description(&self) -> &str {
        let s = format!("{}", self);

        // s doesn't live long enough to do this, I've tried 
        // cloning s and using that, but still the clone doesn't
        // live long enough.
        s.trim()
    }

    fn cause(&self) -> Option<&error::Error> {
        None
    }
}

(for the complete snippet, here is the playpen)

I can't figure out how to return an &str from description, I'd like to reuse the Display logic, unless of course I'm completely misunderstanding what description should be returning (perhaps the short description of the issue). Either, I get the same issue with the return of format!(...) which is a variable I can't seem to get to live long enough to be useful to me.

Oxalate answered 21/4, 2015 at 19:24 Comment(2)
description is supposed to be a description of the error not going into details; fmt::Display is supposed to be there to augment it with details as desired.Sapro
@ChrisMorgan I'm coming from Go as the most recent language I played with and I mistakenly assumed the description method was similar to Go's Error method. Thanks for the info!Oxalate
A
23

First, let’s take a look at what lifetime is actually expected. There is an implicit lifetime in the signature of description:

fn description(&self) -> &str
// Can be rewritten as
fn description<'a>(&'a self) -> &'a str

The returned pointer must be valid for at least as long as self. Now consider s. It will hold a String, an owned string, and it goes out of scope at the end of the function. It would be invalid to return &s, because s is gone when the function returns. trim returns a string slice that borrows s, but the slice is again only valid for as long as s is.

You need to return a string slice that outlives the method call, so this rules out anything on the stack. If you were free to choose the return type, a solution would be to move the string out of the function. For that an owned string would be required, and then the return type would be String, not &str. Unfortunately, you are not free to choose the return type here.

To return a string slice that outlives the method call, I see two options:

  1. Use a &'static string slice. This will certainly outlive the call, but it requires that the string is known at compile time or to leak memory. String literals have type &'static str. This is a suitable option if the description does not contain any dynamic data.

  2. Store an owned string in LexicalError itself. This ensures that you can return a pointer to it that is valid for the entire lifetime of self. You can add a field desc: String to LexicalError and do the formatting when the error is constructed. Then the method would be implemented as

     fn description(&self) -> &str {
         &self.desc
     }
    

    For re-use, you can make Display write the same string.

According to the documentation of Error, Display may be used to provide additional detail. If you wish to include dynamic data in the error, then Display is a great place to format it, but you can omit it for description. This would allow the first approach to be used.

Arianaariane answered 21/4, 2015 at 20:4 Comment(5)
I think the second option will suit me best. Which leads me to ask a semi-related question - are String and &str interchangeable? Or, I suppose String and str in this case, other wise you'd be returning a borrowed &str. I have a poor understanding of the exact difference between String and &str other than when they occur (such as, I know the type of string literals and when building strings more complex than simple formatting I use String) and when to (slightly) choose between them.Oxalate
They are not interchangeable. A String owns its UTF-8 bytes, whereas a &str merely points to UTF-8 bytes borrowed from somewhere. String and &str are analogous to Vec<T> and &[T]. You can borrow a String to obtain a &str. In that case, the borrowed slice cannot outlive the owning String. There is a chapter about strings in the book, which may be useful.Arianaariane
I've been through the book, what you said makes more sense than what I recall. Perhaps I just hadn't gotten enough of an understanding of the language to grasp that section. Thanks for info, it's been a great help.Oxalate
In your example you show a few notations. What's the semantic difference between &'a and &'static? I get that &'static is a lifetime that lasts for the lifetime of the whole program, but I'm not sure what &'a means. What other &'<something> are available, if any?Monocyte
The 'a comes from the <'a> lifetime parameter. In addition to type parameters, methods in Rust can have lifetime parameters. So fn description<'a>(&'a self) -> &'a str means: for every lifetime 'a, if you lend out self for 'a, you get back a &str that is valid for 'a.Arianaariane
H
0

you can Box::leak() a static str from String, after use, drop it manually.

fn main() {
    let s1 = format!("abc");

    // s is static lifetime, can match the lifetime of &str
    let s = Box::leak(s1.into_boxed_str()); 

    // drop it manually.
    unsafe {
        mem::drop(Box::from_raw(s as *const str as *mut str));
    }
}
Haymaker answered 5/1 at 14:26 Comment(2)
drop is in the prelude, no need to prefix it with mem or use it. String::leak is much more straight forward than first turning the string into a Box<str> also &mut str coerces to *mut str so your as *… str caists are redundant.Forage
It's also not a good idea to leak memory on every function call.Forage

© 2022 - 2024 — McMap. All rights reserved.