Methods
Data · Lesson 21
Methods go in an impl block. The first parameter says how the method uses the struct: read it, change it, or take it.
Coming from GoAn impl block holds the methods, and &mut self is your pointer receiver. A Go value receiver gets a copy; in Rust, plain self takes the value itself, and the caller can't use it again unless the type is Copy. &self is a pointer receiver that can only read, which Go can't say. Counter::new() is a naming habit, like NewCounter().
Coming from KotlinMethods go in a separate impl block, and there is no constructor: new is an ordinary function, like a factory in a companion object. A method that changes the struct takes &mut self, and a variable that owns the struct must be let mut to call it, where Kotlin lets a val call anything.
Coming from Pythonself is written out like Python's, but its form says what the method does: &self reads, &mut self changes, plain self uses it up. There is no __init__: Counter::new() is just a function that returns a Counter.
Coming from TypeScriptthis becomes an explicit self parameter, and its form says what the method does: &self reads, &mut self changes. There is no class or new: Counter::new() is a plain function, and a variable that owns the object must be let mut to call a &mut self method.
Coming from JavaMethods live in a separate impl block, and this is written out as the first parameter, with a mode: &self reads, &mut self changes, self takes the value. There are no constructors, so Counter::new() is a plain function, and two methods of one type can't share a name. A variable that owns the object must be let mut to call a &mut self method.
Coming from C#An impl block holds the methods, and fn new() with no self is a static factory, since Rust has no constructors. The receiver says whether a method changes the struct (&mut self) or only reads it (&self), which C# classes never spell out.
Coming from CThis is your counter_bump(struct Counter *c) pattern with the call written c.bump(): &self is like const struct Counter * and &mut self like struct Counter *. Where gcc only warns about passing a const object to a non-const pointer, Rust refuses to build.
Coming from C++&self is a const member function and &mut self is a non-const one, but the default flips: values are immutable unless declared let mut. There are no constructors; Counter::new() is an ordinary associated function that returns a value.
The quick comparison
struct Counter { count: u32,}
impl Counter { fn new() -> Counter { // no self: called as Counter::new() Counter { count: 0 } }
fn get(&self) -> u32 { // &self: only reads self.count }
fn bump(&mut self) { // &mut self: changes the struct self.count += 1; }}
fn main() { let mut c = Counter::new(); c.bump(); c.bump(); println!("{}", c.get());}2
package main
import "fmt"
type Counter struct { count int}
func NewCounter() *Counter { return &Counter{}}
func (c *Counter) Get() int { return c.count}
func (c *Counter) Bump() { c.count++}
func main() { c := NewCounter() c.Bump() c.Bump() fmt.Println(c.Get())}2
class Counter { private var count = 0
fun get(): Int = count
fun bump() { count++ }}
fun main() { val c = Counter() c.bump() c.bump() println(c.get())}2
class Counter: def __init__(self) -> None: # called as Counter() self.count = 0
def get(self) -> int: return self.count
def bump(self) -> None: # any method can change self self.count += 1
c = Counter()c.bump()c.bump()print(c.get())2
class Counter { private count = 0;
get(): number { // only reads return this.count; }
bump(): void { // changes the object this.count += 1; }}
const c = new Counter();c.bump();c.bump();console.log(c.get());2
public class Main { static class Counter { private int count = 0;
int get() { return count; }
void bump() { count++; } }
public static void main(String[] args) { var c = new Counter(); c.bump(); c.bump(); System.out.println(c.get()); }}2
var c = new Counter();c.Bump();c.Bump();Console.WriteLine(c.Get());
class Counter{ private uint count; // starts at 0
public uint Get() => count;
public void Bump() => count++;}2
#include <stdio.h>
struct Counter { unsigned count;};
// C has no methods: plain functions that take the struct by pointerstruct Counter counter_new(void) { return (struct Counter){.count = 0};}
unsigned counter_get(const struct Counter *c) { // const: only reads return c->count;}
void counter_bump(struct Counter *c) { // no const: changes it c->count += 1;}
int main(void) { struct Counter c = counter_new(); counter_bump(&c); counter_bump(&c); printf("%u\n", counter_get(&c)); return 0;}2
#include <iostream>
class Counter {public: unsigned get() const { return count_; } // const: only reads void bump() { count_ += 1; } // not const: changes the object
private: unsigned count_ = 0; // a default member initializer: starts at 0};
int main() { Counter c; c.bump(); c.bump(); std::cout << c.get() << "\n";}2
What's different
&selflets the method read the struct without copying it.&mut selflets the method change it.self(no &) means the method takes the struct; after calling it the caller can't use it (unless the type isCopy), and the method may hand it back.newis not a keyword. It is just the usual name for a constructor function.- You call methods with a dot either way. Rust adds the
&or&mutfor you. - Two methods written in a type's own
implblocks can't share a name, so you can't overload by parameter list. Constructors get different names instead:new,with_capacity,from_parts. (Traits, covered later, can give one name several meanings, asFromdoes forfrom.)
Calling a changing method on a non-mut variable
Looks fine: in Go, Kotlin, Python, TypeScript, Java, C# and C it runs fine. Rust won't compile it.
Works in Go. In Rust it's just a rule.Works in Kotlin. In Rust it's just a rule.Works in Python. In Rust it's just a rule.Works in TypeScript. In Rust it's just a rule.Works in Java. In Rust it's just a rule.Works in C#. In Rust it's just a rule.Works in C. In Rust it's just a rule.C++ refuses this too. Same idea, different words.struct Counter { count: u32,}
impl Counter { fn bump(&mut self) { self.count += 1; }}
fn main() { let c = Counter { count: 0 }; c.bump(); println!("{}", c.count);}package main
import "fmt"
type Counter struct{ count int }
func (c *Counter) Bump() { c.count++ }
func main() { c := Counter{} c.Bump() fmt.Println(c.count)}1
class Counter { var count = 0 fun bump() { count++ }}
fun main() { val c = Counter() c.bump() println(c.count)}1
class Counter: def __init__(self) -> None: self.count = 0
def bump(self) -> None: self.count += 1
c = Counter()c.bump()print(c.count)1
class Counter { count = 0; bump(): void { this.count += 1; }}
const c = new Counter();c.bump();console.log(c.count);1
public class Main { static class Counter { int count = 0;
void bump() { count++; } }
public static void main(String[] args) { final var c = new Counter(); c.bump(); System.out.println(c.count); }}1
var c = new Counter();c.Bump();Console.WriteLine(c.Count);
class Counter{ public uint Count { get; private set; }
public void Bump() => Count++;}1
#include <stdio.h>
struct Counter { unsigned count;};
void counter_bump(struct Counter *c) { c->count += 1;}
int main(void) { const struct Counter c = {0}; counter_bump(&c); printf("%u\n", c.count); return 0;}main.c: In function ‘main’:
main.c:13:18: warning: passing argument 1 of ‘counter_bump’ discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers]
13 | counter_bump(&c);
| ^~
main.c:7:35: note: expected ‘struct Counter *’ but argument is of type ‘const struct Counter *’
7 | void counter_bump(struct Counter *c) {
| ~~~~~~~~~~~~~~~~^1
In C++ you have to write const to get this error; in Rust you get it by writing nothing. bump is not a const method, so it can't be called on a const Counter. (-fpermissive in the message is the switch that would turn this error into a warning; without it, it is an error.)
#include <iostream>
struct Counter { unsigned count = 0; void bump() { count += 1; }};
int main() { const Counter c; c.bump(); std::cout << c.count << "\n";}main.cpp: In function ‘int main()’:
main.cpp:10:11: error: passing ‘const Counter’ as ‘this’ argument discards qualifiers [-fpermissive]
10 | c.bump();
| ~~~~~~^~
main.cpp:5:10: note: in call to ‘void Counter::bump()’
5 | void bump() { count += 1; }
| ^~~~What does the compiler say? Guess first, then open.
error[E0596]: cannot borrow `c` as mutable, as it is not declared as mutable --> src/main.rs:13:5 | 13 | c.bump(); | ^ cannot borrow as mutable | help: consider changing this to be mutable | 12 | let mut c = Counter { count: 0 }; | +++
Go runs this and prints 1. Go takes &c for you behind the scenes when c is a variable, so it can call a pointer method.
Kotlin runs this and prints 1. val only stops you from reassigning c, not from changing what's inside it.
Python prints 1. There is no mut: any name can call any method, and the object changes. An ordinary class has no way to mark a method as "changes the object" or a variable as "never changes" (a frozen dataclass opts in per class).
Prints 1. const only stops you from pointing c at a different object; the object itself can still change. (readonly fields opt in, one by one.)
This prints 1. In Java, final only stops c from pointing at another object; the object itself can still change.
A method can change a class's (non-readonly) fields in C#, so this prints 1. There is no marker at the call site that says this call changes c.
gcc only warns that &c drops the const, then lets counter_bump change it and prints 1. Changing a const object is undefined behaviour, so this is luck.
In C++ you have to write const to get this error; in Rust you get it by writing nothing. bump is not a const method, so it can't be called on a const Counter. (-fpermissive in the message is the switch that would turn this error into a warning; without it, it is an error.)
bump says "I change the counter" (&mut self). But c was declared with plain let, which means "this never changes".
In Rust, let without mut covers the whole value, including everything inside it. That is stronger than Java's final, Kotlin's val or a JavaScript const, which only stop the variable from pointing somewhere else. It is what lets you trust that a non-mut value really stays the same (a few types, such as Cell and Mutex, change through & on purpose; see Shared state).
The fix
Compiles and runs.
struct Counter { count: u32,}
impl Counter { fn bump(&mut self) { self.count += 1; }}
fn main() { let mut c = Counter { count: 0 }; c.bump(); println!("{}", c.count);}1
The toolkit
A struct holds data. A method is a function that belongs to a type, so you can write acct.deposit(5) instead of deposit(&mut acct, 5). This lesson covers writing methods, what the first parameter means, and what Rust uses in place of constructors, overloading and inheritance. Outputs come from Linux on 64-bit ARM.
A method is a function with a self parameter
Methods go inside an impl block (“implementation”), named after the type they belong to:
struct Account { owner: String, balance: u32,}
impl Account { fn new(owner: &str) -> Self { Self { owner: owner.to_string(), balance: 0, } }
fn balance(&self) -> u32 { self.balance }
fn deposit(&mut self, amount: u32) { self.balance += amount; }
fn close(self) -> String { format!("{} closed with {}", self.owner, self.balance) }}
fn main() { let mut acct = Account::new("ana"); acct.deposit(50); acct.deposit(25); println!("balance: {}", acct.balance()); let note = acct.close(); println!("note: {note}");}balance: 75 note: ana closed with 75
impl Account { ... }says “the functions in here belong toAccount”.Selfinside the block is another name forAccount, so the body of the block survives a rename of the type.- A function whose first parameter is
selfis a method.selfis the value you called it on, soself.balanceis thebalancefield ofacct. - A function with no
self, likenew, is an associated function. You call it with::on the type:Account::new("ana"). More on that below.
A method and a field can share a name: acct.balance is the field and acct.balance() is the method. This is the usual way to give read access to a field, with no get_ prefix (API guidelines).
What the first parameter means
deposit takes &mut self, balance takes &self and close takes self. They are not three styles. The value you call a method on is the receiver, and the first parameter says what the method does with it, in the same words as the ownership, borrowing and mutable borrows lessons:
| Write | It is short for | The method may | The caller’s variable |
|---|---|---|---|
&self |
self: &Self |
read the value | a plain let is enough; still usable afterwards |
&mut self |
self: &mut Self |
read and change the value | a variable that owns the value must be let mut; one that already holds a &mut Account needs nothing; still usable afterwards |
self |
self: Self |
take the value: drop it, store it, or return it | a plain let is enough; the value is moved, so the variable can’t be used again (a Copy type such as a number is copied instead) |
mut self |
self: Self, and the method may change it |
change the value it now owns, and hand it back | a plain let is enough; moved |
mut self is the row to read twice. It does not make the caller’s variable changeable: the method owns the value it was given, and mut lets it change that value before returning it. The builder below uses it.
Rule of thumb: start with &self, use &mut self when the method changes the value, and self when it finishes with the value (turns it into something else, or hands its parts on). A method that turns a value into another one and uses it up is conventionally named into_... (API guidelines).
The compiler checks the method against its own signature. A &self method can’t change anything:
struct Account { balance: u32,}
impl Account { fn reset(&self) { self.balance = 0; }}
fn main() { let acct = Account { balance: 5 }; acct.reset(); println!("balance: {}", acct.balance);}error[E0594]: cannot assign to `self.balance`, which is behind a `&` reference --> src/main.rs:7:9 | 7 | self.balance = 0; | ^^^^^^^^^^^^^^^^ `self` is a `&` reference, so it cannot be written to | help: consider changing this to be a mutable reference | 6 | fn reset(&mut self) { | +++
Calling a &mut self method on a plain let is the other direction, and it is the gotcha at the top of this page (E0596). The third direction is a method that takes self, followed by a use of the variable:
struct Account { owner: String, balance: u32,}
impl Account { fn close(self) -> String { format!("{} closed with {}", self.owner, self.balance) }}
fn main() { let acct = Account { owner: String::from("ana"), balance: 75, }; let note = acct.close(); println!("note: {note}"); println!("balance: {}", acct.balance);}error[E0382]: borrow of moved value: `acct` --> src/main.rs:19:29 | 13 | let acct = Account { | ---- move occurs because `acct` has type `Account`, which does not implement the `Copy` trait ... 17 | let note = acct.close(); | ------- `acct` moved due to this method call 18 | println!("note: {note}"); 19 | println!("balance: {}", acct.balance); | ^^^^^^^^^^^^ value borrowed here after move | note: `Account::close` takes ownership of the receiver `self`, which moves `acct` --> src/main.rs:7:14 | 7 | fn close(self) -> String { | ^^^^
The note at the end names the cause: Account::close takes ownership of the receiver self. That is a move, the same as passing acct to a function. The signature tells every caller what happens, with no documentation to read. So a type can say “after this call you are finished with me”. Using something already closed is a run-time error in many languages, and an E0382 here.
Functions without self: new, Self and constants
A function in an impl block with no self belongs to the type but not to a value. These associated functions are where constructors go. Methods and associated functions written in the type’s own impl blocks are called inherent (as opposed to the ones a trait supplies):
struct Account { owner: String, balance: u32,}
impl Account { const MAX_BALANCE: u32 = 1_000;
fn new(owner: &str) -> Self { Self::with_balance(owner, 0) }
fn with_balance(owner: &str, balance: u32) -> Self { Self { owner: owner.to_string(), balance: balance.min(Self::MAX_BALANCE), } }}
// A second `impl` block for the same type is allowed.impl Account { fn is_full(&self) -> bool { self.balance == Self::MAX_BALANCE }}
fn main() { let a = Account::new("ana"); let b = Account::with_balance("bo", 5_000); println!("a: {} has {}, full: {}", a.owner, a.balance, a.is_full()); println!("b: {} has {}, full: {}", b.owner, b.balance, b.is_full()); println!("limit: {}", Account::MAX_BALANCE);}a: ana has 0, full: false b: bo has 1000, full: true limit: 1000
newis not a keyword. It is a name people agree on for “the main way to make one”. The standard library also hasString::from,Vec::with_capacityandFile::open. A struct has no hidden constructor: anywhere its fields are visible, you can build it withAccount { owner, balance }(privacy, below).Self::with_balance(owner, 0)calls another associated function of the same type.Self { ... }builds a value.const MAX_BALANCE: u32 = 1_000;insideimplis an associated constant: one value for the whole type, used asAccount::MAX_BALANCEoutside andSelf::MAX_BALANCEinside.- A type can have several
implblocks; together they are one set of methods. A trait’s methods get a block of their own (Traits).
Two inherent methods of one type can’t share a name, so you can’t overload by parameter list. Constructors that take different arguments get different names, as new and with_balance do:
struct Account { owner: String, balance: u32,}
impl Account { fn new(owner: &str) -> Self { Self { owner: owner.to_string(), balance: 0, } }
fn new(owner: &str, balance: u32) -> Self { Self { owner: owner.to_string(), balance, } }}
fn main() { let a = Account::new("ana"); println!("{} has {}", a.owner, a.balance);}error[E0592]: duplicate definitions with name `new` --> src/main.rs:14:5 | 7 | fn new(owner: &str) -> Self { | --------------------------- other definition for `new` ... 14 | fn new(owner: &str, balance: u32) -> Self { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ duplicate definitions for `new`
The exception is a trait: the standard library’s From can be implemented twice for one type, impl From<u8> for Y and impl From<&str> for Y, giving Y::from(7u8) and Y::from("abc"). That is a trait feature (Traits), not overloading of your own methods.
The dot does the borrowing for you
You never write (&mut acct).deposit(5). At a ., the compiler works out whether the method needs the value, & or &mut, and adds the borrow for you. It also looks through references, so a method works the same on a value and on a reference to it:
struct Account { balance: u32,}
impl Account { fn balance(&self) -> u32 { self.balance }
fn deposit(&mut self, amount: u32) { self.balance += amount; }}
fn top_up(acct: &mut Account) { acct.deposit(10); // `acct` is already a `&mut Account`; no `(*acct)` needed}
fn main() { let mut acct = Account { balance: 0 }; acct.deposit(5); // the compiler passes `&mut acct` top_up(&mut acct); println!("value: {}", acct.balance());
let r = &acct; let rr = &r; println!("&Account: {}", r.balance()); println!("&&Account: {}", rr.balance());
let mut accounts = vec![Account { balance: 1 }, Account { balance: 2 }]; for a in &mut accounts { a.deposit(100); // `a` is a `&mut Account` } println!("accounts[0]: {}", accounts[0].balance()); println!("accounts[1]: {}", accounts[1].balance());}value: 15 &Account: 15 &&Account: 15 accounts[0]: 101 accounts[1]: 102
Inside top_up, acct is already a &mut Account and acct.deposit(10) works with no (*acct) in front. The same goes for a in the loop and for a &&Account. A method that takes self is the exception:
struct Account { owner: String,}
impl Account { fn close(self) -> String { format!("{} closed", self.owner) }}
fn close_it(acct: &Account) -> String { acct.close()}
fn main() { let acct = Account { owner: String::from("ana"), }; println!("{}", close_it(&acct));}error[E0507]: cannot move out of `*acct` which is behind a shared reference --> src/main.rs:12:5 | 12 | acct.close() | ^^^^ ------- `*acct` moved due to this method call | | | move occurs because `*acct` has type `Account`, which does not implement the `Copy` trait | note: `Account::close` takes ownership of the receiver `self`, which moves `*acct` --> src/main.rs:6:14 | 6 | fn close(self) -> String { | ^^^^ note: if `Account` implemented `Clone`, you could clone the value --> src/main.rs:1:1 | 1 | struct Account { | ^^^^^^^^^^^^^^ consider implementing `Clone` for this type ... 12 | acct.close() | ---- you could clone this value
E0507: close_it only borrowed the account, so it cannot hand it to close, which wants to own it. The second note suggests Clone, a way to make a copy; usually the better fix is the signature: take Account by value in close_it, or make close take &self.
Method syntax is shorthand. Type::method(receiver, arguments) works for any method defined on Type, and it is the form to use when you need a method as a plain function:
struct Account { balance: u32,}
impl Account { fn balance(&self) -> u32 { self.balance }
fn deposit(&mut self, amount: u32) { self.balance += amount; }}
fn main() { let mut acct = Account { balance: 0 }; acct.deposit(5); Account::deposit(&mut acct, 5); // the same call, written out println!("method syntax: {}", acct.balance()); println!("function syntax: {}", Account::balance(&acct));
// A method is a function whose first parameter is the receiver, so it can be // stored in a variable and passed around. let read: fn(&Account) -> u32 = Account::balance; println!("through a variable: {}", read(&acct));
let accounts = vec![Account { balance: 3 }, Account { balance: 4 }]; let total: u32 = accounts.iter().map(Account::balance).sum(); println!("total: {total}");}method syntax: 10 function syntax: 10 through a variable: 10 total: 7
read holds a function (fn(&Account) -> u32 is the type of a function from &Account to u32). accounts.iter().map(Account::balance).sum() calls balance on every element of a list and adds the results (iterators covers iter, map and sum). The path must name the type that defines the method: iter belongs to slices, so Vec::iter(&v) is an error (E0599) and <[i32]>::iter(&v) works. The dot form is not a value: let f = acct.deposit; is an error (E0615), where the path form Account::deposit is fine.
Chaining and builders
When a method returns something that has more methods, calls can be chained: Request::new(url).retries(3).header("X-Id: 7"). Rust has no default or named arguments (see Constructors, below), so a type with many optional settings gives each setting its own method. That is the builder pattern, and a chain is how you use it.
A builder method has two possible shapes. This one takes mut self and returns Self, so every call moves the builder in and hands it back:
struct Request { url: String, retries: u32, timeout_secs: u32, headers: Vec<String>,}
impl Request { fn new(url: &str) -> Self { Self { url: url.to_string(), retries: 0, timeout_secs: 30, headers: Vec::new(), } }
fn retries(mut self, n: u32) -> Self { self.retries = n; self }
fn timeout(mut self, secs: u32) -> Self { self.timeout_secs = secs; self }
fn header(mut self, line: &str) -> Self { self.headers.push(line.to_string()); self }}
fn main() { let req = Request::new("https://example.com") .retries(3) .header("Accept: text/plain") .header("X-Id: 7") .timeout(5); println!("url: {}", req.url); println!("retries: {}", req.retries); println!("timeout: {}", req.timeout_secs); println!("headers: {:?}", req.headers);
// Adding a header only sometimes means putting the value back: let verbose = true; let mut req = Request::new("https://example.com"); if verbose { req = req.header("X-Debug: 1"); } println!("headers: {:?}", req.headers);}url: https://example.com retries: 3 timeout: 5 headers: ["Accept: text/plain", "X-Id: 7"] headers: ["X-Debug: 1"]
It reads like return this, but the old name is dead afterwards, which is why a setting made only sometimes needs req = req.header(...). No String or Vec contents are copied. Here Request is its own builder; a separate builder type is worth it when the finished value must be checked or has a different type.
The other shape borrows: &mut self in, &mut Self out. The builder here is a separate type that makes a Request at the end:
struct Request { url: String, retries: u32, headers: Vec<String>,}
struct RequestBuilder { url: String, retries: u32, headers: Vec<String>,}
impl RequestBuilder { fn new(url: &str) -> Self { Self { url: url.to_string(), retries: 0, headers: Vec::new(), } }
fn retries(&mut self, n: u32) -> &mut Self { self.retries = n; self }
fn header(&mut self, line: &str) -> &mut Self { self.headers.push(line.to_string()); self }
fn build(&self) -> Request { Request { url: self.url.clone(), retries: self.retries, headers: self.headers.clone(), } }}
fn main() { // One expression: the temporary builder lives until the end of the statement. let one = RequestBuilder::new("https://example.com") .retries(3) .header("Accept: text/plain") .build(); println!("one: {} {} {:?}", one.url, one.retries, one.headers);
// Step by step: the builder is a `let mut` variable, and a method call does // not need its result put back. let verbose = true; let mut b = RequestBuilder::new("https://example.com"); b.retries(1); if verbose { b.header("X-Debug: 1"); } let two = b.build(); println!("two: {} {} {:?}", two.url, two.retries, two.headers);}one: https://example.com 3 ["Accept: text/plain"] two: https://example.com 1 ["X-Debug: 1"]
Now b.header(...) changes b in place, so the conditional needs no re-assigning. The price is in build: it takes &self, so it clones the strings (clone makes a copy). Why not build(self)? A &mut Self chain only hands the next call a borrow, and a self method can’t be called through one:
struct Builder { name: String,}
impl Builder { fn new() -> Self { Self { name: String::new(), } }
fn name(&mut self, name: &str) -> &mut Self { self.name = name.to_string(); self }
fn build(self) -> String { self.name }}
fn main() { let text = Builder::new().name("ana").build(); println!("built: {text}");}error[E0507]: cannot move out of a mutable reference --> src/main.rs:23:16 | 23 | let text = Builder::new().name("ana").build(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ ------- value moved due to this method call | | | move occurs because value has type `Builder`, which does not implement the `Copy` trait | note: `Builder::build` takes ownership of the receiver `self`, which moves value --> src/main.rs:17:14 | 17 | fn build(self) -> String { | ^^^^ note: if `Builder` implemented `Clone`, you could clone the value --> src/main.rs:1:1 | 1 | struct Builder { | ^^^^^^^^^^^^^^ consider implementing `Clone` for this type ... 23 | let text = Builder::new().name("ana").build(); | -------------------------- you could clone this value
Same E0507 as before. There is also a trap with temporaries: values built in the middle of an expression with no variable, dropped at the end of the statement:
struct Builder { retries: u32,}
impl Builder { fn new() -> Self { Self { retries: 0 } }
fn retries(&mut self, n: u32) -> &mut Self { self.retries = n; self }
fn build(&self) -> u32 { self.retries }}
fn main() { let b = Builder::new().retries(3); println!("built: {}", b.build());}error[E0716]: temporary value dropped while borrowed --> src/main.rs:21:13 | 21 | let b = Builder::new().retries(3); | ^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement | | | creates a temporary value which is freed while still in use 22 | println!("built: {}", b.build()); | - borrow later used here | help: consider using a `let` binding to create a longer lived value | 21 ~ let mut binding = Builder::new(); 22 ~ let b = binding.retries(3);
Builder::new() is a temporary. retries(3) returns a &mut into it, and the next line uses that after it is gone: E0716. The fix is to keep the builder in a let mut variable first. In one statement, as in builder-mut-ref.rs, the temporary lasts long enough.
mut self -> Self |
&mut self -> &mut Self |
|
|---|---|---|
One expression, new().a().b()... |
works | works |
A final build(self) that moves the parts out |
works | E0507 |
| Setting an option only sometimes | b = b.a() |
b.a(); |
let b = Builder::new().a(); |
works | E0716 |
The API guidelines prefer the borrowing shape, and use the consuming one when the result needs the builder’s contents moved into it. The standard library uses both: Command::arg takes &mut self and returns &mut Command, while thread::Builder::name takes self and returns Builder.
Methods on tuple structs
A tuple struct has fields without names, self.0, self.1, and takes methods the same way:
struct Score(u32);
impl Score { fn new(points: u32) -> Self { Self(points) }
fn add(&mut self, points: u32) { self.0 += points; }
fn is_high(&self) -> bool { self.0 >= 100 }
fn into_inner(self) -> u32 { self.0 }}
fn main() { let mut s = Score::new(90); s.add(15); println!("high: {}", s.is_high()); println!("inner: {}", s.into_inner());}high: true inner: 105
Score(90) builds a value, and Self(points) does the same inside the block. Wrapping a number this way gives it its own methods and stops it being mixed up with other numbers.
Methods and privacy
Rust hides fields with a different boundary than Java or C#: the module. A module (mod bank { ... }) is a named block that groups code. Fields and methods are private by default: only the module they are defined in, and the modules nested inside it, can use them. pub makes one visible outside (Reference).
mod bank { pub struct Account { balance: u32, }
impl Account { pub fn new() -> Self { Account { balance: 0 } }
pub fn deposit(&mut self, amount: u32) { self.balance += amount; }
pub fn balance(&self) -> u32 { self.balance } }}
fn main() { let mut acct = bank::Account::new(); acct.deposit(5); acct.balance = 1_000_000; println!("balance: {}", acct.balance());}error[E0616]: field `balance` of struct `Account` is private --> src/main.rs:24:10 | 24 | acct.balance = 1_000_000; | ^^^^^^^ private field
Code outside bank can call deposit and balance, but not touch balance. Methods are how a type keeps its own rules (a balance that only changes through deposit). Hide a field when a rule needs protecting; plain data can stay pub. Inside bank, any code can touch the fields: the boundary is the module, not the type.
Method or function?
Use a method when the code is about one value of the type and reads as value.action(), or when the type must protect private fields. Use a plain function when two values play equal parts (distance(a, b)), when there is no natural owner, or when the type is not yours. A method is a function with a receiver (explicit-call.rs), so the choice is about readability. An inherent impl block must be in the crate that defines the type (a crate is one library or program: everything compiled together; Reference), so you can’t add inherent methods to str or Vec. Adding methods to someone else’s type works through a trait (Traits).
Your language’s methods in Rust
Pick your language in “Coming from” at the top of the page to see how the code you write today maps to Rust.
Going deeper
What self is in memory
acct.deposit(5) does not copy the Account. With &mut self, the method receives the address of acct. With self, the method receives the value itself, and acct is moved. This program prints the facts for both (as_ptr() is the address of the text; the program prints whether two addresses are equal):
use std::mem::size_of;
struct Account { owner: String, balance: u32,}
impl Account { fn itself(&self) -> &Account { self }
fn balance(&self) -> u32 { self.balance }
fn text_address(&self) -> *const u8 { self.owner.as_ptr() }
fn close(self) -> *const u8 { self.owner.as_ptr() }}
fn main() { let acct = Account { owner: String::from("ana"), balance: 75, }; println!("size of Account: {} bytes", size_of::<Account>()); println!("size of String: {} bytes", size_of::<String>()); println!("size of u32: {} bytes", size_of::<u32>()); println!( "owner: {} bytes of text, capacity {}", acct.owner.len(), acct.owner.capacity() ); println!("balance: {}", acct.balance()); println!( "&self is the caller's variable: {}", std::ptr::eq(acct.itself(), &acct) );
let before = acct.text_address(); let after = acct.close(); println!("same text buffer after close: {}", before == after);}size of Account: 32 bytes size of String: 24 bytes size of u32: 4 bytes owner: 3 bytes of text, capacity 3 balance: 75 &self is the caller's variable: true same text buffer after close: true
An Account is a String (24 bytes) plus a u32 (4 bytes), rounded up to 32. The text ana is on the heap. With &self or &mut self:
self holds one address. std::ptr::eq(acct.itself(), &acct) printed true: the method sees the caller's variable, not a copy of it.And self by value, acct.close():
close; the compiler may copy them or not. The text is not copied: its address before and after was the same (true above). When close ends, self is dropped and the text is freed. acct is not dropped a second time.Methods are not stored in the value. Counter below has three methods and is still the size of its one u32 field:
use std::mem::size_of;
struct Counter { count: u32,}
impl Counter { fn get(&self) -> u32 { self.count }
fn bump(&mut self) { self.count += 1; }
fn into_value(self) -> u32 { self.count }}
fn main() { let mut c = Counter { count: 0 }; c.bump(); println!("count: {}", c.get()); println!("size of Counter: {} bytes", size_of::<Counter>()); println!("size of u32: {} bytes", size_of::<u32>()); println!("into_value: {}", c.into_value());}count: 1 size of Counter: 4 bytes size of u32: 4 bytes into_value: 1
A call like c.bump() goes to a function the compiler picked while compiling, not to an entry in a table inside c. Choosing between types at run time (what a Go interface does) is a different feature, with its own lesson later.
How the compiler finds the method
For receiver.name(...), the compiler builds a list of candidate types: the receiver’s type T, then each type reached by dereferencing (looking through a reference, a Box, an Rc or another smart pointer, but not a raw pointer), and for an array, the slice it can be viewed as. After each candidate T it adds &T and &mut T. Going down the list, it looks for a method whose self has exactly that type. At each type the type’s own inherent methods come first, then trait methods. The first match wins, and two matches at the same step are an error: you then say which one with Type::method(&x). This is the rule in the Rust Reference. It is why rr.balance() worked on a &&Account in auto-ref.rs: dereferencing once gives &Account, exactly what balance wants.
No inheritance
Rust has no extends. A type that reuses another one holds it as a field:
struct Account { balance: u32,}
impl Account { fn balance(&self) -> u32 { self.balance }
fn deposit(&mut self, amount: u32) { self.balance += amount; }}
// There is no `extends`. A bigger type holds the smaller one as a field.struct Savings { account: Account, rate_percent: u32,}
impl Savings { fn deposit(&mut self, amount: u32) { self.account.deposit(amount); // forwarded by hand }
fn interest(&self) -> u32 { self.account.balance() * self.rate_percent / 100 }}
fn main() { let mut s = Savings { account: Account { balance: 0 }, rate_percent: 5, }; s.deposit(200); println!("balance: {}", s.account.balance()); println!("interest: {}", s.interest());}balance: 200 interest: 10
Savings has an Account, and deposit is forwarded by hand. That is more typing than inheritance or Go’s embedding. In return, Savings offers exactly what you wrote and nothing is overridden behind your back. A &Savings is not accepted where a &Account is wanted; you pass &s.account. Code that must accept several types uses traits.
Constructors, overloading and default arguments
A constructor is a function, usually new; two inherent methods can’t share a name; a parameter can’t have a default. The answers are different names for different ways to build, a struct literal, and a builder for many settings (above). A builder is more code than a default argument, and that is a real cost of the design.
A struct literal is the closest thing to named arguments: Account { owner: ..., balance: ... }. It needs a value for every field unless you end it with ..Default::default() or ..other (Structs), and only code that can see the fields can write one.
Practice
Try it
Open each exercise in the Playground, replace the todo!(), and run it until it prints all checks passed.
Basket
Write four methods for Basket: new, count (reads), add (changes) and into_receipt (uses the basket up). add trims the text (item.trim() removes spaces at both ends; see Strings) and refuses a blank item. The receipt joins the items with , using a loop. An empty basket gives (empty).
struct Basket { items: Vec<String>,}
impl Basket { /// An empty basket. fn new() -> Self { todo!("build a Basket whose items field is Vec::new()") }
/// How many items are in the basket. It only reads. fn count(&self) -> usize { todo!("self.items.len()") }
/// Adds the item with spaces trimmed off both ends and returns true. /// A blank item (empty, or only spaces) is not added: return false. fn add(&mut self, item: &str) -> bool { todo!("trim() the item; if it is empty return false, else push a String") }
/// Uses the basket up and returns the items joined with ", ". /// An empty basket gives "(empty)". fn into_receipt(self) -> String { todo!("loop over &self.items, push_str each, and put \", \" between them") }}
fn main() { let mut basket = Basket::new(); assert_eq!(basket.count(), 0); assert!(basket.add("café")); assert!(basket.add(" tea ")); assert!(!basket.add(" "), "a blank item is not added"); assert!(!basket.add(""), "an empty item is not added"); assert_eq!(basket.count(), 2); assert_eq!(basket.into_receipt(), "café, tea");
let empty = Basket::new(); assert_eq!(empty.into_receipt(), "(empty)");
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
struct Basket { items: Vec<String>,}
impl Basket { /// An empty basket. fn new() -> Self { Self { items: Vec::new() } }
/// How many items are in the basket. It only reads. fn count(&self) -> usize { self.items.len() }
/// Adds the item with spaces trimmed off both ends and returns true. /// A blank item (empty, or only spaces) is not added: return false. fn add(&mut self, item: &str) -> bool { let item = item.trim(); if item.is_empty() { return false; } self.items.push(item.to_string()); true }
/// Uses the basket up and returns the items joined with ", ". /// An empty basket gives "(empty)". fn into_receipt(self) -> String { if self.items.is_empty() { return String::from("(empty)"); } let mut receipt = String::new(); for item in &self.items { if !receipt.is_empty() { receipt.push_str(", "); } receipt.push_str(item); } receipt }}
fn main() { let mut basket = Basket::new(); assert_eq!(basket.count(), 0); assert!(basket.add("café")); assert!(basket.add(" tea ")); assert!(!basket.add(" "), "a blank item is not added"); assert!(!basket.add(""), "an empty item is not added"); assert_eq!(basket.count(), 2); assert_eq!(basket.into_receipt(), "café, tea");
let empty = Basket::new(); assert_eq!(empty.into_receipt(), "(empty)");
println!("all checks passed");}all checks passed
Greeting builder
Finish a builder whose setters take mut self and return Self, and whose build produces the greeting with a plain loop. Mind a blank name and zero repeats. The last check shows what you do when an option is set only sometimes.
struct GreetingBuilder { name: String, excited: bool, times: u32,}
impl GreetingBuilder { /// No name, not excited, said once. fn new() -> Self { todo!("name is String::new(), excited is false, times is 1") }
/// Sets the name. Each setter takes `mut self` and gives `self` back. fn name(mut self, name: &str) -> Self { todo!("self.name = name.to_string(); self") }
/// `true` ends the greeting with "!", `false` with ".". fn excited(mut self, yes: bool) -> Self { todo!("set the field, return self") }
/// How many times the greeting is repeated. fn times(mut self, n: u32) -> Self { todo!("set the field, return self") }
/// "Hello, NAME." repeated `times` times, separated by one space. /// The name is trimmed; a blank name becomes "stranger". /// Zero times gives an empty String. fn build(self) -> String { todo!("trim the name, make one sentence, push_str it `times` times with spaces between") }}
fn main() { assert_eq!(GreetingBuilder::new().build(), "Hello, stranger."); assert_eq!( GreetingBuilder::new().name("Zoë").excited(true).build(), "Hello, Zoë!" ); assert_eq!( GreetingBuilder::new().times(2).name(" Åsa ").build(), "Hello, Åsa. Hello, Åsa." ); assert_eq!( GreetingBuilder::new().name(" ").build(), "Hello, stranger.", "a blank name falls back to stranger" ); assert_eq!(GreetingBuilder::new().times(0).build(), "");
// Setting an option only sometimes means putting the builder back. let loud = true; let mut builder = GreetingBuilder::new().name("Li"); if loud { builder = builder.excited(true); } assert_eq!(builder.build(), "Hello, Li!");
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
struct GreetingBuilder { name: String, excited: bool, times: u32,}
impl GreetingBuilder { /// No name, not excited, said once. fn new() -> Self { Self { name: String::new(), excited: false, times: 1, } }
/// Sets the name. Each setter takes `mut self` and gives `self` back. fn name(mut self, name: &str) -> Self { self.name = name.to_string(); self }
/// `true` ends the greeting with "!", `false` with ".". fn excited(mut self, yes: bool) -> Self { self.excited = yes; self }
/// How many times the greeting is repeated. fn times(mut self, n: u32) -> Self { self.times = n; self }
/// "Hello, NAME." repeated `times` times, separated by one space. /// The name is trimmed; a blank name becomes "stranger". /// Zero times gives an empty String. fn build(self) -> String { let name = self.name.trim(); let name = if name.is_empty() { "stranger" } else { name }; let end = if self.excited { "!" } else { "." }; let sentence = format!("Hello, {name}{end}"); let mut out = String::new(); for i in 0..self.times { if i > 0 { out.push(' '); } out.push_str(&sentence); } out }}
fn main() { assert_eq!(GreetingBuilder::new().build(), "Hello, stranger."); assert_eq!( GreetingBuilder::new().name("Zoë").excited(true).build(), "Hello, Zoë!" ); assert_eq!( GreetingBuilder::new().times(2).name(" Åsa ").build(), "Hello, Åsa. Hello, Åsa." ); assert_eq!( GreetingBuilder::new().name(" ").build(), "Hello, stranger.", "a blank name falls back to stranger" ); assert_eq!(GreetingBuilder::new().times(0).build(), "");
// Setting an option only sometimes means putting the builder back. let loud = true; let mut builder = GreetingBuilder::new().name("Li"); if loud { builder = builder.excited(true); } assert_eq!(builder.build(), "Hello, Li!");
println!("all checks passed");}all checks passed
Celsius
A tuple struct with an associated constant. Write from_fahrenheit (no self), to_fahrenheit (&self), cool (&mut self, never below absolute zero; f64 has a max method) and colder (takes both values). colder is there to practise self; a real temperature type like this would be Copy.
struct Celsius(f64);
impl Celsius { const ABSOLUTE_ZERO: f64 = -273.15;
/// An associated function: no `self`. Celsius = (F - 32) * 5 / 9. fn from_fahrenheit(f: f64) -> Self { todo!("Self((f - 32.0) * 5.0 / 9.0)") }
/// Reads the temperature. Fahrenheit = C * 9 / 5 + 32. fn to_fahrenheit(&self) -> f64 { todo!("self.0 * 9.0 / 5.0 + 32.0") }
/// Lowers the temperature, but never below ABSOLUTE_ZERO. fn cool(&mut self, degrees: f64) { todo!("subtract, then f64::max with Self::ABSOLUTE_ZERO") }
/// Takes both temperatures and gives back the colder one. fn colder(self, other: Celsius) -> Celsius { todo!("compare self.0 and other.0") }}
fn main() { assert_eq!(Celsius::from_fahrenheit(212.0).0, 100.0); assert_eq!(Celsius::from_fahrenheit(-40.0).0, -40.0); assert_eq!(Celsius(100.0).to_fahrenheit(), 212.0); assert_eq!(Celsius(0.0).to_fahrenheit(), 32.0);
let mut c = Celsius(20.0); c.cool(5.0); assert_eq!(c.0, 15.0); c.cool(1000.0); assert_eq!( c.0, Celsius::ABSOLUTE_ZERO, "nothing is colder than absolute zero" );
let a = Celsius(3.0); let b = Celsius(-4.5); assert_eq!(a.colder(b).0, -4.5);
println!("all checks passed");}Open in the Rust Playground ↗Run it first: it stops at todo!() with not yet implemented and a hint, and the compiler warns that the parameter is unused. Both are expected. Replace the todo!() with your code and run it again until it prints all checks passed.
Show a solution
struct Celsius(f64);
impl Celsius { const ABSOLUTE_ZERO: f64 = -273.15;
/// An associated function: no `self`. Celsius = (F - 32) * 5 / 9. fn from_fahrenheit(f: f64) -> Self { Self((f - 32.0) * 5.0 / 9.0) }
/// Reads the temperature. Fahrenheit = C * 9 / 5 + 32. fn to_fahrenheit(&self) -> f64 { self.0 * 9.0 / 5.0 + 32.0 }
/// Lowers the temperature, but never below ABSOLUTE_ZERO. fn cool(&mut self, degrees: f64) { self.0 = (self.0 - degrees).max(Self::ABSOLUTE_ZERO); }
/// Takes both temperatures and gives back the colder one. fn colder(self, other: Celsius) -> Celsius { if self.0 <= other.0 { self } else { other } }}
fn main() { assert_eq!(Celsius::from_fahrenheit(212.0).0, 100.0); assert_eq!(Celsius::from_fahrenheit(-40.0).0, -40.0); assert_eq!(Celsius(100.0).to_fahrenheit(), 212.0); assert_eq!(Celsius(0.0).to_fahrenheit(), 32.0);
let mut c = Celsius(20.0); c.cool(5.0); assert_eq!(c.0, 15.0); c.cool(1000.0); assert_eq!( c.0, Celsius::ABSOLUTE_ZERO, "nothing is colder than absolute zero" );
let a = Celsius(3.0); let b = Celsius(-4.5); assert_eq!(a.colder(b).0, -4.5);
println!("all checks passed");}all checks passed
Read more
- The Rust Book, chapter 5.3: Method Syntax.
selfforms, associated functions, and “Where’s the->operator?”. - Rust by Example: Associated functions & Methods.
- The Rust Reference: Method-call expressions, for the exact lookup rule, Associated items, for associated constants, and Visibility and privacy.
- Rust API Guidelines: builders, getter names and conversion names.
std::process::Command(a&mut selfbuilder) andstd::thread::Builder(aselfbuilder).- Reading compiler errors, for E0594, E0596, E0382, E0507, E0592, E0616 and E0716.
Learn Rust is an independent project, not affiliated with the Rust Project or the Rust Foundation. Code and text are under the MIT licence.Source on GitHub.