Casting from &mut Trait + ?Sized into &mut dyn Trait

I want to implement serialization for a network protocol with many different types, which might also be nested. As an example, let's assume that the only types are Vector3 and Tuple. I have this written out in the playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8b962ec137d6e9ef6947b110c0bbc949

I want implementation of new types to be as easy as possible, so instead of an enum I've chosen to use a trait (Value) which all values should implement. Because the write() method takes a generic Write type argument, it's not method safe for dynamic dispatch, so I've also created a DynValue trait, which takes &mut dyn Write. If possible, I would like to be use both the dynamic dispatch version and generic version. However, I can't manage to call DynValue::write. The compiler tells me:

error[E0277]: the size for values of type `W` cannot be known at compilation time
  --> src/main.rs:41:21
   |
38 |     fn write<W: Write + ?Sized>(&self, stream: &mut W) -> io::Result<()> {
   |              -- help: consider further restricting this bound: `W: std::marker::Sized +`
...
41 |             v.write(stream)?;
   |                     ^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `W`

And if I remove the ?Sized annotations from everything, the DynValue impl fails to call the concrete Value::write, because &mut dyn Write might not be Sized:

error[E0277]: the size for values of type `dyn std::io::Write` cannot be known at compilation time
  --> src/main.rs:14:24
   |
5  |     fn write<W: Write>(&self, stream: &mut W) -> io::Result<()>;
   |     ------------------------------------------------------------ required by `Value::write`
...
14 |         V::write(self, stream)
   |                        ^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `dyn std::io::Write`

Is there a better way to approach what I'm attempting?

Indirection is the best way to make something Sized. And given that the Write trait is "&mut-transitive" (impl<W : ?Sized + Write> Write for &'_ mut W), you can coerce the initial stream: &'a mut (impl ?Sized + Write) reference itself into a dyn Write, behind the added layer of &mut indirection:

&mut stream // : &'_ mut (&'a mut (impl ?Sized + Write)) = &'_ mut (impl Write + 'a))
    as &'_ mut (dyn Write + 'a) // explicit coercion, could be implicit

Thus, the following change fixes your Playground:

-    fn write<W: Write + ?Sized>(&self, stream: &mut W) -> io::Result<()> {
+    fn write<W: Write + ?Sized>(&self, mut stream: &mut W) -> io::Result<()> {
         for v in &self.0 {
             stream.write_all(&[v.type_id()])?;
-            v.write(stream)?;
+            v.write(&mut stream)?;
1 Like

The solution was simpler than I though. Massive thanks for your help!

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.