sharksforarms / deku

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

deku_derive fails when combined with cfg_attr #343

Open NTmatter opened 1 year ago

NTmatter commented 1 year ago

As background, I'm trying to use Deku as an optional dependency by leveraging #[cfg_attr(feature = "deku", derive(DekuRead))] to toggle Deku's functionality.

This leads to failures when trying to use deku_derive when temp fields are present, as in #[cfg_attr(feature = "deku", deku_derive(DekuRead))]

Using the snippets below, a cargo check (Deku disabled) will complete without issues.

Running cargo check --features deku will fail on Baz, stating cannot find attribute deku in scope and error[E0277]: the trait boundVec: deku::DekuRead<'_, Endian>is not satisfied.

Cargo.toml - deku disabled by default:

[package]
name = "deku-conditional"
version = "0.1.0"
edition = "2021"

[dependencies]
deku = { version = "0.16.0", optional = true }

[features]
deku = ["dep:deku"]

src/main.rs - optional deku support:

#[cfg(feature = "deku")]
use deku::prelude::*;

// Using deku_derive with cfg_attr fails
#[cfg_attr(feature = "deku", deku_derive(DekuRead))]
#[cfg_attr(feature = "deku", deku(endian = "big"))]
struct Baz {
    #[cfg_attr(feature = "deku", deku(temp))]
    length: u16,
    #[cfg_attr(feature = "deku", deku(count = "length"))]
    payload: Vec<u16>
}

// Conditional compilation works with regular derive.
#[cfg_attr(feature = "deku", derive(DekuRead))]
#[cfg_attr(feature = "deku", deku(endian = "big"))]
struct Bar {
    length: u16,
    #[cfg_attr(feature = "deku", deku(count = "length"))]
    payload: Vec<u16>
}

// Using deku_derive without conditional compilation works as documented.
// #[deku_derive(DekuRead)]
// #[deku(endian = "big")]
// struct Foo {
//     #[deku(temp)]
//     length: u16,
//     #[deku(count = "length")]
//     payload: Vec<u16>
// }

fn main() { }

Workaround: Manually duplicate affected structs, strip Deku attributes, and conditionally compile with #[cfg(feature = "deku")] and #[cfg(not(feature = "deku"))].

#[cfg(feature = "deku")]
use deku::prelude::*;

// Using deku_derive to remove a temp field works as documented
#[cfg(feature = "deku")]
#[deku_derive(DekuRead)]
#[deku(endian = "big")]
struct Foo {
    #[deku(temp)]
    length: u16,
    #[deku(count = "length")]
    payload: Vec<u16>
}

#[cfg(not(feature = "deku"))]
struct Foo {
    length: u16,
    payload: Vec<u16>
}