capnproto-rust icon indicating copy to clipboard operation
capnproto-rust copied to clipboard

How to return a reader?

Open vbakc opened this issue 3 years ago • 6 comments

I want to store a Reader inside of a struct but it only uses a reference a does not implement Clone.

use super::capnp_error::CapnpError;
use capnp::{message::ReaderOptions, serialize_packed};
use std::io::Cursor;

pub struct ReaderWrapper<'a> {
    reader: &'a Reader<'a>,
}

impl<'a> TryFrom<Vec<u8>> for ReaderWrapper<'a> {
    type Error = CapnpError;

    fn try_from(buffer: Vec<u8>) -> Result<ReaderWrapper<'a>, CapnpError> {
        let reader =
            &serialize_packed::read_message(&mut Cursor::new(buffer), ReaderOptions::new())?;
        let typed_reader = reader.get_root::<Reader>()?;

        Ok(ReaderWrapper {
            reader: &typed_reader,
        })
    }
}

vbakc avatar Feb 18 '22 14:02 vbakc

cannot return value referencing temporary value returns a value referencing data owned by the current function

vbakc avatar Feb 18 '22 14:02 vbakc

Okay, so my understanding is that you're trying to keep the original Vec<u8> around, and also create a ReaderWrapper which reads directly from the Vec<u8> without modifying it or storing an intermediate representation.

If that's correct, there are a few issues with your initial attempt:

  • You can't impl TryFrom<Vec<u8>>, because that will consume the Vec<u8> that you're trying to reference. You can impl TryFrom<&'a [u8]>, which has two advantages:
    • doesn't consume the buffer
    • properly relates 'a to the buffer's lifetime.
  • You're using serialize_packed::read_message, which can't work. The packed representation has to be un-packed before it can be read, so it's only possible to read directly from the Vec<u8> without copying if it's in the standard binary representation to begin with. In this case, you can use capnp::serialize::read_message_from_flat_slice to read directly from a slice.

Here's my first attempt at something like what you're asking (full repo), based on the Point example in the capn-proto-rust repo readme:

use anyhow::Result;
use capnp::{message::ReaderOptions, serialize::SliceSegments};
use points_capnp::point;

pub struct ReaderWrapper<'a> {
    reader: capnp::message::Reader<SliceSegments<'a>>,
}

impl<'a> TryFrom<&'a [u8]> for ReaderWrapper<'a> {
    type Error = anyhow::Error;

    fn try_from(mut buffer: &'a [u8]) -> Result<ReaderWrapper<'a>> {
        let reader =
            capnp::serialize::read_message_from_flat_slice(&mut buffer, ReaderOptions::new())?;

        Ok(ReaderWrapper { reader })
    }
}

impl<'a> ReaderWrapper<'a> {
    fn print_point(&self) -> Result<()> {
        let point_reader = self.reader.get_root::<point::Reader>()?;
        println!("x = {:.2}", point_reader.get_x());
        println!("y = {:.2}", point_reader.get_y());

        Ok(())
    }
}

Note that your ReaderWrapper stores the "typed" reader (would be points_capnp::point::Reader in my example), whereas mine just stores the "untyped" capnp::message::Reader, which is suboptimal because as you can see in the print_point function, reading from the reader is unnecessarily fallible (the failure should happen once when the ReaderWrapper is created, not every time it's read from).

I thought this would be a good starting point - I haven't tried storing the "typed" version yet, although I think it might be possible with something like the owning_ref crate, although I'm not 100% sure.

OliverEvans96 avatar Mar 19 '22 21:03 OliverEvans96