How to implement Default trait

Implement Default trait by defining a default() function or using #[derive(Default)] to create standard instances of your type.

The baseline you always need

You're writing a configuration struct for a CLI tool. It has twelve fields: log level, timeout, retry count, output format, cache directory, and so on. Every time you need a fresh instance, you type out all twelve fields. Half the time you just want the sensible baseline. You end up copy-pasting the initialization block, changing one field, and hoping you didn't miss a typo in the copy. There's a better way. The Default trait gives you a standard way to say "give me the sensible baseline for this type."

What Default actually is

Default is a trait defined in the standard library. It has one method: fn default() -> Self. That's it. The trait promises that you can ask a type for an instance without providing any arguments, and you'll get back a value that is safe to use.

Think of Default like the factory settings on a router. You can plug it in, press the reset button, and get a working device with a standard password and IP scheme. You don't need to know the internals to get a baseline that works. In Rust, Default is that reset button. It's a contract: "If you ask me for a default value, I'll give you an instance that respects the type's invariants, even if it's not fully configured for your specific needs."

Deriving the easy path

Most of the time, you don't write Default by hand. You ask the compiler to generate it. If every field in your struct already implements Default, you can slap #[derive(Default)] on the struct and you're done.

#[derive(Default)]
struct Settings {
    // u32 implements Default, returning 0.
    timeout: u32,
    // bool implements Default, returning false.
    verbose: bool,
    // String implements Default, returning an empty string.
    output_path: String,
}

fn main() {
    // The compiler generates the impl.
    // This creates Settings { timeout: 0, verbose: false, output_path: "" }
    let settings = Settings::default();
}

When you derive Default, the compiler checks every field recursively. If a field is a struct, the compiler checks that struct's fields. The chain stops when it hits a primitive or a type that implements Default. Common defaults include 0 for integers, false for booleans, "" for strings, and [] for vectors. If any field fails to implement Default, the derive macro fails and you have to implement it manually.

Derive when you can. The compiler writes bug-free boilerplate faster than you can type.

Writing Default by hand

Deriving works when the compiler-generated baseline matches your needs. Often it doesn't. A default timeout of 0 might mean "no timeout" in your API, which crashes the client. A default username of "" might break authentication logic. When the baseline matters, you write the implementation yourself.

use std::default::Default;

struct User {
    username: String,
    active: bool,
    role: String,
}

impl Default for User {
    fn default() -> Self {
        User {
            // "guest" is a safe placeholder that won't break auth checks.
            username: String::from("guest"),
            // New users start active by policy.
            active: true,
            // Default role is restricted.
            role: String::from("viewer"),
        }
    }
}

The community expects Default::default() to be cheap. It shouldn't allocate massive buffers, perform I/O, or do heavy computation. It's a baseline, not a factory. Also, default() must never panic. If you can't provide a safe default without panicking, your type probably shouldn't implement Default. A default value that breaks your invariants is worse than no default at all.

Enums and the #[default] attribute

Enums can implement Default too. Usually, one variant represents the "unset" or "initial" state. Rust provides a convenient attribute to mark the default variant, so you don't need a manual impl.

#[derive(Default)]
enum Status {
    Pending,
    // This variant is returned by Status::default().
    #[default]
    Active,
    Closed,
}

fn main() {
    // Returns Status::Active
    let status = Status::default();
}

The #[default] attribute must appear on exactly one variant. If you omit it, the compiler tries to derive based on the first variant, but relying on field order is fragile. Always mark the default explicitly. It documents intent and prevents bugs when you reorder variants later.

Default versus new()

You'll see types like Vec and String offer both new() and default(). Vec::new() and Vec::default() do the same thing: they create an empty vector. Why have both?

new() is just a method. It's not a trait. You can't use it in generic code. If you write a function that needs to create a value of some type T, you can't call T::new() because the compiler doesn't know if T has a new method. Default is a trait, so you can bound your generic on it.

// This compiles. T must implement Default.
fn create_placeholder<T: Default>() -> T {
    T::default()
}

// This does not compile. T might not have a new method.
// fn create_new<T>() -> T {
//     T::new()
// }

Default is the standard interface for "give me a value." new() is an arbitrary convention that some types follow. When you're writing libraries or generic code, Default is the escape hatch. When you don't know the type, default() is the only way to conjure a value from thin air.

Pitfalls and compiler errors

The most common hit is trying to derive Default on a struct that contains a field which doesn't implement Default. The compiler rejects this with an error like "the trait Default is not implemented for MyCustomType". You can't derive Default if one field is a black box. You have to implement it manually and provide a value for that field.

struct NoDefault {
    value: u32,
}

struct Container {
    // NoDefault does not implement Default.
    inner: NoDefault,
}

// This fails to compile.
// #[derive(Default)]
// struct Container { ... }

If you write a generic function that requires T: Default, and you pass a type that doesn't implement it, you get E0277 (trait bound not satisfied). This happens often with collections or generic builders. The fix is either to implement Default for the missing type or to relax the bound if you don't actually need a default value.

Another pitfall is confusing Default with Copy or Clone. Default creates a new instance. It doesn't copy an existing one. Calling default() allocates memory for heap types like String or Vec. If you need a copy, use clone(). If you need a copy and the type implements Copy, the compiler handles it automatically.

When to use Default

Use #[derive(Default)] when every field in your struct already implements Default and the compiler-generated baseline matches your needs. Use manual impl Default when you need custom baseline values, like a default username or a pre-allocated buffer, or when a field type lacks Default. Use T::default() in generic code when you need a placeholder value for a type parameter bounded by Default. Use Default::default() instead of explicit constructors like vec![] or String::new() when the intent is "empty state" and you want the code to remain flexible if the type changes.

Where to go next