Rust generic method in trait object - rust

I'm trying to implement library that runs scripts written in various languages and extracts some callable object from them. Especially, I'm interested in function get_engine, which returns to you a factory by specifying file extension. Current implementation is:
#[cfg(test)]
mod tests;
use std::error::Error;
pub struct AutoLibrary<'a> {
engines: Vec<
Box<
dyn AutomationFactory<
'a,
dyn AutomationScript<'a, dyn AutomationCommand<'a>>,
dyn AutomationCommand<'a>,
dyn Error,
>,
>,
>,
}
impl<'a> AutoLibrary<'a> {
fn get_engine(
&self,
name: impl AsRef<str>,
) -> Box<
dyn AutomationFactory<
'a,
dyn AutomationScript<'a, dyn AutomationCommand<'a>>,
dyn AutomationCommand<'a>,
dyn Error,
>,
> {
todo!()
}
}
pub struct AssFile {/* doesn't matter for now */}
pub trait AutomationCommand<'a> {
fn call(&self, file: AssFile) -> AssFile;
}
pub trait AutomationScript<'a, C>
where
C: AutomationCommand<'a>,
{
fn commands(&self) -> Vec<C>;
}
pub trait AutomationFactory<'a, S, C, E>
where
C: AutomationCommand<'a>,
S: AutomationScript<'a, C>,
E: Error,
{
fn load_script(&self, path: impl AsRef<str>) -> Result<Box<S>, E>;
}
Which doesn't compile for now. Compiler tries to say me that trait objects cannot contain generic methods, but there are no generic methods, only generic implementations. Also for that particular case I can't understand the reasoning. Compiler knows actual trait that object generic of and therefore can build and return a vtable, because the trait itself Always consumes the &self and in final implementation will always return specific objects.

load_script is generic, due to impl AsRef<str>. impl Trait in parameter position is essentially just syntax sugar for a normal generic. Here's how load_script would desugar:
pub trait AutomationFactory<'a, S, C, E>
where
C: AutomationCommand<'a>,
S: AutomationScript<'a, C>,
E: Error,
{
fn load_script<R: AsRef<str>>(&self, path: R) -> Result<Box<S>, E>;
}
As you can see, the function is generic. To fix this, I'd recommend just changing it to accept and &str instead, and require conversion at the call site:
pub trait AutomationFactory<'a, S, C, E>
where
C: AutomationCommand<'a>,
S: AutomationScript<'a, C>,
E: Error,
{
fn load_script(&self, path: &str) -> Result<Box<S>, E>;
}
// somewhere else
let path = String::new();
factory.load_script(&path);

Related

Can I define a trait that has a generic function returning a trait object?

Is it possible to have a trait function that returns any trait object?
Or, can a generic parameter be a trait object type?
trait ToVec<T> {
fn to_vec(&self) -> Vec<&dyn T>;
}
trait TraitA {}
trait TraitB {}
// etc
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=92008c3e799f9c2f9189a0100daa4e1a
You can't have &dyn T, as T is not a trait. But &dyn ToVec<T> (or &dyn SomeOtherTrait) is fine.
In your playground example, Vec<T> is actually correct because you already have T as a trait object &dyn TraitA in the impls; but as the error message says, you need to fix lifetimes. E.g. this compiles
trait TraitA {}
trait ToVec<T> {
fn to_vec(&self) -> Vec<T>;
}
impl<'a, T: TraitA> ToVec<&'a dyn TraitA> for (&'a str, &'a T) {
fn to_vec(&self) -> Vec<&'a dyn TraitA> {
vec![
self.1 as &dyn TraitA
]
}
}
but may or may not be what you intended.

How do I pass a type bounded by a trait to Serde's deserialize_with?

