10.6Associated functions and constructors
A method needs an instance to operate on, which raises a chicken-and-egg question: how do you make the first one? In many languages the answer is a constructor, a special language feature with its own rules. Rust doesn't have constructors. It has something simpler that you already understand: ordinary functions that live inside an impl block but don't take self.
Associated functions
A function defined in an impl block that has no self parameter is an associated function. It's associated with the type but doesn't operate on a particular instance, so you call it on the type with ::, not on a value with .:
struct Rectangle {
width: f64,
height: f64,
}
impl Rectangle {
fn square(side: f64) -> Rectangle {
Rectangle { width: side, height: side }
}
}
fn main() {
let sq = Rectangle::square(5.0);
println!("{} x {}", sq.width, sq.height);
}5 x 5
Rectangle::square(5.0) reads "call the square function that belongs to Rectangle." You've used this syntax for ages without a name for it. String::from("hi") and String::new() are associated functions on String; so is rand::random_range in spirit. The :: is the same path separator that joins a type to the things defined on it.
The line between a method and an associated function is exactly the self parameter. Has self: it's a method, called with value.method(). No self: it's an associated function, called with Type::function(). Both live in impl blocks; both belong to the type.
::new is a convention, not a keyword
The most common associated function builds and returns an instance, and Rust's convention is to call it new:
impl Rectangle {
fn new(width: f64, height: f64) -> Rectangle {
Rectangle { width, height }
}
}
fn main() {
let r = Rectangle::new(4.0, 2.5);
println!("area: {}", r.width * r.height);
}area: 10
Here's the thing worth internalizing: new is not special. It is not a keyword, the compiler has never heard of it, and nothing forces it to return Self or to take any particular arguments. It's a plain associated function that programmers agreed to name new when its job is "make a fresh instance." You could call it make or build and it would work identically; you call it new so that every other Rust programmer knows what it does at a glance.
This is the chapter's biggest entry in the C++ bloat ledger. Constructors in C++ are a language feature with their own chapter-sized rulebook: initialization lists, default constructors, copy constructors, converting constructors, delegating constructors, and the order they all run in. Rust deletes the entire feature. A constructor is just a function you write, named by convention, and you've known how to write functions since chapter 2.
Best practice
Name the primary constructor new. If a type has several ways to be built, name the others descriptively: Rectangle::square, String::from, Vec::with_capacity. The with_ and from_ prefixes are common conventions for "build one, given this." Readers lean on these names heavily, so follow them.
Default values
Sometimes "a fresh instance" means "all fields at their natural starting value." Rather than write a new that fills in zeros, Rust offers a standard piece of behavior for exactly this, called Default, which you opt into with a derive (the same #[derive(...)] machinery the next lesson covers for printing):
#[derive(Default)]
struct Settings {
volume: u32,
muted: bool,
brightness: u32,
}
fn main() {
let s = Settings::default();
println!("volume {}, muted {}, brightness {}", s.volume, s.muted, s.brightness);
}volume 0, muted false, brightness 0
#[derive(Default)] asks the compiler to write a default() associated function that fills every field with its default: 0 for numbers, false for bool, an empty String for String. This pairs beautifully with struct update syntax from lesson 10.3: start from the default, override only what you care about.
let quiet = Settings { muted: true, ..Default::default() };
That reads "muted, and everything else at its default," which is often exactly the instance you want. We'll meet derives properly next lesson and the full story of what Default and derive really are in chapter 16; for now, treat #[derive(Default)] as "give me a ::default() that zeroes everything out."
Key insight
Rust has no built-in "construct an object" step, so there's no half-built instance to worry about, no constructor that can throw partway through, and no rules about field initialization order to memorize. An instance springs into existence fully formed the moment you write Type { ... }, and ::new is just a function that does that for you and hands it back. The lesson 1.4 ban on uninitialized values holds all the way up to your own types.
Quiz time
Question #1
Add an associated function Point::origin() that returns a Point { x: 0.0, y: 0.0 }, and call it.
Show solution
struct Point {
x: f64,
y: f64,
}
impl Point {
fn origin() -> Point {
Point { x: 0.0, y: 0.0 }
}
}
fn main() {
let o = Point::origin();
println!("({}, {})", o.x, o.y);
}
It takes no self, so it's an associated function, called Point::origin() with ::. Prints (0, 0).
Question #2
What distinguishes an associated function from a method, and how does the call site differ?
Show solution
A method takes self (in some form) as its first parameter and is called on an instance with a dot: r.area(). An associated function has no self and is called on the type with a path: Rectangle::new(...). Both are defined in impl blocks.
Question #3
Is new a Rust keyword? What would happen if you named your constructor create instead?
Show solution
No, new is not a keyword; it's a plain associated function named by convention. Naming it create would compile and work identically. You name it new so other Rust programmers recognize the "make a fresh instance" function instantly. The convention is for humans, not the compiler.
You can now build instances and give them behavior. But try to println!("{}", rectangle) and the compiler stops you. The next lesson explains why, and how #[derive(Debug)] makes any struct printable for the price of one line.