Associated Functions
An associated function is a function defined inside an impl block that is tied to a type but does not take self as its first parameter. Because it doesn’t operate on an existing instance, you call it directly on the type using ::, not on a value using .. The most common use of associated functions is writing constructors — functions like String::from(...) or Vec::new() that build a new value of a type from scratch.
Overview / How it works
Rust has no class keyword and no constructor syntax like new Rectangle(30, 50). Instead, you attach functions to a type inside an impl (implementation) block, and those functions come in two flavors depending on whether their first parameter is self:
- A method takes
self,&self, or&mut selfas its first parameter. It operates on an existing instance and is called with dot syntax:instance.method(). - An associated function has no
selfparameter at all. It is still namespaced to the type, but it isn’t attached to any particular instance, so it’s called with path syntax:Type::function().
Think of the type name plus impl block as a namespace. Everything you write inside impl Rectangle { ... } is reachable through the path Rectangle::something. If something happens to take &self as its first parameter, Rust also lets you call it with the shorthand instance.something() — that’s just syntactic sugar for Rectangle::something(&instance). But if something has no self parameter, there is no instance to call it on, so the dot-syntax shorthand simply doesn’t exist for it; the Type::function() path is the only way to call it.
This is exactly how a constructor works: before you have a value, there is no instance to call a method on, so building one has to be an associated function. By convention (not a compiler rule — new is not a reserved word), most types provide a constructor named new, and many provide additional named constructors for common cases, like Rectangle::square(size) alongside Rectangle::new(width, height). Rust doesn’t support function overloading, so you can’t have two functions both named new with different parameter lists — instead you give each constructor its own descriptive name.
Associated functions aren’t only for constructors, though. Any utility function that logically belongs to a type but doesn’t need an existing instance — a parser, a default-value builder, a validator — can live in the same impl block. Enums use associated functions too: an enum’s impl block can hold a constructor that returns one particular variant, exactly like a struct.
Syntax
impl TypeName {
fn function_name(param1: Type1, param2: Type2) -> ReturnType {
// build and return a value; no `self` parameter here
}
}
// called on the type itself, not on an instance:
let value = TypeName::function_name(arg1, arg2);
| Part | Meaning |
|---|---|
impl TypeName |
Opens a block of functions associated with TypeName. A type can have multiple impl blocks. |
fn function_name(...) |
No self, &self, or &mut self parameter — this is what makes it an associated function instead of a method. |
-> ReturnType |
Constructors typically return Self, which means “whatever type this impl block is for.” |
Self { ... } |
Inside the function body, Self can stand in for the type name when building a value, saving you from repeating it. |
TypeName::function_name(...) |
The call syntax — always path syntax (::), never dot syntax, because there is no instance yet. |
Examples
Example 1: A basic constructor
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn new(width: u32, height: u32) -> Rectangle {
Rectangle { width, height }
}
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect = Rectangle::new(30, 50);
println!("Area: {}", rect.area());
}
Output:
Area: 1500
new has no self parameter, so it’s called as Rectangle::new(30, 50), not rect.new(...) — there is no rect yet at the point we call it. Once the Rectangle value exists, area (which does take &self) is called with the familiar dot syntax.
Example 2: Multiple named constructors with Self
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
fn square(size: u32) -> Self {
Self::new(size, size)
}
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect = Rectangle::new(10, 20);
let sq = Rectangle::square(15);
println!("Rectangle area: {}", rect.area());
println!("Square area: {}", sq.area());
}
Output:
Rectangle area: 200
Square area: 225
Here Self is used both as the return type and inside the function bodies instead of repeating Rectangle. square is a second constructor that expresses a special case (equal width and height) in terms of the general one, Self::new(size, size) — a common and idiomatic pattern since Rust has no constructor overloading.
Example 3: Associated functions on an enum and a struct together
enum TrafficLight {
Red,
Yellow,
Green,
}
impl TrafficLight {
fn new() -> Self {
TrafficLight::Red
}
fn next(&self) -> Self {
match self {
TrafficLight::Red => TrafficLight::Green,
TrafficLight::Green => TrafficLight::Yellow,
TrafficLight::Yellow => TrafficLight::Red,
}
}
fn describe(&self) -> &str {
match self {
TrafficLight::Red => "red",
TrafficLight::Yellow => "yellow",
TrafficLight::Green => "green",
}
}
}
struct User {
name: String,
age: u32,
}
impl User {
fn from_name(name: String, age: u32) -> Self {
User { name, age }
}
fn greet(&self) -> String {
format!("Hi, I'm {} and I'm {} years old.", self.name, self.age)
}
}
fn main() {
let light = TrafficLight::new();
println!("Light is {}", light.describe());
let light = light.next();
println!("Light is now {}", light.describe());
let user = User::from_name(String::from("Ava"), 29);
println!("{}", user.greet());
}
Output:
Light is red
Light is now green
Hi, I'm Ava and I'm 29 years old.
TrafficLight::new() is an associated function returning a specific starting variant, and User::from_name is a constructor that takes ownership of a String (it needs to own the data it stores in the struct, so it takes String rather than &str). Both are called with :: because neither has an instance to act on yet.
How it works step by step
When the compiler sees Rectangle::new(30, 50), here’s what happens conceptually:
- It looks up the type
Rectangleand searches itsimplblock(s) for a function namednew. - It checks that
new‘s parameter list matches the arguments given (30and50both coerce tou32) — this is ordinary type checking, no different from any other function call. - Because
newhas noselfparameter, the compiler does not require (or allow) an instance before the::. Contrast this withrect.area(), where the compiler inserts a reference torectas the hidden first argument, effectively rewriting it toRectangle::area(&rect). - Inside the function body,
Rectangle { width, height }(orSelf { width, height }) constructs a new value on the stack and moves ownership of it out as the return value. There’s no hidden allocation or magic — it’s a plain struct literal like any other, just packaged behind a friendlier name than writing out the literal at every call site. - The returned value becomes owned by whatever binds it, e.g.
let rect = Rectangle::new(30, 50);givesrectsole ownership, following the same move semantics as any other value.
The upshot: associated functions add no runtime behavior beyond an ordinary function call. Their entire purpose is organizational — grouping constructors and type-level utilities under the type’s own name instead of scattering free functions with names like new_rectangle across the module.
Common Mistakes
Mistake 1: Calling an associated function with dot syntax
Because new takes no self, there’s no instance to call it on — trying to call it like a method fails to compile:
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
}
fn main() {
let rect = Rectangle::new(10, 20);
// error[E0599]: no method named `new` found for struct `Rectangle`
// `new` is an associated function, so it must be called as `Rectangle::new(...)`
let rect2 = rect.new(5, 5);
}
The fix is to call it with the type name instead of an instance:
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
}
fn main() {
let _rect = Rectangle::new(10, 20);
let rect2 = Rectangle::new(5, 5);
println!("{} {}", rect2.width, rect2.height);
}
Output:
5 5
Mistake 2: Defining two associated functions with the same name
Rust has no function overloading, so giving two functions in the same type’s implementation the same name is a compile error, even across separate impl blocks for the same type:
struct Point {
x: i32,
y: i32,
}
impl Point {
fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
}
impl Point {
// error[E0592]: duplicate definitions with name `new`
fn new() -> Self {
Self { x: 0, y: 0 }
}
}
fn main() {}
The fix is to give each constructor a distinct, descriptive name, such as new and origin.
Mistake 3: Forgetting that a constructor can move its argument
A constructor that stores a String (or any non-Copy type) takes ownership of it. Using the original variable afterward is a move-after-use error:
struct User {
name: String,
}
impl User {
fn new(name: String) -> Self {
User { name }
}
}
fn main() {
let name = String::from("Ava");
let user = User::new(name);
// error[E0382]: borrow of moved value: `name`
// `name` was moved into `User::new` and is no longer valid here
println!("{}", name);
}
If you still need the original value afterward, clone it before passing it in (cloning a String allocates a second, independent heap buffer):
struct User {
name: String,
}
impl User {
fn new(name: String) -> Self {
User { name }
}
}
fn main() {
let name = String::from("Ava");
let user = User::new(name.clone());
println!("Original: {}", name);
println!("User name: {}", user.name);
}
Output:
Original: Ava
User name: Ava
Best Practices
- Name your primary constructor
newby convention — readers of any Rust code will recognize it immediately, even though the compiler attaches no special meaning to the name. - Return
Selfinstead of repeating the type name; it stays correct automatically if you ever rename the type. - Give alternate constructors distinct, descriptive names (
from_name,square,with_capacity) instead of trying to overloadnew, since Rust doesn’t support overloading. - Prefer building one constructor in terms of another (like
squarecallingnew) so validation or defaults only live in one place. - Take ownership (
String,Vec<T>, etc.) in a constructor only when the struct genuinely needs to own that data long-term; otherwise consider borrowing. - Split unrelated groups of associated functions and methods across multiple
implblocks for the same type when it improves readability — Rust allows as manyimplblocks per type as you like. - Remember associated functions apply to enums too — use them for constructors that pick a starting variant.
Practice Exercises
- Define a
Pointstruct withxandyfields (bothi32). Write an associated functionPoint::origin()that returns aPointat(0, 0), and a methoddescribe(&self)that returns aStringlike"(0, 0)". Print the result of describing the origin. - Define a
Circlestruct with anf64radius field. WriteCircle::new(radius: f64) -> Selfand a methodarea(&self) -> f64using the formula3.14159 * radius * radius. Construct a circle with radius2.0and print its area (expected output:Area: 12.56636). - Add a second constructor
Circle::unit() -> Selfthat returns a circle of radius1.0by callingCircle::new(1.0)internally, rather than duplicating the struct literal.
Summary
- An associated function is defined in an
implblock but takes noselfparameter, so it isn’t tied to an existing instance. - Associated functions are called with path syntax,
Type::function(...), never with dot syntax on an instance. - The most common associated functions are constructors, conventionally named
new, that build and return aSelfvalue. - Rust has no function overloading, so alternate constructors need distinct names, like
squareorfrom_name. - A constructor that takes an owned type like
Stringtakes ownership of the argument — the caller’s original binding becomes invalid unless it’s cloned first. - Methods (which take
&self/&mut self/self) and associated functions can live side by side in the sameimplblock; only the presence of aselfparameter distinguishes them.
