Methods associate functional behavior to a defined type, offering capabilities consistent with and helpful for values of that type. For example (as mentioned earlier), `+` is one of several methods defined for the i32 type. It adds two integers together and returns the result.
This example defines several methods as part of the Point struct declaration:
struct Point: x f32 y f32 // Calculate distance between two points fn distance(self, pt2) f32: (self-pt2).len // Subtract two points fn `-`(self, pt2) Point: Point[pt2.x - x, pt2.y - y] // Calculate length of point fn len(self) f32: _lensq().sqrt // _lensq() is equivalent to self._lensq() // A private method fn _lensq(self) f32: x*x + y*y
There's a lot to unpack here, which we will get to soon. The end result is that the above type definition for Point now makes it possible to write logic that uses these methods:
imm from = Point[x: 1., y: 1.] imm to = Point[x: 4., y: 5.] imm dist = from.distance(to) // == 5.0. Pythagoras would be proud
Methods may be added to any named type declaration (e.g., struct, union, enum, number type), but not directly to any structural type (e.g., tuple, array, reference). To apply methods to a structural type, wrap the structural type as a field in a struct, and then add methods to that struct.
A union type's methods should be defined before the first struct variant. These methods apply to all variants, but can only access the union's common fields.
It is also possible for additional methods to be defined later than in the type declaration. The extend keyword adds methods to an existing type. For example:
extend i32: fn double() Self: self+self
A method definition looks (and behaves) like a function definition: It has a name, named parameter(s), return value type(s), and a statement body. What distinguishes a type's methods from its associated functions is that the first parameter is named 'self'.
As with fields, the names given to methods will not conflict with another type's method or field names, nor with the names of variables or functions outside the type. However, a type cannot have a method and field with the same name.
Two or more methods may be given the same name. This is called method overloading. Each same-named method should be given a different parameter "signature": they should vary in number of parameters or in the types of the parameters. A call to a method of this name will be resolved by matching the method call's argument types to the defined methods' signatures. The method that matches best is used. Supporting multiple methods of the same name makes it convenient for a type to support a similar capability for a broad range of differently-typed values.
A method's first parameter is always named self. If the type of self is not specified, it is assumed to be the same as the type we are specifying (Self). If a type is specified for self, it must either be the same as Self, or some kind of reference to Self.
Implicit parameter types
As with the self parameter, a method's other parameters need not specify their type. If omitted, Self is assumed.
Implicit field and method access
Nearly always, a method's logic needs access to the data object referred to by self. Often, it may also want to invoke other methods in the same type. As one would expect, such capabilities are easily achieved through the use of self:
imm len = (self.x * self.x + self.y * self.y).sqrt self.clear()
Fortunately, a more concise approach is possible, by omitting "self." altogether:
imm len = (x * x + y * y).sqrt clear()
Using the more concise syntax depends on not defining any local variables in the method that would conflict with the type's field or method names.