A dyn type with a nested type

I want to have such a trait for file handler and another one for the actual reader (some crates require an extra layer like here, hence this is the optimal way).

This part worked well:

trait FormatDriver<'a> {
	fn can_open(path: &str) -> bool;

	type FeatureReader: FeatureReader<'a>;
	fn get_feature_reader(&'a mut self) -> Result<Self::FeatureReader, Box<dyn Error>>;
}

trait FeatureReader<'a> {} // hidden the unnecessary

Now I add a wrapper that should contain in itself an instance of any of FormatDriver implementors.

struct FormatsWrapper<'a> {
	reader: Box<dyn FormatDriver<'a, FeatureReader=dyn FeatureReader<'a>>>
}

And it will not compile:

error[E0228]: the lifetime bound for this object type cannot be deduced from context; please supply an explicit bound
   --> src/main.rs:174:44
    |
174 |     x: Box<dyn FormatDriver<'a, FeatureReader=dyn FeatureReader<'a>>>
    |                                               ^^^^^^^^^^^^^^^^^^^^^

The 2 alternatives to this won't work.rs

  1. As I make get_feature_reader return Result<dyn FeatureReader, ..> (or Result<Box<dyn FeatureReader>,..), compiler shows more and more requirements, including to make can_open an instance method (with Self in it).

  2. I tried making get_feature_reader generic. Somehow the compiler let it happen, but that's nonsense -- caller shouldn't be able to ask a different kind of FeatureReader, and problem is how you instantiate a particular one in the function code? But also the compiler again demanded can_open an instance method. (Maybe it would have shown more errors if I got rid of this, IDK.)

I thought that this might be a good candidate for a macro which could assemble an enum, but this greatly reduces compatibility, and requires to enumerate all FormatDrivers.

What's a meaningful solution to this?

The method compile error has nothing to do with your associated type, it errors even with just this code

trait FormatDriver<'a> {
	fn can_open(path: &str) -> bool;

}

fn a() -> Box<dyn FormatDriver<'static>> { todo!() }

Trait objects can't have methods that don't take self, there wouldn't be any way to call them via a trait object anyway.

1 Like

hmm. my cargo c allows this.

Are you sure you're checking the right crate? It fails on the playground

Oh, I see what you mean. Reproduced this error.

What's the reasonable way to have such a method like can_open?

Well what are you trying to do with it?

If you're not calling it on the trait object then you can add a where Self: Sized bound to the method and it will be left off of the trait object. If you do need to call it on the trait objects you can just add a &self parameter and make it into a normal instance method.

1 Like

I wanted to be able to ask format driver if it can open a file. Instead I could put this into open() and return something like Result<Option<GpkgDriver<bla>>, SomeKindOfError>, but it's pain to unpack.

where Self: Sized did the trick. (code in playground does exactly what I wanted.) But what do you mean by "not calling it on a trait object"? Like Box<dyn FormatDriver<'a>>::can_open(&my_path)?

And the main problem remains:

let drv = Box::new(x) as Box<dyn for<'a> FormatDriver<'a, FeatureReader=dyn FeatureReader<'a>>>;

The compiler says it expected a trait object (dyn FeatureReader<'a>), but got a concrete type inside (GpkgLayer<'_>).

