Question

How to Set Default Values for Structs in Rust

rustdefault-value

Question

In Rust, I have a struct where each field needs an initial default value when the struct is created. Right now I am using a new() function that manually assigns every field:

struct CParams {
    i_insert_max: i64,
    i_update_max: i64,
    i_delete_max: i64,
    i_instance_max: i64,
    t_first_instance: bool,
    t_create_tables: bool,
    t_continue: bool,
}

impl CParams {
    fn new() -> CParams {
        CParams {
            i_insert_max: -1,
            i_update_max: -1,
            i_delete_max: -1,
            i_instance_max: -1,
            t_first_instance: false,
            t_create_tables: false,
            t_continue: false,
        }
    }
}

I would prefer a shorter or more idiomatic way to give struct fields default values when the struct is instantiated. Is there a more concise approach in Rust?

Short Answer

By the end of this page, you will understand how Rust initializes struct fields, why fields cannot hold inline default values in the struct definition itself, and how to use idiomatic alternatives such as a new() constructor, the Default trait, and struct update syntax to keep initialization concise and maintainable.

Concept

In Rust, a struct definition describes the shape of the data, not the values each field should start with. That means you cannot write default field values directly inside the struct declaration like some other languages allow.

Instead, Rust uses a few common patterns:

  • A constructor function such as new()
  • The Default trait for standard default values
  • Struct update syntax to override only a few fields

Why this matters:

  • It keeps type definitions separate from object creation logic.
  • It makes initialization explicit.
  • It scales better when your struct grows or when multiple initialization styles are needed.

For your example, manually writing all fields inside new() works, but the more idiomatic Rust approach is often to implement Default. Then you can create values with:

let params = CParams::default();

If you want mostly default values but need to change one or two fields, you can combine that with struct update syntax:

let params = CParams {
    i_insert_max: 10,
    ..Default::default()
};

This is both shorter and easier to maintain.

Mental Model

Think of a Rust struct as a blueprint for a form.

The struct says:

  • these are the fields that exist
  • these are their types

But the struct does not say what values should be filled in automatically.

To actually fill out the form, you need a helper:

  • new() is like filling the form by hand each time
  • Default is like having a pre-filled template
  • struct update syntax is like taking the template and changing only a few boxes

So the struct is the form layout, and Default is the saved template.

Take Quiz

Syntax and Examples

Basic constructor

A straightforward solution is to keep a new() method:

struct CParams {
    i_insert_max: i64,
    i_update_max: i64,
    i_delete_max: i64,
    i_instance_max: i64,
    t_first_instance: bool,
    t_create_tables: bool,
    t_continue: bool,
}

impl CParams {
    fn new() -> Self {
        Self {
            i_insert_max: -1,
            i_update_max: -1,
            i_delete_max: -1,
            i_instance_max: -1,
            t_first_instance: false,
            t_create_tables: false,
            t_continue: false,
        }
    }
}

This is valid and clear. Using Self is slightly shorter than repeating CParams.

Using the Default trait

A more idiomatic solution is to implement Default:

struct CParams {
    i_insert_max: i64,
    i_update_max: i64,
    i_delete_max: i64,
    i_instance_max: i64,
    t_first_instance: bool,
    t_create_tables: bool,
    t_continue: bool,
}

impl Default for CParams {
    fn default() -> Self {
        Self {
            i_insert_max: -1,
            i_update_max: -1,
            i_delete_max: -1,
            i_instance_max: -1,
            t_first_instance: false,
            t_create_tables: false,
            t_continue: false,
        }
    }
}

Now you can create the struct like this:

let params = CParams::default();

Override a few fields with struct update syntax

let params = CParams {
    i_insert_max: 100,
    t_continue: true,
    ..Default::default()
};

This means:

  • set i_insert_max to 100
  • set t_continue to true
  • use default values for all remaining fields

When derive(Default) works

If all fields should use their type's normal defaults, you can derive Default automatically:

#[derive(Default)]
struct Flags {
    enabled: bool,
    retries: i32,
}

Here:

  • bool defaults to false
  • integers default to 0

But in your case, several integer fields should be -1, so you need a manual Default implementation instead of derive(Default).

Step by Step Execution

Consider this example:

struct CParams {
    i_insert_max: i64,
    i_update_max: i64,
    t_continue: bool,
}

impl Default for CParams {
    fn default() -> Self {
        Self {
            i_insert_max: -1,
            i_update_max: -1,
            t_continue: false,
        }
    }
}

fn main() {
    let params = CParams {
        i_insert_max: 50,
        ..Default::default()
    };

    println!("{}", params.i_insert_max);
    println!("{}", params.i_update_max);
    println!("{}", params.t_continue);
}

What happens step by step

  1. Rust sees the CParams struct definition.
  2. Rust sees the Default implementation for CParams.
  3. In main, a new CParams value is created.
  4. i_insert_max is explicitly set to 50.
  5. Default::default() is called to fill in the remaining fields.
  6. So i_update_max becomes -1 and t_continue becomes false.
  7. The program prints:
50
-1
false

This is useful because you only write the fields you want to customize, while the rest come from a single central default definition.

Real World Use Cases

Default struct values are common in real Rust programs.

Configuration objects

A program may have settings like:

  • timeout values
  • retry limits
  • feature flags

You can define sensible defaults and override only what is needed.

API request options

An HTTP client might use a struct like:

  • follow_redirects: true
  • timeout_secs: 30
  • use_cache: false

Most callers use defaults. A few callers customize specific fields.

Command-line tools

A CLI app may store parsed options in a struct. If the user does not provide a flag, the program uses defaults.

Test data setup

Tests often need objects that are mostly the same. A Default implementation lets you create a base object and change only the fields relevant to a test.

