Method
So far a function that works on a struct stands on its own and is handed the struct: Area(card). A method is a function that belongs to a type and is called on a value of it: card.Area(). The value comes first, so the reader sees at once what is being asked about — and the type's behaviour is gathered in one place instead of scattered among free functions.
The extend block
Methods are declared in an extend block, apart from the struct. The struct stays a plain list of fields; the extend block holds what the type can do:
struct Rectangle {
width: float64;
height: float64;
}
extend Rectangle {
func Area(self: &Rectangle) -> float64 {
return self.width * self.height;
}
}
The self receiver
The value a method works on is its first parameter, called the receiver. It is always named self, and its type is written out like any other parameter's. self: &Rectangle is the read-only reference from the Reference lesson, so these methods may read the rectangle but not change it.
Inside a method, fields are always reached through self. self is an ordinary parameter, not an implied scope — a bare width would be an unknown name.
Calling a method
The value before the dot becomes self, borrowed just as an argument would be. The remaining arguments go in the parentheses:
PrintLine("card in envelope {}", card.FitsInside(envelope));
flowchart LR
call["card.FitsInside(envelope)"] --> self["self = card<br/>(borrowed as &Rectangle)"]
call --> other["other = envelope"]
self --> body["body of Rectangle's FitsInside"]
other --> bodyThat call matches the declaration parameter for parameter — self first, then other:
func FitsInside(self: &Rectangle, other: &Rectangle) -> bool {
return self.width <= other.width && self.height <= other.height;
}
Same name, different types
Each type has its own methods. Circle can have an Area of its own without clashing with the one on Rectangle:
extend Circle {
func Area(self: &Circle) -> float64 {
return 3.14159 * self.radius * self.radius;
}
}
Which one runs is decided by the value before the dot: card.Area() runs the rectangle's, coin.Area() the circle's. Two free functions named Area would have to be told apart by their parameter types, as overloading does; methods simply live with their type.
| Free function | Method |
|---|---|
func Area(card: &Rectangle) | func Area(self: &Rectangle) in extend |
Called as Area(card) | Called as card.Area() |
| Lives among all the package's functions | Lives with its type |
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// So far a function that works on a struct stands on its own and is handed the struct:
// `Area(card)`. A method is a function that belongs to a type and is called on a value of it:
// `card.Area()`. The value comes first, and the reader sees at once what is being asked about.
//
// Methods are declared in an `extend` block, apart from the struct, which stays a plain list of
// fields. The value a method works on is its first parameter, which is always named `self` and
// has its type written out like any other parameter. `self: &Rectangle` is the read-only
// reference from the Reference lesson, so these methods may read the rectangle but not change it.
import Io::PrintLine;
struct Rectangle {
width: float64;
height: float64;
}
extend Rectangle {
// Inside a method, fields are always reached through `self`. A bare `width` would be an
// unknown name: `self` is an ordinary parameter, not an implied scope.
func Area(self: &Rectangle) -> float64 {
return self.width * self.height;
}
func IsSquare(self: &Rectangle) -> bool {
return self.width == self.height;
}
// Further parameters come after `self`.
func FitsInside(self: &Rectangle, other: &Rectangle) -> bool {
return self.width <= other.width && self.height <= other.height;
}
}
struct Circle {
radius: float64;
}
// Each type has its own methods, so `Circle` can have an `Area` of its own without clashing with
// the one on `Rectangle`. Which one runs is decided by the value before the dot.
extend Circle {
func Area(self: &Circle) -> float64 {
return 3.14159 * self.radius * self.radius;
}
}
func Main() -> int {
let card = Rectangle { width: 9.0, height: 6.0 };
let envelope = Rectangle { width: 11.0, height: 11.0 };
let coin = Circle { radius: 1.25 };
// The value before the dot becomes `self`, borrowed just as an argument would be.
PrintLine("card area {}", card.Area());
PrintLine("envelope area {}", envelope.Area());
PrintLine("coin area {:.2}", coin.Area());
PrintLine("card square {}", card.IsSquare());
PrintLine("envelope square {}", envelope.IsSquare());
// The remaining arguments go in the parentheses, as for any call.
PrintLine("card in envelope {}", card.FitsInside(envelope));
PrintLine("envelope in card {}", envelope.FitsInside(card));
return 0;
}
Run it
cd Examples/Types/Method
rux run
card area 54.0
envelope area 121.0
coin area 4.91
card square false
envelope square true
card in envelope true
envelope in card false
Common mistakes
self.return width * self.height; fails with error: name 'width' is not defined in this scope. Write self.width: fields are never in scope by themselves.A first parameter called
this is not a receiver, just an ordinary parameter. With func Area(this: &Rectangle), the call card.Area() fails with error: call to 'Area' expects 1 argument, but 0 were provided. The receiver is always self.Area(card) fails with error: name 'Area' is not defined in this scope. A method belongs to its type, so it is reached through a value: card.Area().card.Area without () is read as a field, and fails with error: struct 'Rectangle' has no field 'Area'. A method is called, even when it takes no further arguments.Try it yourself
- Add
func Perimeter(self: &Rectangle) -> float64and print the card's perimeter. - Give
CircleanIsLargerThan(self: &Circle, other: &Circle) -> boolmethod. - Add
func Scaled(self: &Rectangle, factor: float64) -> Rectanglethat returns a new rectangle, and print the area ofcard.Scaled(2.0). - Remove
self.from one field inAreaand read the error.
Learn more
- Methods in the Rux Reference
- Mutating method — a method that changes its receiver
- Extension — adding methods to types you did not write
- Interface — a set of methods that many types share