I'm currently working on a crate to allow sandboxing different interpreted languages in Rust (e.g. running Lua in Rust, but also other languages).
The different machines, which will support different lanuages, implement the following trait (simplified version):
pub trait VirtualMachine {
type Datum: BasicDatum;
fn run(
&mut self,
code: &str,
args: Vec<Self::Datum>,
) -> Result<Vec<Self::Datum>, Box<dyn std::error::Error>>;
}
As each language has their own (dynamic) type system, I use an associated type VirtualMachine::Datum
to denote what kind of arguments and return values the language operates with.
To later be able to switch languages, each Datum
implements a minimal interface, given as trait BasicDatum
(again simplified for the purpose of demonstrating the problem, i.e. only covering bools and integers):
pub trait BasicDatum {
// extracts an i32 if value can be seen as i32
fn i32(&self) -> Option<i32>;
// extracts a bool if value can be seen as bool
fn bool(&self) -> Option<bool> {
self.i32().map(|x| x != 0)
}
// creates a datum (BasicDatum) from a given i32
fn from_i32(value: i32) -> Self
where
Self: Sized;
// creates a datum (BasicDatum) from a given bool
fn from_bool(value: bool) -> Self
where
Self: Sized,
{
match value {
false => Self::from_i32(0),
true => Self::from_i32(1),
}
}
}
The above example does not utilize the TryFrom
and TryInto
traits. However, I would like to provide such implementations in order to be able to write datum.try_into::<i32>()
, for example.
If I have a particular BasicDatum
, let's call it SomeDatum
, I can easily implement it as follows:
impl TryFrom<SomeDatum> for i32 {
type Error = ();
fn try_from(value: SomeDatum) -> Result<Self, Self::Error> {
value.i32().ok_or(())
}
}
As I will soon have more data types and more conversions into Rust types, I would like to avoid the boilerplate for each BasicDatum
type.
But I can't do a generic implementation:
impl<T: BasicDatum> TryFrom<T> for i32 {
type Error = ();
fn try_from(value: T) -> Result<Self, Self::Error> {
value.i32().ok_or(())
}
}
I get the following errors:
error[E0119]: conflicting implementations of trait `std::convert::TryFrom<_>` for type `i32`
--> src/main.rs:66:1
|
66 | impl<T: BasicDatum> TryFrom<T> for i32 {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: conflicting implementation in crate `core`:
- impl<T, U> TryFrom<U> for T
where U: Into<T>;
error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct<T>`)
--> src/main.rs:66:6
|
66 | impl<T: BasicDatum> TryFrom<T> for i32 {
| ^ type parameter `T` must be used as the type parameter for some local type
|
= note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local
= note: only traits defined in the current crate can be implemented for a type parameter
Some errors have detailed explanations: E0119, E0210.
For more information about an error, try `rustc --explain E0119`.
For a full example, see Playground.
Regarding the first error (E0119
), I believe this is because there could be an infallible conversion. That error might (in theory) be avoided with a negative implementations, such as:
impl<T: BasicDatum> !From<T> for i32 {}
But this won't work for the same reason why I can't fix the second error, which are orphan rules (if I understand it right).
But what can I do? Should I…
- write a macro that allows providing all
TryFrom
implementations for a newBasicDatum
type? (And how do I make sure everyBasicDatum
type is doing so?) - not implement
TryFrom
/TryInto
at all? - use an entirely different approach?