Defining custom `cfg` predicates

Hello,

I'm working on a netlink library, and a recurring issue for users is that the netlink message format changes from one target or kernel version to another. For reference, here is the issue: Avoid defining kernel dependent data structures. · Issue #44 · little-dude/netlink · GitHub

One idea to solve this would be to have conditional compilation based on the kernel version. For instance:

#[cfg(kernel_is_older(4.2.1))]
struct MyMessage {
    field1: u32,
    field2: u32,
}
#[cfg(not(kernel_is_older(4.2.1)))]
struct MyMessage {
    field1: u64,
    field2: u32
    new_field: String,
}

But from the Rust Reference, it doesn't seem to be possible. So I'm wondering is there's another way to achieve this sort of conditional compilation.

You can use the feature attribute to set your own conditions for compilation.

Yeah but I don't want to have to define a feature for each kernel version and configuration. I've thought about it though and I guess I could detect the kernel version and config in a build script and enable the proper features. But that doesn't seem very nice...

I'm a little confused how the kernel can be identified at compile time. Does this refer to the kernel headers being used to compile?

It depends on your constraints and what is/isn't acceptable, but one strategy I'd look at is defining multiple versions of the type and detecting which version to use at runtime.

Maybe something like this?

enum MyMessage {
  V1 { 
    field1: u32, 
    field2: u32,
  },
  V2 {
    field1: u32,
    field2: u32,
    new_field: String,
  }
}

You could then create getters to make things easier on downstream users.

impl MyMessage {
  fn field1(&self) -> u32 {
    match self {
      MyMessage::V1 { field1, .. } | MyMessage::V2 { field1, .. } => field1,
    }
  }

  fn new_field(&self) -> Option<&str> {
    match self {
      MyMessage::V1 { .. } => None,
      MyMessage::V2 { new_field, .. } => Some(new_field),
    }
  }
}

As a bonus, this method means downstream users won't get spurious compiler errors when build with a different kernel version than the one you tested with. It also avoids infecting half your codebase with #[cfg(kernel_is_older(4.2.1)] annotations.

Originally you had a pre-4.2.1 option and a post-4.2.1 option. Why can’t you do that with features? I don’t see the need to make one for each version if that’s not what you wanted.