A type satisfying MyTrait is supposed to be passed to deserialize_data specified by deserialize_with. Here is my sample code:
use serde::{Deserialize, Deserializer}; // 1.0.117
use serde_json; // 1.0.59
type Item = Result<String, Box<dyn std::error::Error + Send + Sync>>;
pub trait MyTrait {
fn method(ind: &str) -> Item;
}
#[derive(Deserialize)]
pub struct S<T>
where
T: MyTrait + ?Sized, // intend to pass a type T satisfying `MyTrait` to function `deserialize_data`,
{
#[serde(deserialize_with = "deserialize_data")]
//#[serde(bound( deserialize = "T: MyTrait, for<'de2> T: Deserialize<'de2>" ))]
pub data: String,
}
fn deserialize_data<'de, D, T>(d: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let ind = <&str>::deserialize(d).unwrap();
match T::method(ind) {
Ok(data) => Ok(data),
Err(e) => Err(serde::de::Error::custom(format_args!("invalid type."))),
}
}
struct A;
impl MyTrait for A {
fn method(_ind: &str) -> Item {
// to make it simple, return constant
Ok("method".to_string())
}
}
fn main() {
let s = r#"{"data": "string"}"#;
let ob: S<A> = serde_json::from_str(s).unwrap();
}
The compiler complains:
error[E0392]: parameter `T` is never used
--> src/main.rs:10:14
|
10 | pub struct S<T>
| ^ unused parameter
|
= help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`
I do use T, and PhantomData doesn't help much.
One of the obvious way may be using struct A and its implemented method as crate or something then importing them. This, unfortunately, doesn't apply to my case, so I seek to pass a struct type to deserialize_data and achieve that.
To get the code to compile, you need to:
use T in struct S<T>, for example with PhantomData.
explicitly pass T to deserialize_data using the turbofish operator ::<>.
add the appropriate trait bounds to the T generic type in deserialize_data(), such as MyTrait.
For example (playground):
#[derive(Deserialize)]
pub struct S<T>
where
T: MyTrait + ?Sized,
{
#[serde(deserialize_with = "deserialize_data::<_, T>")]
pub data: String,
marker: std::marker::PhantomData<T>,
}
fn deserialize_data<'de, D, T>(d: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
T: MyTrait + ?Sized,
{
// ...
}

Lifetime in impl does not match method in trait

Code
Playground (Stable Rust 1.45.0, 2018 edition) No external crates needed for example.
type Error = Box<dyn std::error::Error>;
type Result<R=()> = std::result::Result<R, Error>;
struct Arena;
pub trait AsSized<'a> {
type AsSized: Sized + 'a;
}
impl<'a, T: Sized + 'a> AsSized<'a> for T {
type AsSized = Self;
}
impl<'a, T: AsSized<'a>> AsSized<'a> for [T] {
type AsSized = &'a [T::AsSized];
}
pub trait Format<T>: Send + Sync
where T: ?Sized
{
fn get_bytes<'a>(&self, value: &'a T, arena: &'a Arena) -> Result<&'a [u8]>;
fn get_value<'a>(&self, bytes: &'a [u8], arena: &'a Arena) -> Result<T::AsSized>
where T: AsSized<'a>;
}
struct RawBytes;
impl Format<[u8]> for RawBytes
where [u8]: for<'a> AsSized<'a, AsSized=&'a [u8]>
{
fn get_bytes<'a>(&self, value: &'a [u8], _arena: &'a Arena) -> Result<&'a [u8]> {
Ok(value)
}
fn get_value<'a>(&self, bytes: &'a [u8], arena: &'a Arena) -> Result<<[u8] as AsSized<'a>>::AsSized> {
Ok(bytes)
}
}
Issue
I'm getting a compiler error on the impl of get_value for RawBytes:
error[E0195]: lifetime parameters or bounds on method `get_value` do not match the trait declaration
I don't understand what the problem is. It seems like the lifetime specification is the same between both definitions. How do I write the impl of get_value for RawBytes so that it works?
I would think that since u8: Sized then <u8 as AsSized<'a>>::AsSized = u8 and then <[u8] as AsSized<'a>>::AsSized = &'a [u8] but it seems like that isn't the case?
Background
Format takes an arena based allocator and converts a slice of bytes to and from a complex type. I plan to write an adapter for various Serde formats. RawBytes is a trivial implementaiton of Format for a slice of bytes that just returns the original slice.
Both methods of Format are allowed to return a value that borrows from the input value. The format itself may be shared between threads, so the lifetime of self is unrelated to the returned value.
The purpose of AsSized is to allow dynamically sized types like str and [u8] to be used directly, but since dynamically sized types can't be returned directly, AsSized provides a sized equivalent for any type; dynamically sized types return a reference to the DST instead (borrowed from the arena). Sized types like u8 that can be returned directly have an AsSized type of self
Also tried
fn get_value<'a>(&self, bytes: &'a [u8], arena: &'a Arena) -> Result<'a [u8]>
I tried simplifying the impl's get_value to just name the slice directly; rust then says that the impl for get_value is missing entirely.
fn get_value<'a>(&self, bytes: &'a [u8], arena: &'a Arena) -> Result<&'a [<u8 as AsSized<'a>>::AsSized]>
This give the same "lifetime parameters... do not match" error.
Partial answer (i got stuck too :)
I kept deleting code until I reached this minimal example that reproduces the error:
pub trait AsSized<'a> {
type AsSized: Sized + 'a;
}
pub trait Format<T>
where
T: ?Sized,
{
fn get_value<'a>(&self, bytes: &'a [u8])
where
T: AsSized<'a>;
}
impl Format<[u8]> for () {
fn get_value<'a>(&self, bytes: &'a [u8]) {
todo!()
}
}
It now becomes apparent that, in the trait definition, there is a further constraint on the 'a lifetime: we require that T: AsSized<'a>, and this is what's causing the error. You don't have such a clause on the impl block, hence the compiler doesn't accept the lifetimes as equivalent.
(I'm not sure if they're actually incompatible or if this is a limitation of the compiler)
So I decided to add where [u8]: AsSized<'a> to the impl function as well. To get it to compile, I also:
Made Arena public (type leak issue)
Removed where [u8]: for<'a> AsSized<'a, AsSized=&'a [u8]> from the impl (this where clause makes no sense to me – it seems it has no point since it doesn't involve a generic? i could be wrong. anyway it was causing a weird error)
Replaced the function body with todo!()
So this compiles:
fn main() {}
type Error = Box<dyn std::error::Error>;
type Result<R = ()> = std::result::Result<R, Error>;
pub struct Arena;
pub trait AsSized<'a> {
type AsSized: Sized + 'a;
}
impl<'a, T: Sized + 'a> AsSized<'a> for T {
type AsSized = Self;
}
impl<'a, T: AsSized<'a>> AsSized<'a> for [T] {
type AsSized = &'a [T::AsSized];
}
pub trait Format<T>: Send + Sync
where
T: ?Sized,
{
fn get_bytes<'a>(&self, value: &'a T, arena: &'a Arena) -> Result<&'a [u8]>;
fn get_value<'a>(&self, bytes: &'a [u8], arena: &'a Arena) -> Result<T::AsSized>
where
T: AsSized<'a>;
}
struct RawBytes;
impl Format<[u8]> for RawBytes {
fn get_bytes<'a>(&self, value: &'a [u8], _arena: &'a Arena) -> Result<&'a [u8]> {
Ok(value)
}
fn get_value<'a>(
&self,
bytes: &'a [u8],
arena: &'a Arena,
) -> Result<<[u8] as AsSized<'a>>::AsSized>
where
[u8]: AsSized<'a>,
{
todo!()
}
}
Unfortunately, as soon as I replace the todo!() back with your Ok(bytes), it breaks again. The compiler apparently doesn't know that <[u8] as AsSized<'a>>::AsSized is the same as &'a [u8]. Oh well.
error[E0308]: mismatched types
--> src/main.rs:42:12
|
42 | Ok(bytes)
| -- ^^^^^ expected associated type, found `&[u8]`
| |
| arguments to this enum variant are incorrect
|
= note: expected associated type `<[u8] as AsSized<'a>>::AsSized`
found reference `&'a [u8]`
= help: consider constraining the associated type `<[u8] as AsSized<'a>>::AsSized` to `&'a [u8]` or calling a method that returns `<[u8] as AsSized<'a>>::AsSized`
= note: for more information, visit https://doc.rust-lang.org/book/ch19-03-advanced-traits.html

