В утилите NetRuleRouter для маршрутизации трафика два параметра должны были включаться по умолчанию, но после обмена JSON-сообщениями между компонентами приложения оказывались выключенными. Ошибок десериализации не возникало, а значения выглядели допустимыми. Причина — атрибут #[serde(default)]: при отсутствии поля он подставляет Default::default() для его типа, а для bool это false.

Основная логика NetRuleRouter написана на Rust, интерфейс — на Qt/QML, компоненты обмениваются сообщениями через IPC в том числе в JSON. По мере развития в настройках появлялись новые поля, и отправитель со старой версией формата передавал JSON без них. Если поле обязательное, Serde вернёт ошибку, поэтому автор использовал #[serde(default)] — но без явного значения для конкретной настройки.

Так отсутствие поля автоматически загрузки правил и поля комментариев при экспорте превращалось в false, хотя оба параметра должны были быть включены. Автор подчёркивает: значение по умолчанию для типа и значение по умолчанию для настройки — не одно и то же. Serde не знает требований приложения, для него false — корректный дефолт для bool.

Исправление — назначить функцию, возвращающую нужное значение при отсутствии поля. Именованные функции вроде default_export_comments удобнее для сопровождения, чем одна общая: по имени видно, какая настройка получает дефолт. Тесты проверяют два случая: сообщение без новых полей получает нужный дефолт, а явно переданный false сохраняется.

Отдельно автор отмечает: #[serde(default)] влияет только на десериализацию и сам по себе не заставляет Serde пропускать поля, равные значению по умолчанию, при формировании JSON.

Похожая задача возникла при диагностике сетевых адаптеров: нужно было передавать из службы в интерфейс признак has_forwarding_path — есть ли через адаптер путь для трафика. У признака три состояния, а не два: путь есть, пути нет и результат неизвестен. Поле типа Option даёт None и при отсутствии поля, и при явном null — этого достаточно, чтобы отличать виртуальный host-only адаптер от VPN-туннеля без обычного шлюза и не показывать ложное предупреждение, когда проверка не выполнялась.

Заменять все булевы поля на Option автор не советует: для настройки есть два состояния, и при отсутствии поля нужен заранее заданный дефолт, а для результата диагностики отсутствие значения само по себе значимо. При добавлении полей в JSON-контракт он рекомендует проверять, что произойдёт при сообщении от старого отправителя, совпадает ли Default::default() с поведением приложения и не теряется ли важное состояние при выборе типа.