Reputation: 2317
I am trying to create a custom error type to use in my Rust project by implementing std::error::Error
. I've also created a small shortcut function to create std::io::Error
. Unfortunately, I'm stuck with lifetimes so I'm asking some help:
use std::error::Error;
use std::fmt;
use std::io;
#[derive(Debug)]
pub struct BadString<'a> {
desc: &'a str,
}
impl<'a> BadString<'a> {
pub fn new(desc: &str) -> BadString {
BadString{ desc: desc }
}
}
impl<'a> Error for BadString<'a> {
fn description(&self) -> &str { &self.desc }
}
impl<'a> fmt::Display for BadString<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.description())
}
}
fn bad_str_err(desc: &str) -> io::Error {
let err = BadString::new(desc);
io::Error::new(io::ErrorKind::Other, err)
}
fn main() {
}
This reports the error:
<anon>:27:30: 27:34 error: cannot infer an appropriate lifetime for automatic coercion due to conflicting requirements
<anon>:27 let err = BadString::new(desc);
^~~~
<anon>:28:5: 28:46 note: first, the lifetime cannot outlive the call at 28:4...
<anon>:28 io::Error::new(io::ErrorKind::Other, err)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<anon>:28:42: 28:45 note: ...so that argument is valid for the call
<anon>:28 io::Error::new(io::ErrorKind::Other, err)
^~~
<anon>:27:30: 27:34 note: but, the lifetime must be valid for the expression at 27:29...
<anon>:27 let err = BadString::new(desc);
^~~~
<anon>:27:30: 27:34 note: ...so that auto-reference is valid at the time of borrow
<anon>:27 let err = BadString::new(desc);
^~~~
error: aborting due to previous error
playpen: application terminated with error code 101
I am not sure how to fix that so it will compile.
Upvotes: 3
Views: 67
Reputation: 431089
Let's look at the signature of io::Error::new
:
fn new<E>(kind: ErrorKind, error: E) -> Error
where E: Into<Box<Error + Send + Sync>>
This states that error
can be any type, so long as that type implements the trait Into<Box<Error + Send + Sync>>
. That trait means that the type can be converted into a boxed trait object. The trait object itself must implement the traits Error
, Send
and Sync
. What's non-obvious is that by default, trait objects also have a 'static
lifetime bound (there's rationale for this, but it does seem to trip people up).
Let's try to do that conversion ourselves:
fn bad_str_err(desc: &str) -> io::Error {
let err = BadString::new(desc);
let foo: Box<Error + Send + Sync + 'static> = err.into();
}
And we get the same error — "cannot infer an appropriate lifetime for automatic coercion due to conflicting requirements". So our problem lies in the ability to convert to this trait object.
Send
and Sync
are two key traits that help guide the compiler to know which types are safe to send / share between threads. In order for something to be safely shared across threads, it cannot "disappear" while another thread has it. This is a type of bug that Rust helps prevent at compile time.
In this case, you are tying to use a string slice (&str
), but that slice doesn't own the underlying memory, it just references it. As soon as that memory goes out of scope, any references need to become invalid. This is another thing that Rust prevents at compile time.
In this case, the simplest thing to do is to not use a reference:
use std::error::Error;
use std::{fmt, io};
#[derive(Debug)]
pub struct BadString {
desc: String,
}
impl BadString {
pub fn new(desc: &str) -> BadString {
BadString { desc: desc.into() }
}
}
impl Error for BadString {
fn description(&self) -> &str { &self.desc }
}
impl fmt::Display for BadString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.description())
}
}
fn bad_str_err(desc: &str) -> io::Error {
let err = BadString::new(desc);
io::Error::new(io::ErrorKind::Other, err)
}
fn main() {}
A String
owns the underlying memory, so it can safely be transferred across thread boundaries and doesn't need to worry about any other object being freed accidentally.
Upvotes: 3