sharksforarms / deku

Declarative binary reading and writing: bit-level, symmetric, serialization/deserialization
Apache License 2.0
1.11k stars 54 forks source link

Support for serialization of composed types #277

Closed mmmfarrell closed 1 year ago

mmmfarrell commented 2 years ago

I am having trouble getting this example working when I want to be able to serialize / deserialize both the child and parent struct of a nested data type. Any help would be much appreciated!

This is related to #183 , but not identical b/c in that issue, the child type was never serialized independent of the parent type.

Example:

use deku::prelude::*;

#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "endian", ctx = "endian: deku::ctx::Endian")]
struct DekuBase {
    field_a: u8,
    field_b: u8,
}

#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "little")]
struct DekuTest {
    base: DekuBase,
    field_b: u8,
    field_c: u8,
}

fn main() {
    let data: Vec<u8> = vec![0xAB, 0xAC, 0xBE, 0xEF];
    let (_rest, mut val) = DekuTest::from_bytes((data.as_ref(), 0)).unwrap();
    assert_eq!(DekuTest {
        base: DekuBase { field_a: 0xAB, field_b: 0xAC },
        field_b: 0xBE,
        field_c: 0xEF,
    }, val);

    val.field_c = 0xFF;

    let data_out = val.to_bytes().unwrap();
    assert_eq!(vec![0xAB, 0xAC, 0xBE, 0xFF], data_out);

    let data_base: Vec<u8> = vec![0xBE, 0xEF];
    let (_rest, mut val) = DekuBase::from_bytes((data_base.as_ref(), 0)).unwrap();
    assert_eq!(DekuBase {
        field_a: 0xBE,
        field_b: 0xEF,
    }, val);

    println!("pass\n");
}

produces the following error:

error[E0599]: no function or associated item named `from_bytes` found for struct `DekuBase` in the current scope
  --> src/main.rs:33:38
   |
5  | struct DekuBase {
   | --------------- function or associated item `from_bytes` not found for this
...
33 |     let (_rest, mut val) = DekuBase::from_bytes((data_base.as_ref(), 0)).unwrap();
   |                                      ^^^^^^^^^^ function or associated item not found in `DekuBase`
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following traits define an item `from_bytes`, perhaps you need to implement one of them:
           candidate #1: `OsStrExt`
           candidate #2: `deku::DekuContainerRead`

For more information about this error, try `rustc --explain E0599`.

Or if I change the DekuBase class to have the following macros

#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "little")]
struct DekuBase {
    field_a: u8,
    field_b: u8,
}

I get the following errors:

error[E0277]: the trait bound `DekuBase: deku::DekuRead<'_, Endian>` is not satisfied
  --> src/main.rs:12:28
   |
12 | #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
   |                            ^^^^^^^^ the trait `deku::DekuRead<'_, Endian>` is not implemented for `DekuBase`
   |
   = help: the trait `deku::DekuRead<'_>` is implemented for `DekuBase`
   = note: this error originates in the derive macro `DekuRead` (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
   --> src/main.rs:12:38
    |
12  | #[derive(Debug, PartialEq, DekuRead, DekuWrite)]
    |                                      ^^^^^^^^^
    |                                      |
    |                                      expected `()`, found enum `Endian`
    |                                      arguments to this function are incorrect
    |
note: associated function defined here
   --> /home/vibhav/.cargo/registry/src/github.com-1ecc6299db9ec823/deku-0.12.6/src/lib.rs:321:8
    |
321 |     fn write(
    |        ^^^^^
    = note: this error originates in the derive macro `DekuWrite` (in Nightly builds, run with -Z macro-backtrace for more info)

Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
wcampbell0x2a commented 2 years ago

If you do cargo doc for only the two structs building, you can analyze what functions are generated from deku.

I modified the project structure for doc:

src/main.rs

use deku::prelude::*;

use crate::{DekuBase, DekuTest};

fn main() {
    let data: Vec<u8> = vec![0xAB, 0xAC, 0xBE, 0xEF];
    let (_rest, mut val) = DekuTest::from_bytes((data.as_ref(), 0)).unwrap();
    assert_eq!(DekuTest {
        base: DekuBase { field_a: 0xAB, field_b: 0xAC },
        field_b: 0xBE,
        field_c: 0xEF,
    }, val);

    val.field_c = 0xFF;

    let data_out = val.to_bytes().unwrap();
    assert_eq!(vec![0xAB, 0xAC, 0xBE, 0xFF], data_out);

    //let data_base: Vec<u8> = vec![0xBE, 0xEF];
    //let (_rest, mut val) = DekuBase::from_bytes((data_base.as_ref(), 0)).unwrap();
    //assert_eq!(DekuBase {
    //    field_a: 0xBE,
    //    field_b: 0xEF,
    //}, val);

    //println!("pass\n");
}

src/lib.rs

use deku::prelude::*;

#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "endian", ctx = "endian: deku::ctx::Endian")]
pub struct DekuBase {
    field_a: u8,
    field_b: u8,
}

#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
#[deku(endian = "little")]
pub struct DekuTest {
    base: DekuBase,
    field_b: u8,
    field_c: u8,
}

Now you can run cargo doc and see the diff between the two. I would write some code that uses only read() but I'm short on time today 2022-09-13-114458_484x148_scrot 2022-09-13-114514_529x523_scrot

The docs @ https://docs.rs/deku/latest/deku/trait.DekuContainerRead.html give the container context constraints.

sharksforarms commented 1 year ago

Hey @mmmfarrell sorry for not getting back to you sooner.

The error message really isn't great here...

Basically, you're trying to read with DekuBase which requires a context ctx argument endian, which, is normally passed by the parent struct.

But because you're using it on it's own, it doesn't know what endian is and the proc_macro doesn't generate the code to read. So, we need to specify this default with ctx_default.

https://docs.rs/deku/latest/deku/attributes/index.html#ctx_default

use deku::prelude::*;

#[derive(Debug, PartialEq, DekuRead, DekuWrite)]
- #[deku(endian = "endian", ctx = "endian: deku::ctx::Endian")]
+ #[deku(endian = "endian", ctx = "endian: deku::ctx::Endian", ctx_default = "deku::ctx::Endian::Little")]
struct DekuBase {
    field_a: u8,
    field_b: u8,
}
sharksforarms commented 1 year ago

Closing for now. Feel free to re-open if your issue isn't solved