Versioned structure?

I'm working with FFI, some native structures may have new fields added with updated version. I don't want to define a new struct for each version but want to use derive/macro to handle this automatically.

For example, I can create a new "Verisioned" derive with "version" attribute for a struct to describe the fields and their version requirments:

#[derive(Versioned)]
#[repr(C)]
pub struct InnerStruct {
    pub value1: u32,
    #[version(added = 2)] 
    pub value2: u32,
    #[version(added = 3, removed = 6)]
    pub value3: String,
}

I want to return an Option::None if the version is not matched with "version" attribute, otherwise, return the value type. The problem for this is, the struct itself doesn't have the required "version" field, because the original struct doesn't have version at all. This "version" field is specified by a "outer" struct.

pub struct OuterStruct {
    pub version: u32,
    pub inner: InnerStruct,
    pub other: u32,
}

I want to reexport fields of InnerStruct basically for OuterStruct. For example,

impl OuterStruct {

    pub fn value2(&self) -> Option<u32> {
        if self.version >= 2 {
            Some(self.inner.value2)
        } else {
            None
    }
}

As you can see, the implementation for the value2 function is quite trivial, though it needs access the version attribute to know what version is required for the field. I want to see if derive/macro can be used to generate these automatically, so I can use these functions directly?

AFAIK, proc-macro can only access the struct it applies to, so the inner struct with "Versioned" derive can't access the outer struct (it doesn't even know there is one exist or not though), the outer struct can't access inner struct either with macro expansion. Am I understanding this right? Is there any way to solve this problem?

Edit: Added more details.

Macros see only shallow syntax, and no types. Macros also only act locally, so cooperation between macros is very limited.

The macro could generate separate InnerStructV1, InnerStructV2 types for all the versions, and make code elsewhere choose the right type.

The OuterStruct looks tricky – it won't be able to know where the outer field is, because the field before it can change dynamically. It lacks #[repr(c)], so it won't match any specific layout anyway.

For a dynamically chosen version, you could generate an enum with all the version variants, instead of tracking the version externally yourself (with a caveat that it would have the size of the largest version).

Could the getters accept a version argument? Then you don't have the version stored in the struct, but still have the information needed to determine whether the field is present.

impl InnerStruct {
    get_value2(&self, version: u32) -> Option<u32> {
        if version >= 2 {
            Some(self.inner.value2)
        } else {
            None
        }
    }
}

If remembering which version each instance is becomes a pain, wrapping in OuterStruct seems sensible, but never pass it through FFI.

The outer struct impls become very rote, simply forwarding the call to the inner struct and providing the version for you.

impl OuterStruct {
    pub fn value2(&self) -> Option<u32> {
        return self.inner.get_value2(self.version)
    }
}

Copy and past. DRY code you (seem-to-)want takes more time than worth.