Erik
Erik

Reputation: 401

How can I wrap any impl of std::error::Error to ease error propagation?

I'm trying to simplify the error flow in a webapp I'm working on, and my plan was to make a struct that implements std::error::Error and just forwards the result of description() for whatever kind of error it's wrapped around. I've implemented From for the types of errors I want to wrap, so this struct makes it easy to use try! to get a uniform error result. Here's what I have so far for the struct:

#![feature(convert)]
use std::error::{Error};
use std::fmt::{self,Display,Formatter};
use std::io::{self,Read};
use std::ops::Deref;
use std::fs::{File};

#[derive(Debug)]
pub struct StrErr{
    desc:String,
    c: Option<Box<Error>>
}
impl StrErr{
    pub fn new(msg:String) ->Self{
        StrErr{desc:msg, c:None}
    }
}

impl Error for StrErr{
    fn description(&self) -> &str{
        self.desc.as_str()
    }
    fn cause(& self) -> Option<& Error> {
        self.c.map(|e| e.deref())
    }
}

impl Display for StrErr {
    fn fmt(&self, f:&mut Formatter) -> Result<(),fmt::Error> {
        f.write_str(self.desc.as_str())
    } 
}

impl From<io::Error> for StrErr {
    fn from(o:io::Error) -> Self {
        StrErr{desc: String::from(o.description()),c:Some(Box::new(o))}
    }
}


fn main(){
    let contrived = Some("foo.txt")
        .ok_or_else(|| StrErr::new(String::from("error message")))
        .and_then(|filename| Ok(try!(File::open(filename))))
        .and_then(|mut file| {
            let mut content = String::new();
            try!(file.read_to_string(&mut content));
            Ok(content)
        });
    if let Ok(content) = contrived {
        println!("Got content: {}", content);
    } else {
        println!("got an error");
    }
}

playground

The problem is with the cause() method - I can't return a reference to the inner Error instance because e doesn't live long enough. Is there a different way I can structure this so that I can keep the generic reference to anything that implements Error (which I currently do by putting it in a Box) but I can still fully implement the Error trait (which is expecting a ref to the parent Error)?

I've worked around it by just punting on cause() and having it return None, but I'd much rather conform to the intent of the trait.

rustc 1.2.0-nightly (613e57b44 2015-06-01) (built 2015-06-02)

Upvotes: 3

Views: 1439

Answers (1)

Shepmaster
Shepmaster

Reputation: 432239

This is one way you can convert an Option<Box<Trait>> to an Option<&Trait>. I'm avoiding all of the trait implementation to clearly show the interesting code:

use std::error::Error;

pub struct StrErr {
    c: Option<Box<Error>>
}

impl StrErr {
    fn cause(&self) -> Option<&Error> {
        self.c.as_ref().map(|e| &**e)
    }
}

fn main() {}

We use Option::as_ref to avoid consuming the self.c item. The map closure is provided with a &Box<Trait>, so we dereference it twice to get to a Trait, and then reference it once to get to a &Trait.

Upvotes: 4

Related Questions