Error of TryInto Result needs to be Infallible when call-in function with generic return

I use the lib antonok/enum_dispatch to transform trait objects into concrete compound types. But I meet a strange behavior with TryInto.

Here the macro implementation of core::convert::TryInto expansion.rs#L147-157.

Here a simple implementation
    use std::borrow::Borrow;
    use enum_dispatch::enum_dispatch;
    use strum_macros::{IntoStaticStr, EnumDiscriminants };
    use serde::{Serialize, Deserialize};
    use std::convert::{Infallible, TryInto};
    use std::error::Error;

    #[derive(Serialize, Deserialize, Clone, Default, Debug)]
    struct A {
        a: i32
    }

    #[derive(Serialize, Deserialize, Clone, Default, Debug)]
    struct B {
        b: i8
    }

    #[enum_dispatch]
    #[derive(EnumDiscriminants, Serialize, Deserialize, Clone, Debug)]
    #[strum_discriminants(derive(IntoStaticStr))]
    #[strum_discriminants(name(ModelKind))]
    enum Model {
        B(B),
        A(A)
    }

    impl Model {
        fn vec_u8(&self) -> Result<Vec<u8>, bincode::error::EncodeError> {
            bincode::serde::encode_to_vec(self, bincode::config::standard())
        }

        fn from_vec_u8(v: &[u8]) -> Model {
            let output = bincode::serde::decode_from_slice(v, bincode::config::standard()).unwrap();
            output.0
        }
    }

    #[enum_dispatch(Model)]
    trait Action where Self: Serialize {
        fn key(&self) -> i64;
    }

    impl Action for B {
        fn key(&self) -> i64 {
            self.b.into()
        }
    }

    impl Action for A {
        fn key(&self) -> i64 {
            self.a.into()
        }
    }

And I want to create a method to unwrap the try_into() method. This implementation work :white_check_mark: :

    fn get_inner_by_param<T,M: TryInto<T, Error = &'static str>>(m: M) -> T {
        m.try_into().unwrap()
    }
...
    let m = Model::A(A::default());
    let a: A = get_inner_by_param(m);
    // OK

But, If I call it inside a function with a generic output that's doesn't compile :x: :

    fn get_inner_by_param<T,M: TryInto<T, Error = &'static str>>(m: M) -> T {
        m.try_into().unwrap()
    }
...
    fn get_inner<T>() -> T {
        let model = Model::A(A::default());
        get_inner_by_param(model)
    }
// Error
error[E0271]: type mismatch resolving `<main3::tests::Model as TryInto<T>>::Error == &'static str`
  --> src/main3.rs:63:9
   |
63 |         get_inner_by_param(model)
   |         ^^^^^^^^^^^^^^^^^^ expected `&str`, found enum `Infallible`
   |
note: required by a bound in `get_inner_by_param`
  --> src/main3.rs:57:43
   |
57 |     fn get_inner_by_param<T,M: TryInto<T, Error = &'static str>>(m: M) -> T {
   |                                           ^^^^^^^^^^^^^^^^^^^^ required by this bound in `get_inner_by_param`

Why does the error need to be Infallible in this case? A solution does exist?

You need to constrain T in get_inner to be a type that can be converted from your enum. Currently there's no constraints on it so the caller could pick any type they wanted.

I think the error message you're seeing is coming from one of the blanket impls on TryFrom but I'm not 100% clear on what's happening there since I don't know what other impls might be involved in your types.

Thank you @semicoleon. I tried to constraints T for get_inner like.

    fn get_inner<T: From<Model>>() -> T {
        let model = Model::A(A::default());
        get_inner_by_param(model)
    }

But the same error is thrown.

Maybe I didn't quite understand what you mean.

I think the bound you want is

fn get_inner<T>() -> T where Model: TryInto<T, Error = &'static str> {
    let model = Model::A(A::default());
    get_inner_by_param(model)
}

Basically you need the same bound as get_inner_by_param but instead of a type parameter M you have your concrete type Model filled in.


Here's a trivial complete example
Playground

#![allow(dead_code, unused_variables)]
struct Test;

impl TryFrom<u32> for Test {
    type Error = &'static str;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        if value > 0 {
            Ok(Test)
        } else {
            Err("Nope")
        }
    }
}

fn get_inner_by_param<T, M: TryInto<T, Error = &'static str>>(m: M) -> T {
    m.try_into().unwrap()
}

fn get_inner<T>() -> T
where
    u32: TryInto<T, Error = &'static str>,
{
    get_inner_by_param(1u32)
}

fn main() {
    let t: Test = get_inner_by_param(1u32);

    let t2: Test = get_inner();
}
2 Likes

@semicoleon Thanks! I didn't know we could type with a concrete type like that T where Model: TryInto<T, Error = &'static str>. Do you have any reading advice of rust documentation for this typing way?

There's nothing special in this syntax, in fact. When you have a bound like where Type: Trait, it's not relevant where exactly the generic parameter is - it can be somewhere in type, somewhere in trait, or both (or, if/when this RFC lands - nowhere at all).

2 Likes