Methods¶
A method is a function called with a receiver in front of it: value.name(args).
The built-in types come with methods, and an impl block adds methods to your
own types and to the built-in ones.
impl blocks¶
An impl block names a type and holds functions whose first parameter is the
receiver. The type is a struct or enum you declare, or one of the built-in type
names: string, list, map, int, float, bool.
struct Rect {
w: int,
h: int,
}
impl Rect {
fn area(self) {
return self.w * self.h;
}
fn scaled(self, factor) {
return Rect { w: self.w * factor, h: self.h * factor };
}
}
fn main() {
let r = Rect { w: 2, h: 3 };
print(r.area());
print(r.scaled(2).area());
}
impl blocks go at the top level, next to the type they belong to. A type may
have several. A block on a generic type names the type arguments it is written
against, and a method can take type parameters of its own
(s.tagged<string>("hi")); see Generics.
What self is¶
self is an ordinary first parameter, not a keyword. It receives the value the
method was called on, and its type is the type the impl block names. Any name
works, but self is the convention and reads best.
Parameters after the first behave exactly like function parameters: name them,
pass them positionally, and either leave the type to be inferred from the call
or pin it with name: type. A method takes a -> Type return annotation on the
same terms as a free function, and it is checked against what the body returns.
A method call is compiled to a plain function call with the receiver passed as
the first argument, so r.area() and a free function taking a Rect cost the
same and behave the same.
Mutating through a method¶
Structs, lists, and maps are passed by reference, so a method that writes to
self changes the value the caller holds.
struct Counter {
n: int,
}
impl Counter {
fn bump(self, by) {
self.n = self.n + by;
}
}
fn main() {
let c = Counter { n: 0 };
c.bump(5);
print(c.n);
}
Methods on enums¶
An enum takes methods the same way. Inside, match self to tell the variants
apart; see Enums.
enum Signal {
Stop,
Go,
}
impl Signal {
fn label(self) {
let out = "";
match self {
Stop => { out = "stop"; }
Go => { out = "go"; }
}
return out;
}
}
fn main() {
print(Signal::Go.label());
}
Resolution¶
The receiver's type decides which function a call reaches. Two types may both
define len, and neither collides with the other or with a free function of the
same name.
A field that holds a function is called through the same dot: obj.cb(x) calls
what the field cb holds, when the receiver's type has no method of that name.
A method wins, so adding one never changes which call a program was already
making. See Functions.
Calling a name the struct or enum has no method and no function-holding field for is a compile error at the call, naming the type, so a misspelling is caught before the program runs.
Operators on your own types¶
A method named by an operator symbol is what that operator reaches on a value
of the type. a + b calls the + method the type of a defines, with b as
the other operand.
struct Vec2 {
x: float,
y: float,
}
impl Vec2 {
fn +(self, other: Vec2) -> Vec2 {
return Vec2 { x: self.x + other.x, y: self.y + other.y };
}
fn -(self) -> Vec2 {
return Vec2 { x: -self.x, y: -self.y };
}
fn ==(self, other: Vec2) -> bool {
return self.x == other.x && self.y == other.y;
}
fn <(self, other: Vec2) -> bool {
return self.x < other.x;
}
}
fn main() {
let a = Vec2 { x: 1.0, y: 2.0 };
let b = Vec2 { x: 3.0, y: 4.0 };
print((a + b).x); // 4
print(a != b); // true
print(b > a); // true
a += b; // a = a + b
}
The operators a type defines are +, -, *, /, %, ^, &, |, ^^,
<<, >>, ==, < and <= between two values, and - and ~ in front of
one. - is the one symbol with both forms: the parameter count says which is
being declared, and a type may define both.
!=, > and >= need no method. != is the type's == with the answer
flipped, > is its < with the operands swapped, and >= is its <= the same
way. The swap is in the operands, so a derived comparison evaluates the right
operand first. ==, < and <= answer a question about two values, so each
returns a bool.
The other operand is of the receiver's type unless the method declares
otherwise, so fn *(self, k: float) -> Vec2 is what a vector scales by a
number with. Only the left operand decides which method an operator reaches:
2.0 * v is a type error however Vec2 defines *, because there is one
spelling of an operator method and it belongs to the type on the left.
A compound assignment applies the matching operator, so a += b reaches +
and needs nothing of its own. There is no method-call spelling of an operator:
a + b is how the method is called.
Built-in types are closed. What + means on an int or a string is part of
the language, so an operator method inside impl int or impl list is a
compile error; wrap the value in a type of your own instead. An operator a type
does not define is the ordinary operator error, and the report names the method
that would make the expression work. See errors and
operators.
Methods on the built-in types¶
An impl block can also name a built-in type, which is how the standard
library's collection modules define their helpers:
impl string {
fn shout(self) {
return self.uppercase() + "!";
}
}
fn main() {
print("hey".shout());
}
The built-in methods listed below keep precedence: an impl list method named
len never resolves, [1, 2].len() stays the built-in length. The one
exception is find on a list: with a function argument it resolves to the
standard library's predicate search, because the built-in find is the index
search by value and the argument type picks between the two.
The built-in methods¶
These come with the language and need no import.
- Strings:
len,uppercase,lowercase,trim,trim_left,trim_right,trim_sequence,trim_sequence_left,trim_sequence_right,starts_with,ends_with,contains,find,replace,split,repeat,reverse,is_int,is_float. - Lists:
len,push,remove,contains,find,sort,reverse,repeat,join,partition. - Maps:
len,get,insert,remove,contains,keys,values. - Integers:
abs. - Floats:
abs,sqrt,round,floor.
fn main() {
print(" Candela ".trim().lowercase());
print([3, 1, 2].len());
print((-4).abs(), 2.25.sqrt());
}
Lists carry a second set of methods from the standard library's list module,
available without an import: map, filter, reduce, each, any, all,
find, sort_by, first, last, is_empty, sum, product, min, max,
index_of, count, unique, chunk, take, and drop. See
Collections. The standard library's string and map
modules add methods to strings and maps the same way, behind an import.
The Option and Result types from the standard library are enums with impl
blocks, so their helpers are called as methods once the module is imported; see
Error handling.