ProgrammingRust Backend Developer

How does the constructor and factory method system work in Rust? What object creation patterns are applied, how is invariance ensured and how are structures initialized?

Pass interviews with Hintsage AI assistant

Answer.

In Rust, there are no traditional constructors as in C++ or Java, but object types are usually created using associated functions (often named new) and so-called factory methods. This is related to the language's history, where particular attention is paid to safety and the explicitness of initialization: only an explicitly written and called function is responsible for the correct initialization of each field of the structure.

Background

Initially, Rust allowed direct assignment of all fields during structure initialization (the so-called "struct literal" syntax). However, to ensure invariance, hide details, and implement additional checks, it is common to use factory methods (impl SomeStruct { fn new(...) -> Self { ... } }) or even generalization through templates (builder pattern).

Problem

The main tasks are to prevent partially initialized objects and make it impossible to use structures with invalid states. This is especially critical for complex structures (for example, those related to resources — files, sockets, etc.), where manually initializing all fields is fraught with errors.

Solution

In Rust, it is recommended to create factory methods that return a fully initialized object, perform validation if necessary, and hide the instantiation details.

Code example:

struct User { username: String, age: u8, } impl User { pub fn new(username: String, age: u8) -> Option<Self> { if age >= 18 { Some(Self { username, age }) } else { None } } } fn main() { let user = User::new("Alice".to_string(), 20); // user: Option<User>, safely handle the error }

Key features:

  • No automatic constructors as in some other languages, but there is an implementation via associated functions (fn new).
  • Factory methods allow implementing integrity checks and hiding details of the internal implementation.
  • The builder pattern is effectively supported when many optional parameters and phased initialization are necessary.

Trick questions.

Can private fields be made in a structure to prevent instances from being created directly outside the module?

Yes, if all fields of the structure are made private and only public factory methods are provided, the structure cannot be initialized directly outside its module.

Does the factory method always have to be named new?

No, this is a convention, but not an obligation. Different strategies for initialization use names like "with_capacity", "from_config", "from_env", and so on.

Can there be private constructors?

Yes, if the associated function is declared as fn new(...) -> Self without the pub modifier, it cannot be called outside this module. This allows, for example, to implement a singleton, enforce a factory, or hidden initialization.

Typical mistakes and anti-patterns

  • Creating a structure with public fields that allow bypassing invariants or obtaining an object in an invalid state.
  • Not using factory methods for complex structures with external resources.
  • Confusion between a constructor and a validator method: for example, returning Result/Option, although refusal in initialization means logic at a different level.

Real-life example

Negative case

Direct use of a structure with open fields, without a factory method:

struct Connection { fd: i32, timeout: u64, } let c = Connection { fd: -1, timeout: 10 }; // fd: -1 is invalid for a descriptor!

Pros:

  • Speed of prototyping.
  • Less code.

Cons:

  • No guarantee that the object will not be in an invalid state.
  • Errors occur only during execution.

Positive case

Using private fields and a factory method:

pub struct Connection { fd: i32, timeout: u64, } impl Connection { pub fn new(fd: i32, timeout: u64) -> Option<Self> { if fd >= 0 { Some(Self { fd, timeout }) } else { None } } }

Pros:

  • The compiler won’t allow creating an invalid object.
  • Explicit control over checks.

Cons:

  • Slightly increases the amount of code.
  • Not all simple structures require such a pattern.