Why is a cast needed when a closure is passed as a trait object argument?

I'm trying to create a trait that should abstract over functions/closures with a different number of arguments. Something like this:
trait MyTrait {}
impl MyTrait for Box<Fn() -> &'static str> {}
impl MyTrait for Box<Fn(u8) -> u8> {}
Initially I planned to use it like this:
fn myf<F: MyTrait>(_fun: F) {}
fn main() {
myf(Box::new(|i: u8| i + 2))
}
But this code fails with error:
error[E0277]: the trait bound `std::boxed::Box<[closure#src/main.rs:11:18: 11:31]>: MyTrait` is not satisfied
When I cast the box like this, everything compiles correctly:
myf(Box::new(|i: u8| i + 2) as Box<Fn(_) -> _>)
Playground
Why can't the Rust compiler infer this trait without a cast? Is my approach (using cast) correct, or is there a simpler way? I prefer to enable trivial_casts warning for my projects and this syntax triggers it.
This is a thing that one tends to forget: each closure is a different struct that implements Fn: Fn is a trait, not a struct, and trait implementations are not transitive.
Here is a little example that shows this point:
trait Base {}
trait Derived {}
struct Foo {}
impl Base for Derived {}
impl Derived for Foo {}
fn myf<T>(_t: Box<T>)
where
T: Base + ?Sized,
{
}
fn main() {
let foo = Box::new(Foo {});
//myf(foo) // does not compile
myf(foo as Box<Derived>)
}
The thing you really wanted to do is:
trait MyTrait {}
impl<T> MyTrait for T
where
T: Fn() -> &'static str,
{
}
impl<T> MyTrait for T
where
T: Fn(u8) -> u8,
{
}
fn myf<F>(_fun: Box<F>)
where
F: MyTrait,
{
}
fn main() {
myf(Box::new(|i: u8| i + 2))
}
But this cannot compile because there are two conflicting implementations.

