Struct
There are no classes in the language, so structs are the next best option. Structs can be defined using the struct keyword and a label, which will then be used as the "type".
struct Fraction {
int numerator,
int denominator,
}
Fraction my_frac = Fraction{.numerator = 10, .denominator = 31}
Members of a struct can be accessed with <struct>.<member>.
If your struct is mutable, you can update any of its members:
mut Fraction my_frac = Fraction{.numerator = 1, .denominator = 10}
my_frac.numerator *= 2
assert(my_frac == Fraction{.numerator = 2, .denominator = 10})
Structs can be exported from their module using the pub keyword, and all of their fields will be public as well.
Methods
You can define methods on structs in the same way that you define functions. There are 3 types of methods: static, non-mutating, and mutating.
There are some special keywords available for use in methods. The self keyword represents an instance of the struct. It can be used to access fields and non-static methods on the struct. The Self type represents the struct type, and is simply a placeholder for the struct type name.
Unlike fields, methods of an exported struct are not exported by default, and must be specifically exported with the pub keyword. Combining everything together, there are 6 possible types of struct methods:
fn(): static methodpub fn(): exported static methodfn(self): non-mutating methodpub fn(self): exported non-mutating methodmut fn(self): mutating methodpub mut fn(self): exported mutating method
Also note that methods can be overloaded, meaning that multiple methods can exist with the same name, provided they have different function signatures. The mut and pub modifiers do not count in the function signature, so these two methods:
struct Data {
fn method(self) -> int {
return 1
}
mut fn method(self) -> int {
return 1
}
}
are considered to have the same function signature.
Static methods
Static methods are functions inside a struct that do not have self as an argument. Functionally, these are equivalent to normal functions, and only differ in their ability to use the Self type in their definition.
struct Fraction {
int numerator,
int denominator,
fn new(int n, int d) -> Self {
return Self{.numerator = n, .denominator = d}
}
}
You can call static methods using the Type.method() syntax. For the example above:
Fraction f = Fraction.new(1, 2)
assert(f == Fraction{.numerator = 1, .denominator = 2})
Static methods cannot be called from struct instances. So in the example above, f.new() will error at compile time.
Non-mutating methods
These are methods that use the self keyword, but do not mutate the struct instance. A good use case for these is to chain methods together, or if you want to return a result using the struct fields. For instance:
import "std/math" as math
struct Vector {
int x,
int y,
fn mod(self) -> float {
return math.sqrt(x**2 + y**2)
}
fn add_1_x(self) -> Self {
return Self{.x = self.x + 1, .y = self.y}
}
fn add_1_y(self) -> Self {
return Self{.x = self.x, .y = self.y + 1}
}
}
Vector vec = Vector{.x = 3, .y = 4}
assert(vec.mod() == 5.0)
assert(vec.add_1_x().add_1_y() == Vector{.x = 4, .y = 5})
Mutating methods
These methods mutate the struct instance and can only be called on mut instances. The function definition must also have the mut keyword to indicate that the method is mutating. Only self is mutable in the function, all of the other arguments are still immutable.
struct Vector {
int x,
int y,
mut fn add(self, Self other) {
self.x += other.x
self.y += other.y
}
}
mut Vector a = Vector{.x = 1, .y = 2}
Vector b = Vector{.x = 3, .y = 4}
a.add(b)
assert(a == Vector{.x = 4, .y = 6})
Interface
Interfaces are used to add extra methods to structs, and to declare that any struct that implements that interface is guaranteed to have the defined methods. To declare a new interface, use the interface keyword.
interface Display {
fn display(self) -> str
fn debug(self) -> str
}
This declares an interface called Display with two non-mutating methods, display() and debug(). To implement this on a struct, use the impl keyword.
struct Vec3 {
int x,
int y,
int z,
}
impl Vec3: Display {
fn display(self) -> str {
return "({self.x}, {self.y}, {self.z})"
}
fn debug(self) -> str {
return "Vec3\{.x = {self.x}, .y = {self.y}, .z = {self.z}\}"
}
}
Interfaces can be used as types if all you need is a guarantee of the existence of the method, regardless of the shape of the struct otherwise. For instance, if you'd like to take any struct that implements Display as an input to your function, you can simply use Display as the type, and that will give you access to its methods.
fn stdout_print(Display val) {
println(val.display())
}
fn stderr_print(Display val) {
eprintln(val.debug())
}
You can then pass any value to these functions, so long as it has the Display interface implemented on it.
Vec3 v = Vec3{.x = 3, .y = 4, .z = 5}
stdout_print(v) // (3, 4, 5)
stderr_print(v) // Vec3{.x = 3, .y = 4, .z = 5}
Note that simply implementing the method on the struct does not count as implementing the interface using the impl keyword.
struct Vec3 {
int x,
int y,
int z,
fn display(self) -> str {
// this is different from implementing `Display`
return "({self.x}, {self.y}, {self.z})
}
fn debug(self) -> str {
return "Vec3\{.x = {self.x}, .y = {self.y}, .z = {self.z}\}"
}
}
Vec3 v = Vec3{.x = 3, .y = 4, .z = 5}
stdout_print(v) // error: `Vec3` does not implement `Display`
Structs can implement different interfaces at the same time by using an impl for each interface.
interface Display {
fn display(self) -> str
}
interface Debug {
fn debug(self) -> str
}
struct Vec3 {
int x,
int y,
int z,
}
impl Vec3: Display {
fn display(self) -> str {
return "({self.x}, {self.y}, {self.z})"
}
}
impl Vec3: Debug {
fn debug(self) -> str {
return "Vec3\{.x = {self.x}, .y = {self.y}, .z = {self.z}\}"
}
}
fn stdout_print(Display val) {
println(val.display())
}
fn stderr_print(Debug val) {
eprintln(val.debug())
}
Vec3 v = Vec3{.x = 3, .y = 4, .z = 5}
stdout_print(v) // (3, 4, 5)
stderr_print(v) // Vec3{.x = 3, .y = 4, .z = 5}
There are some conditions on how you can implement interfaces:
-
You can implement your own interfaces on an external structs.
import "std/math/complex" as complex interface Add { fn add(self, int n) -> Self } impl complex.Complex: Add { fn add(self, int n) -> Self { return Self{.real = self.real + n, .imag = self.imag} } } -
You can implement external interfaces on your own structs.
import "fmt" as fmt struct Vec3 { int x, int y, int z, } impl Vec3: fmt.Display { fn display(self) -> str { return "({self.x}, {self.y}, {self.z})" } } -
You can implement your own interfaces on your own structs.
-
You can not implement external interfaces on external structs.
import "fmt" as fmt import "std/math/complex" as complex impl complex.Complex: fmt.Display { // This is not allowed }