Any suggestions are welcome. (As well as telling me I'm going the wrong path.)

The bigger goal, why I made 2 traits and have 2 connected types is that I try to store structs from other crates in a uniform way, and they generate 2-3 layers of structs, sometimes with references instaed of ownership, and I can't store everything in one self-referential structure.

This can be done with couple of structs, but I want to make it extensible, so that I or someone else writes another pair of types for their other format, and my code should handle them. Something like Serde.

If you have a Box<dyn Trait>[1] you cannot call functions on the trait that have where Self: Sized bounds. That's was what I meant.


Unlike other languages with polymorphism, dyn Trait is a concrete type. Types implementing the trait can be coerced into dyn Trait in some places, but the compiler doesn't consider dyn Trait and TypeImplementingTrait to be the same type.

Thus FeatureReader=dyn FeatureReader<'a> causes problems since your impl uses a type that implements the trait and not dyn FeatureReader<'a> directly.

Incidentally using the bare dyn FeatureReader<'a> is probably wrong anyway since you appear to be returning that from a trait method without any indirection which doesn't work for unsized types.

You can create a wrapper type that implements the trait for the type it wraps by boxing the return value and making it a trait object to fix both of those issues

Playground

use std::{convert::Infallible, fmt::Display};

trait Driver {
    type Reader: Display;
    fn get_reader(&self) -> Result<Self::Reader, Infallible>;
}

struct Test;

impl Driver for Test {
    type Reader = String;

    fn get_reader(&self) -> Result<Self::Reader, Infallible> {
        Ok("Hello".into())
    }
}

struct BoxDriver<T>(T);

impl<T: Driver> Driver for BoxDriver<T>
where
    T::Reader: 'static,
{
    type Reader = Box<dyn Display>;

    fn get_reader(&self) -> Result<Self::Reader, Infallible> {
        self.0.get_reader().map(|x| Box::new(x) as Box<dyn Display>)
    }
}

fn main() {
    let test: Box<dyn Driver<Reader = Box<dyn Display>>> = Box::new(BoxDriver(Test));

    println!("{}", test.get_reader().unwrap())
}

  1. or other trait object type ↩ī¸Ž

2 Likes

Now it makes sense. Thanks a lot!

Somehow this fails when the nested struct gets <'a> in it. I inserted my nested struct into your code, and it fails. Playground.

Error:

45 |     type Reader = Box<dyn FeatureReader>;
   |                   ^^^^^^^^^^^^^^^^^^^^^^ the trait `FeatureReader` is not implemented for `Box<(dyn FeatureReader + 'static)>`
   |
   = help: the trait `FeatureReader` is implemented for `GpkgLayer<'a>`
note: required by a bound in `Driver::Reader`
  --> src/main.rs:22:18
   |
22 |     type Reader: FeatureReader;
   |                  ^^^^^^^^^^^^^ required by this bound in `Driver::Reader`

Code:

use std::{marker::PhantomData, error::Error, convert::Infallible};

trait FeatureReader {
	// forward the reader 1 record
	fn forward(&mut self) -> Result<bool, Box<dyn Error>>; // Ok(false) -> end loop
	// accessors sort of like in Serde
}

#[derive(Debug)]
struct GpkgLayer<'a> {
	fii: &'a mut PhantomData<bool>,
	feature: Option<&'a PhantomData<bool>>,
}

impl<'a> FeatureReader for GpkgLayer<'a> {
	fn forward(&mut self) -> Result<bool, Box<dyn Error>> {
		todo!()
	}
}

trait Driver {
    type Reader: FeatureReader;
    fn get_reader(&self) -> Result<Self::Reader, Infallible>;
}

#[derive(Debug)]
struct Test<'a>(PhantomData<&'a bool>);

impl<'a> Driver for Test<'a> {
    type Reader = GpkgLayer<'a>;

    fn get_reader(&self) -> Result<Self::Reader, Infallible> {
        Ok(GpkgLayer { fii: &mut PhantomData, feature: None })
    }
}


#[derive(Debug)]
struct BoxDriver<T>(T);

impl<T: Driver> Driver for BoxDriver<T>
where
    T::Reader: 'static,
{
    type Reader = Box<dyn FeatureReader>;

    fn get_reader(&self) -> Result<Self::Reader, Infallible> {
        self.0.get_reader().map(|x| Box::new(x) as Box<dyn FeatureReader>)
    }
}

fn main() {
    let test: Box<dyn Driver<Reader = Box<dyn FeatureReader>>> = Box::new(BoxDriver(Test(PhantomData)));
}

Box<dyn Trait> doesn't automatically implement trait, but you can add:

impl FeatureReader for Box<dyn FeatureReader + '_> {
    fn forward(&mut self) -> Result<bool, Box<dyn Error>> {
        (**self).forward()
    }
}

The remaining error is unrelated and I think just a result of the mock-up?

(N.b. I didn't read the backlog of this thread, only your latest playground.)

3 Likes

I knew it's a separate type but it didn't occur to me to implement a trait for Box<dyn ...>. Thanks! I fixed the PhantomData error and made it compile.