Structs

Constructors by Convention

Rust does not have a dedicated constructor keyword or implicit constructor syntax like new ClassName() in C++ or Java. Instead, structs are constructed directly via struct literal syntax (Type { field: value }) or through associated functions named by convention, starting with Type::new(...).

Standard Rust Constructor Naming Conventions

Following the Rust API Guidelines (C-CTOR), standard library and ecosystem crates use consistent names for constructor functions so callers immediately know their cost and behavior:

Constructor Name Signature Shape Convention and Purpose
new(...) fn new(...) -> Self The primary, natural constructor for a type
with_*(...) fn with_capacity(cap: usize) -> Self Secondary constructor that configures an auxiliary resource or pre-allocation
from_*(...) fn from_utf8(v: Vec<u8>) -> Result<Self, E> Constructor that converts or validates specific input components
try_new(...) fn try_new(...) -> Result<Self, Error> Fallible primary constructor when invariant validation can fail
Default::default() fn default() -> Self Zero-argument trait constructor for types with a sensible default value

Primary and Secondary Constructors (new and with_*)

When a type has a clear zero-argument default state, implement both Type::new() and the Default trait:

#[derive(Debug, Default)]
pub struct RingBuffer<T> {
    storage: Vec<T>,
    limit: usize,
}

impl<T> RingBuffer<T> {
    // Primary constructor
    pub fn new() -> Self {
        Self::with_limit(64)
    }

    // Secondary constructor specifying a custom limit and pre-allocating memory
    pub fn with_limit(limit: usize) -> Self {
        Self {
            storage: Vec::with_capacity(limit),
            limit,
        }
    }
}

fn main() {
    let default_buf: RingBuffer<i32> = RingBuffer::new();
    let custom_buf: RingBuffer<i32> = RingBuffer::with_limit(8);

    println!(
        "default_buf: limit = {}, capacity = {}",
        default_buf.limit,
        default_buf.storage.capacity()
    );
    println!(
        "custom_buf:  limit = {}, capacity = {}",
        custom_buf.limit,
        custom_buf.storage.capacity()
    );
}
default_buf: limit = 64, capacity = 64
custom_buf:  limit = 8, capacity = 8

Fallible Constructors Enforcing Type Invariants (try_new)

Because struct literals (Port { number: 0 }) bypass validation if fields are pub, keeping fields private and providing a fallible constructor (fn new(...) -> Result<Self, Error> or Option<Self>) guarantees that every existing instance of the struct satisfies its domain invariants:

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NonZeroPort(u16); // Private inner field (`0` is forbidden)

impl NonZeroPort {
    pub fn new(port: u16) -> Option<Self> {
        if port == 0 {
            None
        } else {
            Some(Self(port))
        }
    }

    pub fn get(self) -> u16 {
        self.0
    }
}

fn main() {
    println!("Port 0:   {:?}", NonZeroPort::new(0));
    println!("Port 443: {:?}", NonZeroPort::new(443));
}
Port 0:   None
Port 443: Some(NonZeroPort(443))

Implementing Default Alongside new()

Deriving #[derive(Default)] initializes every field with its own Default::default() value (0 for numbers, false for bool, "" for String, empty Vec). When a struct's sensible default differs from all-zeros, implement Default manually and delegate Self::new() to Self::default() (or vice versa):

#[derive(Debug)]
pub struct HttpClientConfig {
    pub max_retries: u32,
    pub timeout_secs: u64,
    pub follow_redirects: bool,
}

impl HttpClientConfig {
    pub fn new() -> Self {
        Self {
            max_retries: 3,
            timeout_secs: 30,
            follow_redirects: true,
        }
    }
}

impl Default for HttpClientConfig {
    fn default() -> Self {
        Self::new()
    }
}

fn main() {
    // Callers can override specific fields while falling back to `Default::default()`:
    let cfg = HttpClientConfig {
        timeout_secs: 5,
        ..Default::default()
    };
    println!("{cfg:?}");
}
HttpClientConfig { max_retries: 3, timeout_secs: 5, follow_redirects: true }