How to implement specialized versions of a generic function?

I'd like to have multiple versions of a function optimized for type of its arguments, and Rust call appropriate one depending on context.
In my case all arguments have the same type, and all are equivalent, so it'd rather avoid having a self argument.
I've tried this code:
trait Foo<T> {
fn foo(a: T, b: T, c: T);
}
impl Foo<i32> {
fn foo(a: i32, b: i32, c: i32) {}
}
impl Foo<i16> {
fn foo(a: i16, b: i16, c: i16) {}
}
fn main() {
Foo::foo(1i32,2,3);
Foo::foo(1i16,2,3);
}
but Rust requires type annotations:
error: type annotations required: cannot resolve _ : Foo<i32> [E0283]
Can I avoid providing type annotations at the call site? If I have to, how to do it?
Remember that you always implement a trait for something. Therefore, trait implementation must always contain for clause:
impl SomeTrait for Something
If there is no for, then it is not a trait implementation. In your case impl Foo<i32> is not an implementation of Foo for i32 or whatever you think it is; it is an inherent method declaration clause on the bare trait object type Foo<i32>.
What you actually want is possible to do using Self type parameter:
trait Foo {
fn foo(a: Self, b: Self, c: Self);
}
impl Foo for i32 {
fn foo(a: i32, b: i32, c: i32) {}
}
impl Foo for i16 {
fn foo(a: i16, b: i16, c: i16) {}
}
fn main() {
Foo::foo(1i32,2,3);
Foo::foo(1i16,2,3);
}
This code works.
Note that now Foo is implemented for a certain type. The type a trait is implemented for is available via the implicit Self type parameter, and you can see how it is used in foo() declaration.

Resources