Traits & modes
Share method implementations and adapt the behavior of existing types.
On this page
Traits share method implementations between types. Modes customize the behavior of an existing class. Neither is the same as an interface, which describes a contract callers can use.
Reuse methods with a trait#
A trait's methods are included in a class or struct with use:
trait Labeled {
fn label() String {
return "Item: %{this.name}"
}
}
class Folder {
name: String
use Labeled
}
fn main() {
let folder = Folder { name: "Reports" }
println(folder.label())
}
This trait expects the receiving type to have a name property. Use a trait when several types should share an implementation, and an interface when callers need to accept different classes through one method contract.
Traits can also have type parameters. A declaration such as trait Wrapper[T] can be included with use Wrapper[String].
Customize behavior with a mode#
A mode wraps an existing class without adding properties. For example, a string mode can compare text without case sensitivity:
mode CaseInsensitive for String {
fn equals(other: CaseInsensitive) bool $eq {
return this.lower() == other.lower()
}
fn hash() uint $hash {
return this.lower().hash()
}
}
fn main() {
let name: CaseInsensitive = "Ada"
println(name == "ADA") // true
}
$eq provides equality behavior and $hash provides the matching hash. Equal values must have equal hashes when a type is used as a hash-map key.
A mode remains implicitly compatible with its base class in both directions. It is therefore a way to adapt behavior, not a validation boundary that prevents conversion back to the base type.
That compatibility does not automatically extend through generic containers: Array[CaseInsensitive] is still a different type from Array[String].
Name a type or declaration#
Use type to name a type expression, alias to give an existing declaration another name, and value for a compile-time value:
use valk.fs
alias ReportPath for fs.Path
type Predicate (fn(int)(bool))
value PAGE_SIZE (20)
fn main() {
let accepts: Predicate = fn(value: int) bool { return value > 0 }
let path: ReportPath = "reports"
println(accepts(PAGE_SIZE))
println(path)
}
Choose a name that explains the role of a value in the program. Naming a type expression does not by itself add new storage or validation behavior.