Many operators are implemented as method calls. For instance, x+3 is treated as if it were: x.`+`(3) A type need only implement the `+` method to make the + operator meaningful to values of that type. If a program tries to apply the + operator to a value whose type has not implemented the `+` method, a compiler error results.

Let's explore the implications across a number of operators.

Arithmetic Operator Methods

A type may support any of the arithmetic operators (+, -, *, / and %) by implementing them as methods. The integer and floating point types do this, of course, but really any type that effectively encodes numbers or number structures can do this. This might include: complex numbers, "big" integers, rationals, unit-based numbers, vectors or matrices.

On the previous page, the Point type example took advamtage of this by implementing the subtraction method:

fn `-`(pt2) Point
	Point[pt2.x - x, pt2.y - y]

Note: Whenever a name includes non-alphanumeric (or underscore) characters, it must be enclosed in backticks. This is why the method name is shown as `-`.

With this method implemented, one may simply subtract two Points in an expression: pt1 - pt2.

Unary negation may also be implemented using the `-`. The difference between the negation method and the subtraction method is that the former needs only the self parameter:

fn `-`() Point
  Point[-x, -y]

Arithmetic Assignment Operators

A custom type may also support any of the arithmetic assignment operators (+=, -=, *=, /= and %=) by implementing them as methods.

If a type does not implement the arithmetic assignment operators, the compiler will attempt to implement them using the arithmetic methods. Thus, pt1 += pt2 would be treated as if it were: pt1 = pt1 + 2.

Increment and Decrement Operators

A type may implement the increment or decrement operators (++ and --) as methods. When used as prefix operators, their names are `++` and `--`. When used as postfix operators, their names are `_++` and `_--`.

Logic and Shift Operator Methods

A type may support any of the logic and shift operators (!, &, ^, ~, << and >>) by implementing them as methods.

Similarly, the logic an shift assignment operators may also be implemented as methods. If not, they will be handled in the same way as the arithmetic assignment operators.

Comparison Operator Methods

To support operator-based comparison of two values, implement the appropriate comparison operator(s) (==, <, <=, >, or >=) as methods. It is not necessary to implement the inequivalence operator (!=), as this is supported automatically if `==` is implemented.

'set' Methods for Assignment

To implement methods that work to the left of the assignment operator, use a set method. Such methods are designated by preceding fn with set. For example:

struct Counter
  _count u32 = 0
  set fn count(cnt u32)
    _count = cnt / 2

As this example demonstrates, a set method typically alters some private field's value using a formula or algorithm. We can make use of this method as follows:

mut cnt = Counter[]
cnt.count() = 10    // Sets _count to 5
cnt.count = 6       // Note: the parentheses can be omitted

The value to the right of the assignment is passed to the set method as the last argument.

Getters and Setters

For public fields, there is often no reason to implement getters and setters to offer direct access to their values. However, if it would be unsafe to offer direct access to certain fields, in order to preserve specific data invariants, one should make them private and then potentially offer appropriate getters and setters that preserve the invariants.

Implementing a getter and setter is as easy as defining a a regular and set method of the same name:

struct Counter
	_count u32 = 0

  // a getter
  fn counter() u32
    _count++

  // a setter
  set fn count(cnt u32)
    _count = cnt

And here we use them:

mut x = Counter[]
x.counter // 0
x.counter // 1
x.counter = 10 // uses the "setter" to set the value
x.counter // 10

Because we can omit the parentheses when using a getter or setter, their use looks no different than access to a public field. This makes it possible to start off with a type making a field public, and then later changing it to private because of an invariant. By implementing getters and setters instead, the API can be preserved even as the implementation evolves.

Collection Operators

By wrapping an array (or array reference) within a struct, one can create all sorts of collections: indexed collections like vectors and dictionaries, as well as stacks, queues, sets and more. The difference between them is largely determined by which methods are implemented for working with various elements of the collection. Most collections will typically implement the len method to find how many elements are currently in the collection, and iter to enable iteration through the collection's elements.

A couple of operators make it easier to work with collections: [] for indexed access and <- to add or remove an element. Each collection type implements methods that determine how these operators work.

[] Indexed Access Operator

As we saw with arrays, the [] operator is used to access a specific collection element according to its index. Implementing the [] method as both a getter and setter makes indexed access possible for other kinds of indexed collections. The index need not even be an unsigned integer, as it is for an array. Dictionaries, for example, often use strings as the index.

<- Append Operator

A collection may implement the <- operator for adding an element or a slice's elements to the end of a collection.

() "Function call" Operator

It is actually possible to "call" an object as if it were a function. Doing so will actually invoke the () method for the object. This is what makes it possibe to support closures.

Initializers and Finalizers

A type may define one or more initializers. Initializers offer an alternative to the type constructor to create a new value of that type. In contrast to a type constructor, which uses square brackets, an initializer uses parentheses:

imm pt = Point(1.)

The default name for initializers is init. An initializer may, however, use some other name. In particuar, an initializer named clone is automatically used (when defined) whenever a copy of a value needs to be made due to assignment, function call parameters, etc.

A type may also define a single finalizer called final. This is automatically invoked immediately before any value of that type is destroyed, and its memory space reclaimed.

On a later page, detailed advice is given on how to define initializers and finalizers.

_