Cast from Box<dyn Any> to concrete impl

Is there a way to downcast from Box<dyn Any> initiated with something that impl Write to &dyn Write ? I don't know the concrete type, I just have a field of Box<dyn Any> smth like Rust Playground

The idea is I want to use it differently based on type, if it's just Write in a function I'll just call write but if it's Seek also I want to call seek too.

No.

  • You can't get a &dyn Write without the compiler generating a dyn Write vtable from the concrete impl Write for TheConcreteType, and there’s no way for the compiler to know that it needs to do that when the Box<dyn Any> is constructed, nor is there any place in dyn Any to store a dyn Write vtable.
  • What your code is trying to do is, in part, asking the question “does this implement Write”, which is not possible to do in any way whatsoever in generic code. (Read about “specialization” in Rust to learn why not.)

What you can do is define your own trait, which can incorporate Any if you wish:

trait MyAny: Any {
    fn as_any(&self) -> &dyn Any;
    fn as_write(&mut self) -> Option<&mut dyn Write>;
    fn as_write_seek(&mut self) -> Option<&mut dyn Write + Seek>;
    // ...
}

then implement it for types you want, and use Box<dyn MyAny>.

2 Likes

Problem is I don't know what types will I receive, all I know is it can be Write or Write + Seek.
Can I do somehow blanket Implementation for MyAny for all Write and Write + Seek and then call as_* on that?

Tried like this Rust Playground but getting error[E0119]: conflicting implementations of trait MyAny`

What I'm trying to model is like here rencfs/src/crypto/read.rs at RingCryptoWriterSeek · radumarias/rencfs · GitHub

In this case I have a wrapper struct that wraps a Read or a Read + Seek. In the latter case when I'm using the wrapper I can see the seek method too.

Similarly I want to model a Writer wrapper over Write or Write + Seek and in the latter case I want to expose seek too but the actual write method will be different as in the case it's Seek too I first need to read and decrypt the block and only after that write to it, like here rencfs/src/crypto/write.rs at 51fe35679876aa7aa3b20fe53018cc45262d17d3 · radumarias/rencfs · GitHub
So I somehow need to know if I'm Seek also so then I can call read and seek methods as needed.

No, you can’t, because there will be a lot of types where both implementations are applicable and the compiler has no way to choose between them. Ultimately, whoever is giving you these types will need to explicitly choose which behavior they want one way or another.

The best you can do right now is to aim for an API that makes that explicit choice as painless as possible.

1 Like

Another issue is I need to return then the inner object like here rencfs/src/crypto/write.rs at 51fe35679876aa7aa3b20fe53018cc45262d17d3 · radumarias/rencfs · GitHub
For now I don't know how to return the actual type I received, like I could have received a File but I can only return Write or Write + Read + Seek, is there a way to do this?

Was thinking to have generic over <T> but then I can't see how to use as_write and as_write_seek methods anymore.

If this becomes too complex, you may want to consider an enum instead of a dyn type.

2 Likes

To me, that sounds like a completely different operation that shouldn’t share the same name. What should happen if you have a write-only seekable object? Or a read-write object that currently contains junk that should be blindly overwritten instead of encrypted data?

tried that, you can see in the above link to GH

In case it's Seek it needs to be Read also so I can decrypt.

Or a read-write object that currently contains junk that should be blindly overwritten instead of encrypted data?

It should contain valid decryptable data, if that's not the case then write will fail

No matter what, when you coerce the value into dyn, you must know statically whether that particular type implements the combination(s) of traits you care about. You cannot make a decision based on "whether this type implements Seek"; you can only require it to implement Seek, or follow a different code path with a different coercion that does not.

In other words, another representation you could use is:

enum BoxedIo {
    Write(Box<dyn Write>),
    All(Box<dyn Read + Seek + Write>),
    ...
}

and when you convert to this type, you must already know which enum variant you want to pick — not pick it based on whether Read and Seek are implemented.

But, like @2e71828 said, it seems a lot like you're not just trying to support different things depending on which traits are implemented, but to perform a different operation depending on which traits are implemented. That's not just impossible — it's also bad design. Don't use whether a trait is implemented to decide what a value means.

6 Likes

Ended up doing it like this:

pub trait WriteSeekRead: Write + Seek + Read {}

impl<T: Write + Seek + Read> WriteSeekRead for T {}

pub trait CryptoInnerWriter: Write + Any {
    fn into_any(self) -> Box<dyn Any>;
    fn as_write(&mut self) -> Option<&mut dyn Write>;
    fn as_write_seek_read(&mut self) -> Option<&mut dyn WriteSeekRead>;
}

impl<T: Write + Seek + Read + 'static> CryptoInnerWriter for T {
    fn into_any(self) -> Box<dyn Any> {
        Box::new(self)
    }
    fn as_write(&mut self) -> Option<&mut dyn Write> {
        Some(self)
    }

    fn as_write_seek_read(&mut self) -> Option<&mut dyn WriteSeekRead> {
        Some(self)
    }
}

And using it like self.writer.as_write_seek_read.

And users of the lib would need to impl CryptoInnerWriter if they have a custom writer.

For the case of finish to return the inner received one I do

pub trait CryptoWrite<W: CryptoInnerWriter + Send + Sync>: Write + Send + Sync {
    fn finish(&mut self) -> io::Result<W>;
}

impl<W: CryptoInnerWriter + Send + Sync> CryptoWrite<W> for RingCryptoWrite<W> {
    fn finish(&mut self) -> io::Result<W> {
        if self.buf.is_dirty() {
            // encrypt and write last block, use as many bytes as we have
            self.encrypt_and_write()?;
        }
        let boxed = self
            .writer
            .take()
            .ok_or(io::Error::new(io::ErrorKind::NotConnected, "no writer"))?
            .into_any()
            .downcast::<W>()
            .map_err(|_| io::Error::new(io::ErrorKind::Other, "downcast failed"))?;
        Ok(Box::into_inner(boxed))
    }
}

Not sure if there is a cleaner solution but this at least works good for my use case.

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.