defaulted 0.1.3

Trait and derive macro for testing whether a value equals its default state, with per-field customization and optional serde integration
Documentation

defaulted

Trait and derive macro for testing whether a value equals its default state, with per-field customization and optional serde integration.

Overview

Rust's standard [Default] trait constructs default values, but it says nothing about whether an existing value is still at its default state. This crate fills that gap with a single-method trait:

pub trait Defaulted {
    fn is_defaulted(&self) -> bool;
}

The crate also provides:

  • Built-in impls for all primitive types, String, Option, Vec, HashMap, HashSet, etc.
  • A derive macro #[derive(Defaulted)] (behind derive feature) to generate the impl automatically for a user-defined struct or enum.
  • An attribute macro #[skip_serializing_defaults] (behind derive and serde features) making serde skip serialization for fields that are in their default state.

Usage

Add the crate to Cargo.toml:

[dependencies]
defaulted = { version = "0.1", features = ["derive"] }

Enable serde support with:

[dependencies]
defaulted = { version = "0.1", features = ["derive", "serde"] }

Basic derive

use defaulted::Defaulted;

#[derive(Default, Defaulted)]
struct Config {
    width: u32,
    height: u32,
    label: String,
}

let c = Config::default();
assert!(c.is_defaulted());

let c2 = Config { width: 1920, ..Config::default() };
assert!(!c2.is_defaulted());

Per-field customization

Each field can opt into one of several checker strategies via #[defaulted(...)]:

Attribute Meaning
(none) Delegate to the field type's Defaulted impl
#[defaulted(default = expr)] Field is "default" when it equals expr
#[defaulted(with = func)] Call func(&field) -- true means "default"
#[defaulted(with = |v| ...)] Inline closure
#[defaulted(ignore)] Always treat this field as default (skip it)
use defaulted::Defaulted;

#[derive(Defaulted)]
struct Request {
    path: String,

    // "default" timeout is 30, not 0
    #[defaulted(default = 30)]
    timeout_secs: u64,

    // treat any non-empty tag list as non-default
    #[defaulted(with = Vec::is_empty)]
    tags: Vec<String>,

    // internal counter -- never affects defaulted
    #[defaulted(ignore)]
    _request_id: u64,
}

Enums

Mark exactly one variant with #[defaulted(default)]:

use defaulted::Defaulted;

#[derive(Defaulted)]
enum Status {
    #[defaulted(default)]
    Idle,
    Running { task: String },
    Failed(u32),
}

assert!(Status::Idle.is_defaulted());
assert!(!Status::Running { task: "build".into() }.is_defaulted());

Enum variants with fields follow the same per-field rules as structs.

Generating Default from field defaults

#[defaulted(Default)] on a struct generates a Default impl whose field values match the #[defaulted(default = expr)] annotations, so the two stay in sync automatically:

use defaulted::Defaulted;

#[derive(Debug, PartialEq, Defaulted)]
#[defaulted(Default)]
struct Settings<'a> {
    #[defaulted(default = "dark")]
    theme: &'a str,

    #[defaulted(default = 8080)]
    port: u16,

    enabled: bool,
}

let s = Settings::default();
assert_eq!(s.theme, "dark");
assert_eq!(s.port, 8080);
assert!(s.is_defaulted());

Serde integration

#[skip_serializing_defaults] rewrites a struct's fields to add #[serde(skip_serializing_if = "...")] (and #[serde(default = "...")] for EqualsValue fields) automatically, so default-valued fields are omitted from serialized output and restored correctly on deserialization.

use defaulted::Defaulted;
use serde::{Deserialize, Serialize};

#[defaulted::skip_serializing_defaults] // must go before #[derive(Serialize)]
#[derive(Debug, Serialize, Deserialize, Defaulted)]
#[defaulted(Default)]
struct ApiConfig {
    #[defaulted(default = "https://api.example.com")]
    base_url: String,

    #[defaulted(default = 60)]
    timeout_secs: u32,

    tags: Vec<String>,
}

let c = ApiConfig::default();
let json = serde_json::to_string(&c).unwrap();
assert_eq!(json, "{}"); // all fields at default -- nothing serialized

Feature flags

Flag Default Description
std yes Enables impls for HashMap, Mutex, Path, etc. Implies alloc.
alloc yes (via std) Enables impls for String, Vec, Box, Arc, Cow, etc.
derive no Enables #[derive(Defaulted)]
serde no Enables #[skip_serializing_defaults] attribute macro
serde-json no Enables impls for serde_json::Value and serde_json::Map
serde-yaml no Enables impls for serde_yaml::Value and serde_yaml::Mapping
bytes no Enables impls for Bytes and BytesMut
indexmap no Enables impls for IndexMap and IndexSet
smallvec no Enables impls for SmallVec

no_std support

Disable default features and optionally enable alloc:

[dependencies]
defaulted = { version = "0.1", default-features = false }              # core only
defaulted = { version = "0.1", default-features = false, features = ["alloc"] }  # core + alloc

License

MIT