Function which takes an Iterator<Item = Box<dyn Any>>

Thanks very much for this example. It's the first time I see / have to use Box<Self>.

It's exactly what I wanted to achieve in my original example.

But after trying some things based on your code, I'm not sure whether (or how) this can help me with my real-world problem.

After some struggles with crates.io and figuring out a publication process, I was finally able to get things published! Here's the full real-life example:

mmtkvdb/src/lib.rs

(Documentation doesn't build yet, I'll ask another question in the forum read into this thread on how to fix that.)

My goal is to make this function work with databases that use different types (K, V) and constraints (C):

    /// Open databases in environment
    ///
    /// SAFETY: If a database exists already, it must have been created with
    /// compatible options.
    unsafe fn open_dbs<'a, K, V, C>(
        &self,
        metas: impl IntoIterator<
            IntoIter = impl Iterator<Item = &'a DbOptions<K, V, C, Option<CString>>> + ExactSizeIterator,
        >,
    ) -> Result<Vec<Db<K, V, C>>, io::Error>
    where
        K: ?Sized + Storable,
        V: ?Sized + Storable,
        C: Constraint;

I tried to add a trait akin to @quinedot's proposal but ran into some problems when doing so (I think it had to do with having to specify associated types in the functions signature).

If I find time, I'll try to address the issue again and provide more information on where I stumbled.

Finally the package built properly :hot_face:, so there is documentation on my project on docs.rs available :smiley:.

Consider the method mmtkvdb::Env::open_dbs (source code available behind the link) which opens several databases:

unsafe fn open_dbs<'a, K, V, C>(
    &self,
    metas: impl IntoIterator<
        IntoIter = impl Iterator<Item = &'a DbOptions<K, V, C, Option<CString>>> + ExactSizeIterator
    >
) -> Result<Vec<Db<K, V, C>>, Error> where
    K: ?Sized + Storable,
    V: ?Sized + Storable,
    C: Constraint;

Each database may use different types for their keys and values. Thus, the DbOptions builder uses the typestate pattern and has type arguments K and V for the key and value type.

The open_dbs method opens several databases at once (it will have to acquire a mutex for it and do a few other things, so when multiple databases are opened, it may make sense to only do that once, which is why I allow passing multiple DbOptions through an iterator). However, while the signature of open_dbs is generic over different K and V types, it does not support opening databases with different keys (or values) in one call, because K and V are particular types for each invocation of open_dbs.

To overcome the limitation of only being able to open multiple databases with the same key (and value) types, I wanted to use dyn objects. The caller of open_dbs could then downcast each element in the result according to the (known) types K and V.

In practice, the code quickly gets very complex though. I might just leave the interface as is and accept the overhead of opening differently-typed databases in multiple calls.

I wonder, however, if similar problems occurred in other contexts where there are lists of structs with type arguments passed to a function that returns a list of different structs with the same type arguments (in my example above: K, V, C appear both in the arguments as well as in the result of the function).

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.