paupino / rust-decimal

Decimal number implementation written in pure Rust suitable for financial and fixed-precision calculations.
https://docs.rs/rust_decimal/
MIT License
1.03k stars 183 forks source link

Request for rkyv 0.8 support #682

Closed fuchaoqun closed 1 month ago

fuchaoqun commented 1 month ago

let a = Decimal::from_str("0.123").unwrap(); let bytes = rkyv::tobytes::<, 256>(&a).unwrap();

Cargo.toml

rkyv = "0.8"

can not build with rkyv 0.8.x, 0.7.x works

Tony-Samuels commented 1 month ago

We currently only support 0.7, but I'll raise an MR for 0.8 support instead (removing 0.7).

If you need support immediately, consider using remote derive support.

Tony-Samuels commented 1 month ago

Actually, @paupino, how do you want to handle an rkyv bump?

The problem is as follows:

Options I see are:

paupino commented 1 month ago

@Tony-Samuels That remote derive feature is quite a nice approach! Ultimately, that is probably the best way forward since maintaining multiple versions is always a bit of a nightmare.

We have handled a situation like this before with regards to diesel. In that we handled version 1 and 2 simultaneously by namespacing the dependency. That said, I'm not a big fan of this as it creates a bit of a mess in regards to feature management (i.e. need to allow both versions to be enabled at once, picking only one etc). It's also not incredibly scalable since there is always a hard dependency on someone keeping things updated.

Perhaps collectively if we can provide an example remote derive then that could be beneficial - eventually we can bake it into our docs too about how to use it (maybe via examples?). At least, that is definitely an avenue I think we should explore.

fuchaoqun commented 1 month ago

hope for an example of a performance-critical use case.

fuchaoqun commented 1 month ago
#[derive(Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
#[rkyv(remote = rust_decimal::Decimal)]
pub struct ArchivedDecimal {
    #[rkyv(getter = get_bytes)]
    bytes: [u8; 16],
}

fn get_bytes(d: &Decimal) -> [u8; 16] {
    d.serialize()
}

impl From<ArchivedDecimal> for Decimal {
    fn from(value: ArchivedDecimal) -> Self {
        Decimal::deserialize(value.bytes)
    }
}

#[derive(Debug, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)]
pub struct FB {
    pub foo: String,
    #[rkyv(with = ArchivedDecimal)]
    pub bar: Decimal,
}

let foo = FB {
    foo: "Foo".to_string(),
    bar: Decimal::from_str("123.456789").unwrap(),
};
let bar = rkyv::to_bytes::<rkyv::rancor::Error>(&foo).unwrap();

let archived = rkyv::access::<ArchivedFB, rkyv::rancor::Error>(&bar).unwrap();
let deserialized = rkyv::deserialize::<FB, rkyv::rancor::Error>(archived).unwrap();

an ugly example, is there any better implement? performance critical use case.

Tony-Samuels commented 1 month ago

Prettiness-wise that's nearly as good as you're going to get. You can replace the get_bytes function with #[rkyv(getter = Decimal::serialize)] to make it cleaner. At that point it's just a #[rkyv(with = ArchivedDecimal)] where you want it included and less than 10 lines of conversion code.

Performance-wise, it should be pretty close to a direct derive.