Stateful application logic

Structs representing application state often need a known initial state before processing begins.

Take Quiz

Real Codebase Usage

In real projects, developers commonly use Default in a few standard patterns.

Pattern: default configuration object

let config = AppConfig::default();

This gives a clean starting point.

Pattern: override only what matters

let config = AppConfig {
    debug: true,
    ..Default::default()
};

This is very common in tests and setup code.

Pattern: combine new() with Default

Sometimes a type provides both:

impl MyType {
    fn new() -> Self {
        Self::default()
    }
}

This is useful when users naturally expect a new() constructor.

Pattern: validation after construction

If some fields must follow rules, developers may avoid exposing public fields directly and instead provide constructor functions that validate values.

impl AppConfig {
    fn new(timeout: i64) -> Self {
        Self {
            timeout,
            ..Default::default()
        }
    }
}

Pattern: test fixtures

Tests often create a default object and override a single field:

let params = CParams {
    t_continue: true,
    ..Default::default()
};

This keeps tests short and readable.

Common Mistakes

Mistake 1: Expecting inline field defaults in the struct definition

This is not valid Rust:

struct CParams {
    i_insert_max: i64 = -1,
    t_continue: bool = false,
}

Rust structs declare field names and types only.

Mistake 2: Using derive(Default) when custom values are needed

#[derive(Default)]
struct CParams {
    i_insert_max: i64,
    t_continue: bool,
}

This gives:

  • i_insert_max = 0
  • t_continue = false

If you need -1, derive(Default) is not enough. Write impl Default manually.

Mistake 3: Forgetting required fields without ..Default::default()

Broken code:

let params = CParams {
    i_insert_max: 10,
};

Rust requires all fields to be initialized unless you use struct update syntax.

Correct version:

let params = CParams {
    i_insert_max: 10,
    ..Default::default()
};

Mistake 4: Duplicating the same defaults in many places

If you manually repeat the same values across the codebase, maintenance becomes harder.

Better approach:

  • put defaults in Default
  • optionally call them from new()

Mistake 5: Using unclear sentinel values

Using -1 can be valid, but ask whether it really represents "not set" clearly.

Sometimes a better design is:

struct CParams {
    i_insert_max: Option<i64>,
}

Then None means "no value provided" more explicitly than -1.

Comparisons

ApproachWhen to use itProsCons
new() methodYou want a familiar constructorClear and explicitCan become repetitive
Manual Default implementationYou need custom defaults like -1Idiomatic, reusable, works well with struct update syntaxSlightly more code up front
#[derive(Default)]Type defaults are correct for every fieldVery shortCannot set custom defaults like -1
Struct update syntaxYou want to override a few fieldsConcise and readableUsually depends on Default or another existing struct

new() vs Default

let a = CParams::new();
let b = CParams::default();
  • new() is common and easy to understand.
  • Default is more idiomatic when a type has a natural default state.
  • Default works especially well with generic code and ..Default::default().

Sentinel value vs Option

ChoiceMeaningGood whenDrawback
-1Special numeric markerYou must interact with legacy logic or fixed formatsCan be unclear
Option<i64>Some(value) or NoneAbsence of a value is meaningfulSlightly more handling required
Take Quiz

Cheat Sheet

Quick reference

Define a struct

struct CParams {
    i_insert_max: i64,
    t_continue: bool,
}

Constructor with new()

impl CParams {
    fn new() -> Self {
        Self {
            i_insert_max: -1,
            t_continue: false,
        }
    }
}

Implement Default

impl Default for CParams {
    fn default() -> Self {
        Self {
            i_insert_max: -1,
            t_continue: false,
        }
    }
}

Create with defaults

let p = CParams::default();

Override some fields

let p = CParams {
    i_insert_max: 100,
    ..Default::default()
};

Auto-derive defaults

#[derive(Default)]
struct Flags {
    enabled: bool,
    retries: i32,
}

Important rules

  • Rust does not support inline field default values in struct definitions.
  • Use Default when the type has a natural default state.
  • Use manual impl Default for custom values.
  • Use #[derive(Default)] only when each field's type default is correct.
  • Use ..Default::default() to fill in omitted fields.

Edge case

If -1 means "unset", consider whether Option<i64> is clearer.

FAQ

Can I assign default values directly in a Rust struct definition?

No. Rust struct definitions only declare field names and types. Default values must be provided through construction logic such as new() or Default.

Is Default more idiomatic than new() in Rust?

Often, yes. If a type has a natural default state, implementing Default is idiomatic and works nicely with other Rust features.

When should I use #[derive(Default)]?

Use it when every field should use its type's built-in default value, such as 0 for integers and false for booleans.

Why doesn't #[derive(Default)] work for -1?

Because deriving Default uses the default of the field's type. For i64, that default is 0, not -1.

Can I keep both new() and Default?

Yes. A common pattern is for new() to simply call Self::default().

Is using -1 as a default a good idea?

It can work, but it may be less clear than using Option<i64> when you mean "no value set".

What is ..Default::default() doing?

It fills in all unspecified fields using the struct's Default implementation.

Take Quiz

Mini Project

Description

Create a small Rust configuration type for a command-line import tool. The struct should have sensible default values, but allow a caller to override only the settings they care about. This demonstrates the practical use of Default and struct update syntax in a realistic setup scenario.

Goal

Build a Rust struct with custom default values and create multiple configurations by overriding selected fields.

Requirements

  • Define a struct named ImportConfig with at least four fields.
  • Implement the Default trait manually because at least one field must use a non-standard default value.
  • Create one config using ImportConfig::default().
  • Create another config that overrides one or two fields using struct update syntax.
  • Print both configurations to verify the values.
Take Quiz

Keep learning

Related questions

View more