skip to content

Some languages give object creation dedicated syntax that always yields a fresh instance of exactly the named type; others build objects with ordinary functions. Compare the two using Go, Rust, Python and Java, and say what the dedicated syntax makes impossible.

level: middleimportance: should knowfreq 45%

answer

  1. creation as syntax vs creation as a function
  2. new T pinned to T: no cache, no subtype, no failure value
  3. Go: zero value must be usable; NewT is convention only
  4. Rust: Result<Self,E> + private fields = enforced door
  5. Python __new__ may return an existing or foreign instance; __init__ then skipped

basics

~20 s

Java's and C++'s new T is pinned to T: it cannot return a cached instance, a subtype, or a failure value, so anything interesting moves into a separate function. Go, Rust and Python's new have no such rule -- construction there is a normal function returning a value, so those abilities need no pattern name.

solid answer

~60 s

The familiar advice to prefer a factory is not universal wisdom; it is a workaround for languages where creation is privileged syntax. - **Java / C++**: `new T` always allocates a new T and can report failure only by throwing -- hence the separate static creation method. - **Go**: no constructors at all, and the doctrine is that the **zero value must be usable** (`sync.Mutex`, `bytes.Buffer` work at zero). A `NewT` returning `(T, error)` is a convention only -- a caller can still write `var t T` and skip it, so the compiler enforces nothing. - **Rust**: `T::new` is a plain associated function, and fallible construction returns `Result<Self, E>` -- "valid or fail" lives in the signature with no exceptions. Unlike Go it is enforceable: private fields make the struct literal unusable outside the module. - **Python**: allocation (`__new__`) is split from initialization (`__init__`). `__new__` may return an interned or foreign-class instance, in which case `__init__` never runs -- so caching sits inside the constructor. - **Objective-C**: class clusters return a private subclass from `init` outright.

code

go · 10 lines
go
package store

type Cache struct{ items map[string]string }

func NewCache() *Cache { return &Cache{items: map[string]string{}} }

// elsewhere:
var c store.Cache        // legal: items is nil
c.items["k"] = "v"       // (inside the package) panics on a nil map
// Go's answer: make the zero value work, because you cannot forbid it.

go deeper

for a junior

Know that construction must leave the object valid, and that some languages use dedicated syntax for it while others just call a function.

for a middle

Name what dedicated syntax forbids -- returning a cached instance, a subtype, or a failure value -- and give one language that has no such restriction.

for a senior

Discuss enforcement: Rust's privacy boundary makes the creation function unavoidable, Go's does not, so Go pushes the invariant into a usable zero value. Choose accordingly when designing a package API.

for a principal

Frame invariant establishment as a function with a contract, independent of syntax, and evaluate a language by how much of that contract it can express and enforce -- failure in the type, uniqueness of the door, and whether an invalid state is representable at all.

