🎓 A1Learner
🔍

Rust Constructors & Methods

A constructor is a function on a type that builds a new value of that type. Vec::new() and String::from("hi") are constructors.

Constructors live on the type

The main constructor is an inherent method called new. There is no constructor keyword and no free function.

impl Counter {
    pub fn new() -> Counter { Counter { value: 0 } }
}
let counter = Counter::new();

Second ways to build say how they differ

let text = String::with_capacity(100);  // with_: new, but pre-sized
let n = u32::from_str_radix("ff", 16);  // from_: the radix says how to read it

A clear receiver makes it a method

If one argument is obviously the thing being worked on, the function belongs in that type's impl block.

impl Counter {
    fn tick(&mut self) { self.value += 1; }
}
// reads as counter.tick(), not tick(&mut counter)

Return values, not out-parameters

Rust functions return their results. A function that fills a caller-supplied value to report several answers is a C habit. Return a tuple or a small struct instead.

fn min_max(list: &[i32]) -> (i32, i32) {
    (*list.iter().min().unwrap(), *list.iter().max().unwrap())
}

Conversions live on the specific type

utf8 is a fact about str, so the conversion is str::from_utf8 — not a method invented on byte slices.

let text = str::from_utf8(&bytes);   // the specific type owns the conversion

Example

struct Counter {
    value: u32,
}
impl Counter {
    fn new() -> Counter {
        Counter { value: 0 }
    }
    fn with_start(start: u32) -> Counter {
        Counter { value: start }
    }
    fn tick(&mut self) {
        self.value += 1;
    }
    fn value(&self) -> u32 {
        self.value
    }
}
impl From<u32> for Counter {
    fn from(start: u32) -> Counter {
        Counter::with_start(start)
    }
}
fn main() {
    let mut a = Counter::new();
    a.tick();
    a.tick();
    let mut b = Counter::with_start(10);
    b.tick();
    let c = Counter::from(41);
    println!("a = {}", a.value());
    println!("b = {}", b.value());
    println!("c = {}", c.value());
}

Key points

  • A constructor is a function on the type itself: it builds and hands back a new value.
  • A type often has both new and Default.
  • When it has both, they must build the same value.
  • Secondary constructors end with what makes them different.
  • Regex::new versus TcpStream::connect shows another option.
  • The suffix can also be a domain word, like open or connect.
  • A from_ constructor may do things From cannot.
  • It may be unsafe.
  • It may take extra input.
  • from_str_radix taking the radix is one example.
  • Smart pointers are the exception to "add helpful methods".
  • Box adds no inherent methods of its own.
  • So a method call on a Box clearly belongs to the value inside it.
  • Only smart pointers implement Deref and DerefMut.
  • The compiler uses them silently while resolving method calls.
  • So a surprising Deref hides what code really runs.
  • Implement Add only for something that truly adds.
  • A surprising overload is worse than a plainly named method.

In this example, Counter offers three ways in. new() starts at 0, and two ticks bring a to 2. with_start(10) is the secondary constructor. The with_ suffix says it is still construction, just seeded. And one tick brings b to 11. The From impl is a third door. It simply delegates to with_start. So every path agrees on what a start value means. And c is 41. tick and value are ordinary methods, because the counter is the clear receiver of both.

Note

An overloaded operator must behave the way its symbol suggests.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Which name fits a second constructor that builds a buffer with room for 100 items reserved up front?