Deserializing Struct

Hi,

I'm creating a library to handle money by using BigInts and now I'm trying to store those values in a database. I'm thinking of serializing the data to a JSON string and save it as text and then read it from the database when I need it.

The serializing works but I can't make the deserializing work as it is giving me the following error:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter `'de` due to conflicting requirements
  --> src\serialization.rs:18:5
   |
18 |     currency: Currency,
   |     ^^^^^^^^
   |
note: first, the lifetime cannot outlive the lifetime `'de` as defined on the impl at 14:35...
  --> src\serialization.rs:14:35

From what I can understand, the problem is about using a static lifetime in the Currency struct that outlives the de lifetime. I'm using a static str here because it allows me to use the Copy trait so I can just copy the variable everywhere.

What should I change to make it work?


Cargo.toml

[dependencies]
num = { version = "0.3.1", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

main.rs

use num::bigint::{BigInt};
use serde::{Deserialize, Serialize};


#[derive(Debug, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)]
pub struct Currency {
    name: &'static str,
    symbol: &'static str,
    separator: &'static str,
    decimal: &'static str,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Money {
    amount: BigInt,
    precision: u32,
    currency: Currency,
}

pub fn main() {
    let usd = Currency {
        name: "USD",
        symbol: "$",
        separator: ",",
        decimal: ".",
    };
    
    let money_1 = Money {
        amount: BigInt::from(100),
        precision: 0,
        currency: usd,
    };
    
    let money_2 = Money {
        amount: BigInt::from(200),
        precision: 0,
        currency: usd,
    };

    let json_1 = serde_json::to_string(&money_1).unwrap();
    println!("{:?}", json_1);
    
    let json_2 = serde_json::to_string(&money_2).unwrap();
    println!("{:?}", json_2);
}

You can't deserialize into 'static string slices (unless you try really hard and you have really special deserializers and a 'static buffer from which to deserialize – but usually you don't, and you shouldn't either).

Generally, it's better to deserialize into an owning type. Change the definition of Currency to:

pub struct Currency {
    name: String,
    symbol: String,
    separator: String,
    decimal: String,
}

Or, if you really-really want to also construct Currency from string literals (when not deserialized, of course), you can use a Cow, which is a copy-on-write smart pointer type, it contains either borrowed or owned data:

pub struct Currency {
    name: Cow<'static, str>,
    symbol: Cow<'static, str>,
    separator: Cow<'static, str>,
    decimal: Cow<'static, str>,
}
2 Likes

Thank you for replying.

Both of your tips work, but there is just one problem: I can't copy the Currency struct.

For example if I wanted to use the same Currency on multiple Money variables, I would need to either create a new currency or use references. Should I just change the Money struct to use a reference or is there other way to do this?

& are not general-purpose references, but temporary borrows, and many programming patterns just don't make sense with them.

If you want to share data "by reference", then Arc is the correct type to do it. You should be able to deserialize to Arc<str>.

You could also try to reuse Arc<Currency>, but I think for that you'd need a custom deserializer that knows how to look them up to reuse them.

Or have a thin enum CurrencyCode { ABC, DEF } and store a lookup table somewhere HashMap<CurrencyCode, CurrencyDetails>.

Indeed, Arc<Currency> doesn't work.

Initially I tried using an enum with the currency codes but I didn't want to have a huge enum with hundreds of currencies.

What would you do in this case?

Can't you just #[derive(Clone)] and .clone() the currency if you need a new one? It doesn't look like Currency is a rich object with identity, it seems like it's pure data. In which case, its memory address should not matter and you should be able to just clone it if needed.

1 Like

I can but, isn't it just a waste of performance by cloning the Currency everywhere?

Only you can tell if it is. I have no way of benchmarking your code and deciding whether it's fast enough. (It probably is, anyway.)

2 Likes

I'd say enum would be more idiomatic solution (if you know all the possible currencies up-front ofc), but anyway, if you want a cheap copy/clone, then you can consider small-string optimization. Or even just use chars, eg.:

pub struct Currency {
    // currency code is 3 letters by definition
    name: ArrayString[[u8; 3]],
    // I guess char should be enough for these?
    symbol: char,
    separator: char,
    decimal: char,
}

This will be even smaller to copy than your original &'static str. Check out:

Sidenote: I always get confused with these small-string/vec-crates and their naming, never remember which is which. Does anyone have a cheatsheet?

1 Like

char is not enough for currency symbol, e.g. there's "zł".

4 Likes

Then you can use a currency code, e.g. wrap [u8; 3] or use one of the small string libraries. And then use the code to look up rest of the info when needed.

2 Likes

For now I guess I'll use an enum for simplicity sake. At least until I build something usefull with it.

(off-topic)

:man_facepalming: I guess as a Pole I should have thought about it :upside_down_face: I think I was focusing too much on the pound sign and though "surely there are multibyte unicode symbols for currencies, but I guess nobody uses combining characters"