简体   繁体   中英

Apply trait bounds to associated type

Let's say I have a trait like this:

trait InternalError {
    fn internal(error: String) -> Self;
}

If I want to use this in a function, I can do so like this:

struct MyU16(pub u16);

fn my_try_from<E: InternalError>(value: u32) -> Result<MyU16, E> {
    if value < u16::MAX as u32 {
        Ok(MyU16(value as u16))
    } else {
        Err(E::internal("invalid".to_string()))
    }
}

This works just fine .

Now imagine instead of using my own function, I want to use TryFrom :

impl<E: InternalError> TryFrom<u32> for MyU16 {
    type Error = E;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        if value < u16::MAX as u32 {
            Ok(MyU16(value as u16))
        } else {
            Err(E::internal("invalid".to_string()))
        }
    }
}

This however does not work. I have tried various different ways to phrase this, but I can't figure out how to say "this TryFrom impl, in the error case, returns something that impls InternalError".

Any advice on how to do this properly would be much appreciated. Thanks!

this TryFrom impl, in the error case, returns something that impls InternalError

TryFrom wants to know at compile time what kind of error is returned. You seem to want to decide on the type at runtime.

Let's assume FirstError and SecondError both implement InternalError .

In this case

impl TryFrom<u32> for MyU16 {
    type Error = FirstError;

    fn try_from(value: u32) -> Result<Self, FirstError> {
        /*...*/
    }
}

Would compile, but of course try_from can only ever return FirstError .

If you want to decide at runtime between FirstError and SecondError however. The associated error type (not trait) needs to be able to hold both.

impl TryFrom<u32> for MyU16 {
    type Error = Box<dyn InternalError>;

    fn try_from(value: u32) -> Result<Self, Box<dyn InternalError>> {
        /*...*/
        Box::new(FirstError::internal("invalid".to_string()))
    }
}

This also gives a hint of why enum s are the idiomatic way to represent errors in Rust: Usually you do not know at compile time what is going to go wrong. So an enum which can list all the states makes it clear for the user what could go wrong and in many situations allows you to create the error without an additional heap allocation.

The reason it won't compile is there is a rule that the type parameter in the impl declaration has to appear in the implementing type ( impl<T> Foo<T> ) or the implemented trait ( impl<T> MyTrait<T> for Foo ). You can't just write impl<T> for Foo and only mention T again inside the impl block.

As the expanded error suggests, there is a workaround introducing a phantom type to the implementing type, but you might not like how it makes your code look:

use std::marker::PhantomData;

struct MyU16<T>(pub u16, PhantomData<T>);

impl<E: InternalError> TryFrom<u32> for MyU16<E> {
    type Error = E;

    fn try_from(value: u32) -> Result<Self, Self::Error> {
        if value < u16::MAX as u32 {
            Ok(MyU16(value as u16, PhantomData))
        } else {
            Err(E::internal("invalid".to_string()))
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM