Error type as associated type for a trait


#1

We are implementing a trait called Document.

pub trait Document :
    type Err;
    fn index(self, off: usize) -> result::Result<Self, Self::Err>;
}

And enumeration type Json implements this trait as:

impl Document for Json {
    type Err=Error;

    fn index(self, off: usize) -> result::Result<Json, Error> {
        match self {
            Json::Array(mut a) => if off < a.len() {
                    Ok(a.remove(off))
                } else {
                    Err(Error::IndexUnbound(off, off))
                },
            _ => Err(Error::NotMyType("json is not array".to_string())),
        }
    }
}

where Error is implemented in json module along with Json type.

Subsequently we are invoking Document methods on a type parameter like:

type Result = result::Result<T, myapp::Error>
fn do_index_lookup<D>(off: usize, doc: D) -> Result<D> where D: Document {
    Ok(doc.index(off)?)
}

The above code is part of the application code, that defines its own Error type -
myapp::Error, which implements.

impl From<json::Error> for myapp::Error {
    fn from(err: json::Error) -> myapp::Error {
        ...
    }
}

Going back to the do_index_lookup() function compiler give the following error:

error[E0277]: the trait bound `myapp::Error: std::convert::From<<D as document::Document>::Err>` is not satisfied
   --> src/myapp.rs:225:41
    |
225 |             Ok(doc.index(off)?)
    |                ^^^^^^^^^^^^^^^ the trait `std::convert::From<<D as document::Document>::Err>` is not implemented for `myapp::Error`
    |
    = help: consider adding a `where myapp::Error: std::convert::From<<D as document::Document>::Err>` bound
    = note: required by `std::convert::From::from`

error: aborting due to previous error

The gears are not falling in place. Need some help.


#2

I think you need to update the where clause in do_index_lookup(). At the moment, it just says D implements Document, but you need to also say that D::Err can be converted into myapp::Error.

I imagine it’d look something like this:

fn do_index_lookup<D>(off: usize, doc: D) -> Result<D> 
  where D: Document,
        D::Err : Into<myapp::Error>
{
    ...
}

#3

Adding that doesn’t seem to solve the problem.


#4

@Michael-F-Bryan I think you are right. We need to add type contraint, and the compiler error message already suggest them.

error[E0277]: the trait bound `myapp::Error: std::convert::From<<D as document::Document>::Err>` is not satisfied
   --> src/myapp.rs:225:41
    |
225 |             Ok(doc.index(off)?)
    |                ^^^^^^^^^^^^^^^ the trait `std::convert::From<<D as document::Document>::Err>` is not implemented for `myapp::Error`
    |
    = help: consider adding a `where myapp::Error: std::convert::From<<D as document::Document>::Err>` bound
    = note: required by `std::convert::From::from`

error: aborting due to previous error

Only that I had to wire up that constrain in every call path. Which is tedious and laborious . Wonder there is any way to avoid that …


#5

You can add a trait bound on the associated type itself:

pub trait Document {
    type Err: Into<myapp::Error>;
    ...
}

#6

Tried that, Throwing the following error:

error[E0277]: the trait bound `myapp::Error: std::convert::From<<D as document::Document>::Err>` is not satisfied
   --> src/myapp.rs:238:25
    |
238 |         Some(off) => Ok(doc.index(off)?),
    |                         ^^^^^^^^^^^^^^^ the trait `std::convert::From<<D as document::Document>::Err>` is not implemented for `myapp::Error`
    |
    = help: consider adding a `where myapp::Error: std::convert::From<<D as document::Document>::Err>` bound
    = note: required by `std::convert::From::from`

error: aborting due to previous error

Although the documentation https://doc.rust-lang.org/stable/std/convert/trait.Into.html claims that Into is reflexive, looks like Into<> does not imply a From<> ?


#7

Right, it doesn’t imply that. And one might be tempted to add the following bound to Document instead:

trait Document where MyError: From<Self::Err> { ... }

But then you still need to repeat the same bound because implied bounds don’t exist for anything other than the bounds on the associated type itself.

In your case, you can write the following slightly more verbose version (keeping your existing Into bound on the associated type):

Some(off) => Ok(doc.index(off).map_err(Into::into)?),

#8

Lovely. When all the gears fell in place this one issue was poking out like an ugly shaft. @vitalyd suggestion has beautifully sorted it out. I wish I can give a dozen likes for this.

Fixing it ASAP - https://github.com/bnclabs/db-rs/issues/8

Thank you,