## Two ways to create an object There is a family of languages in which creation is **syntax**. `new Point(1, 2)` in Java, C# or C++ is not a call you can substitute; the language guarantees it allocates a fresh object whose class is exactly `Point`, runs the constructor body, and yields that object or throws. The guarantee is genuinely useful -- you always know what you got -- but it removes four abilities at once: returning an already-existing instance, returning an instance of a different (sub)type, returning a value that represents failure rather than throwing, and giving different creation paths different names when their parameter lists collide. All four have to move into a separate function outside the syntax. There is another family in which creation is **just a function**. Nothing is privileged, so none of the four abilities was ever taken away, and no pattern name is needed to get them back. ## Go: no constructors, so the zero value carries the burden Go has no constructor concept. `var b bytes.Buffer` produces a value with every field zeroed, and it is idiomatic that such a value is immediately usable -- a zero `sync.Mutex` is an unlocked mutex, a zero `bytes.Buffer` is an empty buffer. The consequence is a doctrine: **make the zero value meaningful**, because you cannot stop anyone from creating one. When an invariant genuinely cannot hold at zero, the package exports `NewThing(...) (*Thing, error)`, but this is convention with no teeth: unexported fields prevent an outsider from writing a *populated* composite literal, yet `var t Thing` remains legal everywhere. Go's answer to "how do you guarantee the invariant?" is largely "design so the zero state is a legal state." ## Rust: a plain function, but the boundary is enforced Rust also has no constructor syntax; `T::new()` is an associated function by convention only. Two things make this stronger than Go's version. First, fallible construction is typed: `fn parse(s: &str) -> Result<Config, ConfigError>` says in the signature that construction may fail, with no exception mechanism and no possibility of a caller silently ignoring the failure, since the `Result` must be handled. Second, if the struct has private fields, the struct-literal form simply cannot be written outside the defining module, and there is no zero-value fallback -- a value cannot exist until every field has been given one. So the creation function is the only door, and the compiler holds it. The price is that nothing is automatic: no chaining into a base type's initialization, and multi-parameter creation typically grows into an explicit builder. ## Python: allocation and initialization are two hooks Python splits the job. `__new__` allocates and returns the object; `__init__` initializes an object it is given. Because `__new__` merely *returns* something, it can return an existing instance -- interning small values, enforcing a singleton, handing back a cached parse -- and if what it returns is not an instance of the class being constructed, `__init__` is not called at all. So the caching and subtype tricks that require a separate static method elsewhere live inside the ordinary call syntax here. The cost is two hooks that can disagree, and a well-known trap: to customize an immutable type you must override `__new__`, because by the time `__init__` runs the value is already fixed. ## Objective-C and Swift Objective-C separates `alloc` from `init` and permits `init` to return a *different* object; Foundation's class clusters exploit this, so `[[NSString alloc] initWithFormat:...]` hands back a private subclass. Swift keeps dedicated `init` syntax but restores the missing ability directly in the language: a **failable initializer** `init?` returns an Optional, so "valid object or nothing" is expressible without exceptions and without leaving the constructor. ## What to take from the comparison The invariant-establishing step is a *function* -- "produce a valid value of this type or do not produce one". Some languages decorate that function with syntax and a guarantee about its return type, which is a real benefit and a real restriction; other languages leave it undecorated. When you next reach for a creation method with a descriptive name, be able to say which of the four lost abilities you are recovering. And when you move to a language without constructors, remember that the enforcement question moves with you: Rust's privacy boundary enforces the door, Go's does not, which is why Go pushes the invariant into the zero value instead.

  • Go cannot force callers through NewThing. How do well-designed Go packages cope?
    They make the zero value a legal, useful state -- an unlocked mutex, an empty buffer, a disabled logger -- so skipping the constructor produces something correct rather than something broken. Where that is impossible, they lazily initialize on first use behind an accessor, or return an interface from the constructor so the concrete zero value is not nameable outside the package. The design rule is to remove the invalid state rather than to guard the door.
  • In Python, when must you override __new__ instead of __init__?
    When the object must be finished before __init__ could run, or when creation must not produce a fresh object. Subclassing an immutable type such as tuple or str requires __new__ because the value is fixed at allocation. Interning, singletons and caches also belong in __new__, since only it can return an existing instance -- and if it returns an object that is not an instance of the class, __init__ is skipped entirely.
  • Swift kept dedicated init syntax but added init?. What problem does that solve that a separate creation function would also solve?
    It lets construction report failure as a value rather than by throwing, without giving up the constructor's guarantees or forcing a differently named function. The caller gets an Optional and must unwrap it, so the failure is visible in the type. It is the language absorbing one of the four abilities that privileged creation syntax normally removes, instead of leaving it to a convention.

saying these in an interview costs you the question

  • Presenting the separate creation method as universal best practice, without noticing it exists to work around a language restriction.
  • Claiming Go's NewX functions are enforced by the compiler.
  • Thinking Python's __init__ can return a different object -- it returns nothing; only __new__ chooses the object.
  • Assuming every language reports construction failure by throwing; Rust returns Result and Swift's init? returns nil.
  • Saying Rust has constructors as a language feature rather than an associated-function convention.

context