Reputation: 6343
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()))
}
}
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".
How can I do this properly?
Upvotes: 2
Views: 506
Reputation: 1376
this TryFrom impl, in the error case, returns something that impls InternalError
TryFrom
wants to know at compile time what error type 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 know at compile time what could go wrong, but you do not yet know what actually does go wrong (if anything). Actual errors occurring you only know at runtime. Therfore it is a good idea to have the type being able to represent a set of potential error, and the instance to be a specific error.
An enum
eration fits that bill and 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.
Upvotes: 2
Reputation: 4911
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()))
}
}
}
Upvotes: 0