🧊

Serdeの`default = "path`属性は、そのフィールドがdeserializeされる時にしか呼ばれない

つまり、デフォルト指定してても、いざデフォルトで取得してしまうと、意図した値がデフォルトにならない。

何言ってるんやって感じなので、順を追って書く。

default = "path"属性とは

Field attributes · Serde https://serde.rs/field-attrs.html

SerdeでstructをJSONから生成するとき、つまりdeserializeするとき、オプショナルなキーなんかに初期値を設定したいとする。

その場合には、#[serde(default)]という属性を書けばよい。

#[derive(Deserialize)]
struct Config {
    #[serde(default)]
    key: String,
}

ただこの指定では、その型の初期値しか指定できない。

  • Stringなら空文字列
  • boolならfalse
  • 数値型は0
  • etc…

そこで、デフォルトをtrueにしたいとか、独自の文字列にしたい場合には、#[serde(default = "path")]と書きつつ、pathという関数を定義しておく。

serde::Deserializeで、default=trueしたい | Memory ice cubes https://leaysgur.github.io/posts/2024/03/13/213641

deserialize()では呼ばれるけど

で、この#[serde(default = "path")]による指定は、deserialize()される時に呼ばれる。

let config: Config = serde_json::from_str(json).unwrap();
// or
let config = Config::deserialize(json).unwrap();
// or...

このときは、意図した通りにカスタムな初期値が指定されてる。

default()では呼ばれない

これが問題のケース。

ついでに#[derive(Default)]属性を付けて、default()できるようにしてた場合。

let config = Config::default();
// or...

なんとこのときは、さっきのカスタムな初期値は指定されない。

バグでは・・・?って思ったけど、どうやら意図通りらしい。

Add #[derive(serde::Default)] that is aware of serde default annotations · Issue #2622 · serde-rs/serde https://github.com/serde-rs/serde/issues/2622

deserialize()で呼ばれても、ネストの影響を受ける

つまりこういうこと。

use serde::Deserialize;

#[derive(Debug, Deserialize, Default)]
struct Config {
    #[serde(default)]
    k1: bool,
    #[serde(default = "default_true")]
    k2: bool,

    #[serde(default)]
    nested: Nested,
}

#[derive(Debug, Deserialize, Default)]
struct Nested {
    #[serde(default)]
    k1: bool,
    #[serde(default = "default_true")]
    k2: bool,
}

fn default_true() -> bool {
    true
}

fn main() {
    let c = Config::deserialize(serde_json::json!({})).unwrap();
    println!("{c:?}");

    let c = Config::default();
    println!("{c:?}");
}

これを実行すると、こうなる。

Config { k1: false, k2: true,  nested: Nested { k1: false, k2: false } }
Config { k1: false, k2: false, nested: Nested { k1: false, k2: false } }

前者はc.nested.k2falseになってしまってて、後者はc.k2ですらfalseになってしまってる。

どうする

先のIssueでは、これを解決するためのクレートを作ってる人がいた。

BrynCooke/serde-derive-default https://github.com/BrynCooke/serde-derive-default

これで済ませられるならそれでいい気もするけど、正解はわかってない。

自分でDefaultを実装するのが、正攻法なんだろうか。