franza
franza

Reputation: 2317

How can I use a type with a lifetime as the `error` argument to io::Error::new?

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() {

}

playground

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

Answers (1)

Shepmaster
Shepmaster

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

Related Questions