Skip to content

Traits

A trait declares a set of signatures that any type can satisfy. The definition contains no data — only the interface. Several independent traits describe orthogonal behaviors:

Displayable and Shape are two traits; Square implements both with separate bodies.

06-trait-definition.zolo
Playground
// Feature: Trait definition — shared interface

// Syntax: `trait Name { fn method(self) -> Ret  /* no body */ }`

// When to use: describe a set of methods that multiple types will

// implement (ad-hoc polymorphism).


// Trait without defaults — only signatures. Each implementer is

// required to provide a body for `display`.

trait Displayable {
  fn display(self) -> str
}

// Trait with multiple signatures.

trait Shape {
  fn area(self) -> float

  fn name(self) -> str
}

struct Square {
  side: float,
}

impl Displayable for Square {
  fn display(self) -> str {
    return "Square({self.side})"
  }
}

impl Shape for Square {
  fn area(self) -> float {
    return self.side * self.side
  }

  fn name(self) -> str {
    return "square"
  }
}

let s = Square { side: 4.0 }
print(s.display())  // Square(4)

print(s.area())  // 16

print(s.name())  // square

// expected:

// Square(4)

// 16

// square

impl Trait for Type provides the body of each method required by the trait. Distinct types have independent implementations — each one resolves the same interface in its own way:

Circle and Rectangle implement HasArea independently.

07-trait-implementation.zolo
Playground
// Feature: Trait implementation — `impl Trait for Type`
// Syntax: `impl Trait for Type { fn method(self) { ... } }`
// When to use: declare that a concrete type satisfies a trait by
// providing the body for each required method.

trait HasArea {
  fn area(self) -> float
}

struct Circle {
  radius: float,
}

struct Rectangle {
  width: float,
  height: float,
}

// Each `impl Trait for Type` is independent.
impl HasArea for Circle {
  fn area(self) -> float {
    return 3.14159 * self.radius * self.radius
  }
}

impl HasArea for Rectangle {
  fn area(self) -> float {
    return self.width * self.height
  }
}

let c = Circle { radius: 5.0 }
let r = Rectangle { width: 3.0, height: 4.0 }

// The `area` method resolves correctly for each type.
print(c.area())  // 78.53975
print(r.area())  // 12
// expected:
// 78.53975
// 12

A trait can include default methods — with a body already defined in the trait declaration. The implementor can accept the default or override it:

Person uses the default behavior of hello; Pet replaces it with a custom greeting.

08-trait-default-impl.zolo
Playground
// Feature: Default implementation in traits
// Syntax: inside the `trait`, the fn already has a body `{ ... }`
// When to use: provide default behavior derived from other trait
// methods. Implementers can accept the default or override it in
// `impl Trait for Type`.
//
// Note: the checker resolves methods by looking at the type's
// `impl`. To use the default, we still copy the delegated call —
// that is, `impl` must list ALL trait methods. Here we show
// override and explicit delegation.

trait Greet {
  fn name(self) -> str
  // Default — implementers MAY redefine.


  fn hello(self) -> str {
    return "Hello, {self.name()}!"
  }
}

struct Person {
  full: str,
}

// Accepts the default — copies the body in the impl.
impl Greet for Person {
  fn name(self) -> str {
    return self.full
  }

  fn hello(self) -> str {
    return "Hello, {self.name()}!"
  }
}

struct Pet {
  nickname: str,
}

// Overrides the default with something specific.
impl Greet for Pet {
  fn name(self) -> str {
    return self.nickname
  }

  fn hello(self) -> str {
    return "Hi {self.nickname}, who's a good one?"
  }
}

let alice = Person { full: "Alice" }
let rex = Pet { nickname: "Rex" }

print(alice.hello())  // Hello, Alice!
print(rex.hello())  // Hi Rex, who's a good one?
// expected:
// Hello, Alice!
// Hi Rex, who's a good one?

A single type can satisfy multiple traits at the same time, each in its own impl Trait for Type block. The methods from all traits are available on the instance:

Circle implements HasArea, Named, and Describabledescribe combines the other two.

09-multiple-traits.zolo
Playground
// Feature: Multiple traits on the same type

// Syntax: several independent `impl Trait for Type`

// When to use: combine orthogonal behaviors — the type satisfies

// each trait separately.


trait HasArea {
  fn area(self) -> float
}

trait Named {
  fn name(self) -> str
}

trait Describable {
  fn describe(self) -> str
}

struct Circle {
  radius: float,
}

impl HasArea for Circle {
  fn area(self) -> float {
    return 3.14159 * self.radius * self.radius
  }
}

impl Named for Circle {
  fn name(self) -> str {
    return "circle"
  }
}

impl Describable for Circle {
  fn describe(self) -> str {
    return "{self.name()} of area {self.area()}"
  }
}

let c = Circle { radius: 2.0 }
print(c.area())  // 12.56636

print(c.name())  // circle

print(c.describe())  // circle of area 12.56636

Challenge

Create a trait Resizable with a method scale(self, factor: float) -> Self. Implement it for Circle and Rectangle from the examples above.

enespt-br