Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write example for backwards compatbility via enums #393

Open
crwn1337 opened this issue Jun 8, 2023 · 3 comments
Open

Write example for backwards compatbility via enums #393

crwn1337 opened this issue Jun 8, 2023 · 3 comments
Labels
question Further information is requested
Milestone

Comments

@crwn1337
Copy link

crwn1337 commented Jun 8, 2023

Is there anyway to do backwards compatibility for enums? a short example:

pub struct V1 {
    pub label: String,
}

pub struct V2 {
    pub label: String,
    pub password: String,
}

pub struct Latest {
    pub label: String,
    pub password: String,
    pub destination: String,
}

pub enum Door {
    V1(V1),
    V2(V2),
    Latest(Latest),
}

pub fn v1_to_v2(door: V1) -> V2 {
    V2 {
        label: door.label,
        password: "".to_string(),
    }
}

pub fn v2_to_latest(door: V2) -> Latest {
    Latest {
        label: door.label,
        password: door.password,
        destination: "".to_string(),
    }
}

pub fn is_latest(door: &Door) -> bool {
    matches!(door, Door::Latest(_))
}

pub fn any_to_latest(door: Door) -> Door {
    match door {
        Door::V1(door) => {
            let door = v1_to_v2(door);
            let door = v2_to_latest(door);
            Door::Latest(door)
        }
        Door::V2(door) => {
            let door = v2_to_latest(door);
            Door::Latest(door)
        }
        Door::Latest(door) => Door::Latest(door),
    }
}

or is there a better way to do this? Adding a bigger struct to Door and trying to deserialize an older version of it fails.

@djkoloski
Copy link
Collaborator

@crwn1337
Copy link
Author

crwn1337 commented Jun 16, 2023

Have you seen this example? https://github.com/rkyv/rkyv/blob/master/examples/backwards_compat/src/main.rs

It's not really a good example, if I would have a lot of versions wouldn't I have to keep calling rkyv::check_archived_root on all of them to check which one of them matches, I haven't checked how that function works, but shouldn't it get expensive if there were A LOT of versioned enums in a vec, if so is this even the correct crate for my purpose (storing data about a game's world)?.

Is there no way to just do this:

pub enum Test {
    A(u16),
    B(u32),
}

pub fn main() {
    let test = Test::A(1);

    let mut serializer = AllocSerializer::<0>::default();
    serializer.serialize_value(&test).unwrap();
    let bytes = serializer.into_serializer().into_inner();

    println!("{:?}", bytes);
    // [0, 0, 1, 0, 0, 0, 0, 0]
}

increase the size of the enum by adding C, but keeping A and B the same:

pub enum Test {
    A(u16),
    B(u32),
    C(u64)
}

pub fn main() {
    // bytes from above
    let bytes: [u8; 8] = [0, 0, 1, 0, 0, 0, 0, 0];
    let archived = rkyv::check_archived_root::<Test>(&bytes[..]).unwrap();
    let deserialized: Test = archived.deserialize(&mut rkyv::Infallible).unwrap();

    println!("{:?}", deserialized);
}

this should work, since I haven't changed A nor B, but:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: ContextError(ArchiveError(OutOfBounds { base: 0x7dcb6ff6e8, offset: -8, range: 0x7dcb6ff6e8..0x7dcb6ff6f0 }))', src\main.rs:199:66
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\rust-tests.exe` (exit code: 101)

@djkoloski
Copy link
Collaborator

When the C variant is added to Test, the size of Test increased from size_of::<Discriminant>() + size_of::<u32>() to size_of::<Discriminant>() + size_of::<u64>(). This breaks binary compatibility because even though the first two variants haven't changed, the overall enum has and rkyv checks for enough memory for an ArchivedTest before checking the enum discriminant. One way to work around this is to make sure that the size of Test never increases by boxing all of your enum fields:

#[derive(Archive, Serialize, Deserialize)]
pub enum Test {
    A(#[with(AsBox)] u16),
    B(#[with(AsBox)] u32),
    C(#[with(AsBox)] u64),
}

This isn't 100% perfect because if you add enough variants to increase the size of the discriminant, the size of the enum may change. But it should be good enough in most cases.

@djkoloski djkoloski added the question Further information is requested label Jan 10, 2024
@djkoloski djkoloski changed the title Backwards compatibility for enums? Write example for backwards compatbility via enums Mar 9, 2024
@djkoloski djkoloski added this to the v0.8 milestone Mar 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants