[Solved] How to return &str from format!()


#1

I’m trying to use a String that is part of an Error in it’s description() method.
Edit: Note this is a simplified toy example demonstrating the issue.

use std::error::Error;
use std::fmt;

#[derive(Debug)]
enum NamedError {
    Name(String),
}

impl fmt::Display for NamedError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            NamedError::Name(ref name) => write!(f, "My name is: {}", name),
        }
    }
}

impl Error for NamedError {
    fn description(&self) -> &str {
        match *self {
            NamedError::Name(ref name) => format!("My name is: {}", name),
        }
    }

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

fn main() {
    let err = NamedError::Name("Arnold".to_owned());
}

But I get a compile error:

$ cargo run
   Compiling rust-test v0.1.0 (file:///Users/hagenjt1/PycharmProjects/rust-test)
error[E0308]: match arms have incompatible types
  --> src/main.rs:19:9
   |
19 | /         match *self {
20 | |             NamedError::Name(ref name) => format!("My name is: {}", name),
21 | |         }
   | |_________^ expected &str, found struct `std::string::String`
   |
   = note: expected type `&str`
              found type `std::string::String`
note: match arm with an incompatible type
  --> src/main.rs:20:43
   |
20 |             NamedError::Name(ref name) => format!("My name is: {}", name),
   |                                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: this error originates in a macro outside of the current crate

error: aborting due to previous error

error: Could not compile `rust-test`.

To learn more, run the command again with --verbose.

Process finished with exit code 101

I tried adding a .to_str() but then the reference doesn’t live long enough. Is there an easy way to solve this?


#2

No, there’s no way to return that. However, I think you’re fine anyway. description can be some static text, and then your callers/users of the NamedError can use its Display impl if they want the more detailed information.


#3

String is equivalent to C’s malloc()-ed string that needs to be free()d, and &str is const char * that can be anything (on stack, in ROM, etc.)

In C that would be:

/** Do *not* call free() on this pointer */
const char *description(const Self *self);

And your question becomes equivalent of “how can I use malloc() in description and return it as a pointer that is never free()d?”. So the only options are return something that’s already in self, or leak memory.


#4

In rust-lang-nursery/api-guidelines#71 we determined that description() should basically never be called. So don’t worry about putting a useful message there. For example serde_json's error type just returns a description that is "JSON error".


#5

Thank you all.

What I’m gathering is that if a function returns -> &str it really is returning &'static str (which is elided). Thus it must return a 'static string literal to satisfy this.


#6

Not quite in this case - fn description(&self) -> &str returns either a reference that’s tied to the lifetime of self or a 'static reference. So the non-elided signature is fn description<'a>(&'a self) -> &'a str. But 'static is a subtype of 'a (i.e. a longer lifetime can be substituted for a shorter one), so you can also return 'static there. Note this subtype relationship is true only for immutable references.

If you had fn bar() -> &str then that really is fn bar() -> &'static str because there’s no input lifetime to associate with the output one.

You could, if you really wanted to, embed the entire error message into the String inside NamedError::Name. Then you could return a reference to that. But as mentioned, it’s unnecessary really and will distort the meaning of Name.


#7

Anytime I need to turn a String into a &str I wrap it with &[..]

// Create String
let val = "asdf".to_string();

// Create &str
let x = &val[..];