Можно ли объяснить serde зависимость значений полей?


#1

Пример:

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct S {
    pub base_n: u8,
    pub n: u8,
}

Хотелось бы, что бы в конфиге пользователю необходимо было указывать только n поле, а base_n при/после сериализации автоматически приравнивалось бы к n.

У serde есть https://serde.rs/field-attrs.html#serdedefault и https://serde.rs/field-attrs.html#serdedefault--path, но они должны иметь сигнатуру fn() -> T, т.е. не могут использовать значения из других полей структуры.

Единственное, что пока в голову приходит: дописать #[serde(default)] к base_n полю, что бы оно ставилось в ноль (давать Default всей структуре тоже не хочется), а потом, после сериализации, руками вызывать функцию init, которая сделает с полями что нужно.

#[macro_use]
extern crate serde; // 1.0.78

extern crate serde_json; // 1.0.27

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct S {
    #[serde(default)]
    pub base_n: u8,

    pub n: u8,
}

impl S {
    fn from_str(s: &str) -> Self {
        let mut this: S = serde_json::from_str(s).unwrap();
        this.base_n = this.n;
        this
    }
}

fn main() {
    let s = S::from_str(r#"{ "n": 3 }"#);
    println!("s = {:?}", s);    
}

Playground

Но тут система типов и автоматизация мало нам помогают соблюдать инварианты и появляется опасность когда-нибудь потом случайно десериализовать структуру без вызова initа.


#2

Как вариант, можно попробовать добавить приватное () поле, которое помешает создавать структуру как-то в обход ее from_str метода. Выглядит несколько костыльно, но не так уж и хуже перекладывания этой работы на serde.


#3

Вариант с приватным полем:

#[macro_use]
extern crate serde; // 1.0.78

extern crate serde_json; // 1.0.27

mod data {
    use serde_json;

    #[derive(Clone, Debug, Serialize, Deserialize)]
    pub struct S {
        #[serde(default)]
        pub base_n: u8,

        pub n: u8,

        #[serde(skip)]
        _priv: (),
    }

    impl S {
        pub fn from_str(s: &str) -> Self {
            let mut this: S = serde_json::from_str(s).unwrap();
            this.base_n = this.n;
            this
        }
    }
}

fn main() {
    let s = data::S::from_str(r#"{ "n": 3 }"#);
    println!("s = {:?}", s);

    // Can't create `S` with any other safe method:
    //
    // let s2 = data::S { n: 0, base_n: 10, _priv: () };
    // // ^ field `_priv` of struct `data::S` is private
    //
    // let s3 = data::S { n: 0, base_n: 10 };
    // // ^ missing field `_priv` in initializer of `data::S`
    //
    // let s4 = data::S { n: 0, base_n: 10, ..std::default::Default::default() };
    // // ^ the trait bound `data::S: std::default::Default` is not satisfied
}

Playground

В целом работает. Правда, теперь все такое громосткое, что я не уверен стоит ли оно того) С другой стороны, serde сам не смог бы защитить от создания структуры руками и тоже пришлось бы приватное поле делать.