TryFrom Code explosion on error types


#1

Please refer to this code.
https://play.rust-lang.org/?gist=9d2535d71f997dba662e942b4b751aec&version=nightly

impl<'a> TryFrom<&'a Value> for f64 {
    type Error = ConvertError;

<T as TryFrom<&'a Value>>::Error

Apparently, type Error = ConvertError is not really ConvertError enum, but <T as TryFrom<&'a Value>>::Error. It’s been infecting my code base to have a generic <T> on the Err.
I would appreciate any help to simplify my code.


#2

Try T::Error, should work


#3

Many thanks, the code is bit shorter now.

#[derive(Debug)]
pub enum DaoError<'a, T>
where
    T: TryFrom<&'a Value>,
    T::Error: Debug,
{
    UglyConvertError(T::Error), // now shorter
    ConvertError(ConvertError),                         // why can't it be wrapped into this?
    NoSuchValueError(String),
}

Going further, is there a way to completely eliminate T from DaoError<T>.
The complete api usage looks like this.

let life: Result<f64, DaoError<f64>> = dao.get("life");

I wish I can eliminate f64 from DaoError<f64>.


#4

I noticed that TryFrom is not consistent with Deref. The type Target you specified with impl Deref really maps to the actual type being specified, where us TryFrom type Error does not map to the type specified.


#5

I’m not sure how that would work. The error you’re capturing in DaoError is dependent on the exact type you’re calling try_from on. You can avoid some of the boilerplate with a type alias: type Result<T> = std::result::Result<T, DaoError<T>>


#6

What do you mean exactly?


#7

I mean, you can do these with Deref, in these example

impl<'a, T> std::ops::Deref for DaoError<'a, T>
where
    T: TryFrom<&'a Value>,
    T::Error: Debug{
    type Target = ConvertError;
    fn deref(&self) -> &Self::Target {
        match *self{
            DaoError::ConvertError(ref e) => e,
            _ => panic!("only for converterror")
        }
    }
}

The return type of fn deref is really type &ConvertError and not some T:Target, like in T:Error. I’m guessing because of the : ?Sized in type Target: ?Sized; of Deref.


#8

Oh, you meant Deref returns a reference to Self::Target whereas TryFrom returns the error as Self::Error (i.e. no reference).

That’s because Deref, by design, is borrowing something from &self. TryFrom is a (fallible) conversion, and so it takes self in the try_from function. As such, there’s nothing to borrow from in the general case. You can implement TryFrom for a reference to something, whereby self becomes a reference, and then you can make Self::Error become a reference as well. Here’s a super contrived example:

#![feature(try_from)]
use std::convert::TryFrom;

struct Foo<T: std::error::Error> {
    e: T
}

struct ConvertTo<'a, T: std::error::Error + 'a> {
    e: &'a T
}

impl<'a, T: std::error::Error> TryFrom<&'a Foo<T>> for ConvertTo<'a, T> {
    type Error = &'a T;
    
    fn try_from(f: &'a Foo<T>) -> std::result::Result<Self, Self::Error> {
        Ok(ConvertTo {e: &f.e})
    }
    
}

However, most of the time conversions will be done on values, not references.

So I wouldn’t say that Deref and TryFrom are “inconsistent” - they serve different needs, and have different use cases in mind.