0

I have a struct

struct Triangle {
    normal: Normal,
    vertices: [Vertex; 3],
}

that I'd like to deserialize from a Read type. I thought implementing TryFrom would be idiomatic since I'm turning a stream of bytes into a Triangle but it may fail.

impl TryFrom<&mut dyn std::io::Read> for Triangle {
  type Error = std::io::Error;
  fn try_from(reader: &mut dyn std::io::Read) -> Result<Self, Self::Error> {
    Ok(Self {
        normal: Normal::try_from(reader)?,
        vertices: [Vertex::try_from(reader)?, Vertex::try_from(reader)?, Vertex::try_from(reader)?]
    })
  }
}

I believe I could shove all of the usage of the reader : &mut Read into the Triangle implementation but

  1. I like having the parsing for an object in its own impl and
  2. I'll just run into the same problem when I try to parse multiple Triangles to build a mesh

I'm starting to get the feeling that implementing TryFrom is the wrong approach here but before I gave up I thought I'd ask if there was a clean way to let Normal::try_From borrow the reader then let Vertex::try_from borrow the reader while following ownership rules.

Here is a full listing of the module: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d79881fee912acf0fa4496f90a34df86

(I know that STL has a u16 attribute after each triangle, just haven't gotten around to it yet)

Dave Fol
  • 515
  • 3
  • 11

2 Answers2

1

You can fully qualify the call, although it is not obvious to me why it doesn't compile in the first place:

impl TryFrom<&mut dyn std::io::Read> for Triangle {
    type Error = std::io::Error;
    fn try_from(reader: &mut dyn std::io::Read) -> Result<Self, Self::Error> {
        Ok(Self {
            normal: <Normal as TryFrom<&mut dyn std::io::Read>>::try_from(reader)?,
            vertices: [
                <Vertex as TryFrom<&mut dyn std::io::Read>>::try_from(reader)?,
                <Vertex as TryFrom<&mut dyn std::io::Read>>::try_from(reader)?,
                Vertex::try_from(reader)?,
            ],
        })
    }
}

But I'd just implement a custom method.

Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
1

I was able to make it work with a little coercion:

impl TryFrom<&mut dyn std::io::Read> for Triangle {
    type Error = std::io::Error;
    fn try_from(reader: &mut dyn std::io::Read) -> Result<Self, Self::Error> {
        Ok(Self {
            normal: Normal::try_from(reader as &mut _)?,
            vertices: [
                Vertex::try_from(reader as &mut _)?,
                Vertex::try_from(reader as &mut _)?,
                Vertex::try_from(reader as &mut _)?,
            ],
        })
    }
}

I understand the "use of moved value: reader" error can happen where the compiler understands the context generically and doesn't consider reborrowing instead of moving like in other contexts. See: Do mutable references have move semantics?.

However, I'm also confused that using &mut *reader also doesn't work and yields "cannot borrow *reader as mutable more than once at a time" errors. The compiler doesn't shorten the reference's lifetime and seems to think the reborrow needs to last as long as the original.

Fortunately, the syntax above seems to do the trick.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • Thanks for the insight, I find it odd that when I reimplemented the exact same logic and types in a custom method no coercion was needed. I've got a long way to go with Rust generics. – Dave Fol Jan 11 '23 at 03:32
  • @DaveFol See the linked question. Yes, I was also surprised to see explicit reborrowing didn't work. It's nice `as` does. – Chayim Friedman Jan 11 '23 at 06:47