Extension
extend is not only for types you declared yourself. It can add methods to a type you did not write — the built-in ones included — and from then on, in this package, every value of that type has them. 42.IsEven() reads better than IsEven(42), and a slice that can Sum() itself saves passing it to a helper every time.
Extending a built-in type
The block looks exactly like the ones in Method, with a built-in type's name after extend:
extend int {
func IsEven(self: int) -> bool {
return self % 2 == 0;
}
// Clamped follows in the same block.
}
From here on, any int can be asked: answer.IsEven(), volume.Clamped(0, 100).
Receivers by value
These receivers are taken by value — self: int, not self: &int. A number or a slice is small, and copying it is as cheap as borrowing it. The three receiver forms now on the table:
| Receiver | The method gets | Typical for |
|---|---|---|
self: T | a copy | small values: numbers, slices, enum cases |
self: &T | the caller's value, to read | structs and arrays that only need reading |
self: &var T | the caller's value, to change | methods that update the value |
A slice is a view, so even taken by value it still sees the caller's elements — copying the slice copies the view, not the numbers behind it.
Extending a slice
Any type can be extended, compound types included:
extend int[..] {
func Sum(self: int[..]) -> int {
var total = 0;
for value in self {
total += value;
}
return total;
}
// CountEven follows in the same block.
}
Inside CountEven, the IsEven added a few lines earlier is used like any other method: value.IsEven(). Extensions build on one another.
One exact type
The extended type must be one exact type, and methods do not spread to its relatives:
flowchart LR
ext["extend int[..]"] --> yes["int[..]<br/>has Sum and CountEven"]
ext -. "not" .-> no1["uint8[..]"]
ext -. "not" .-> no2["int32[..]"]
ext -. "not" .-> no3["int[5]<br/>(an array, not a slice)"]int32 is not int, uint8[..] is not int[..], and an array is not a slice. Each would need an extend of its own. An array reaches the slice methods through a view of itself:
PrintLine("sum of the last 3: {}", scores[2..].Sum());
The same view, scores[..] or the let all: int[..] = scores; in the program, gives the whole array.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// `extend` is not only for types you declared yourself. It can add methods to a type you did not
// write, the built-in ones included, and from then on, in this package, every value of that type
// has them.
//
// The receiver here is taken by value: `self: int`, not `self: &int`. A number or a slice is small,
// and copying it is as cheap as borrowing it. A slice is a view, so even by value it still sees
// the caller's elements.
import Io::PrintLine;
extend int {
func IsEven(self: int) -> bool {
return self % 2 == 0;
}
func Clamped(self: int, low: int, high: int) -> int {
if self < low {
return low;
}
if self > high {
return high;
}
return self;
}
}
// The extended type must be one exact type. `int[..]` gains these methods, but a `uint8[..]` or an
// `int32[..]` does not: it is a different type, and would need an `extend` of its own.
extend int[..] {
func Sum(self: int[..]) -> int {
var total = 0;
for value in self {
total += value;
}
return total;
}
func CountEven(self: int[..]) -> uint {
var count: uint = 0;
for value in self {
// A method added above is used like any other.
if value.IsEven() {
count += 1;
}
}
return count;
}
}
func Main() -> int {
let answer = 42;
let volume = 130;
PrintLine("{} is even: {}", answer, answer.IsEven());
PrintLine("{} clamped: {}", volume, volume.Clamped(0, 100));
let scores: int[5] = [7, 12, 9, 20, 4];
let all: int[..] = scores;
PrintLine("sum of all: {}", all.Sum());
PrintLine("even scores: {}", all.CountEven());
// An array is not a slice, so `scores.Sum()` stops with
// error: type 'int[5]' has no field 'Sum'
// The slice methods are reached through a view of the array instead.
PrintLine("sum of the last 3: {}", scores[2..].Sum());
return 0;
}
Run it
cd Examples/Types/Extension
rux run
42 is even: true
130 clamped: 100
sum of all: 52
even scores: 3
sum of the last 3: 33
Common mistakes
scores.Sum() fails with error: type 'int[5]' has no field 'Sum'. int[5] is an array, a different type from int[..]. Take a view first: scores[..].Sum().extend int does not reach int32: with let small: int32 = 4;, the call small.Double() fails with error: type 'int32' has no field 'Double'. Likewise a uint8[..] view has no Sum — the error is error: slice type 'uint8[..]' has no member 'Sum', with a note listing the members a slice does have.Try it yourself
- Add
func IsPositive(self: int) -> booltoextend intand use it. - Add
func Largest(self: int[..]) -> intto the slice extension, and print the largest score. - Add
func Average(self: int[..]) -> float64that reusesSum. Convert withasbefore dividing. - Add
extend uint8[..]with its ownSum, and call it on auint8array through a view.