Skip to content

Traits

Traits define behavioral contracts that types can implement. They work with for blocks to ensure types provide specific functionality.

A trait declares method signatures that implementing types must provide:

trait Display {
fn display(self) -> string
}

Use for Type: Trait to implement a trait for a type:

type User { name: string, age: number }
for User: Display {
fn display(self) -> string {
`${self.name} (${self.age})`
}
}

The compiler checks that all required methods are implemented. If you forget one, you get a clear error:

Error: trait `Display` requires method `display` but it is not implemented for `User`

Traits can provide default method bodies. Implementors inherit them unless they override:

trait Eq {
fn eq(self, other: string) -> boolean
fn neq(self, other: string) -> boolean {
!(self |> eq(other))
}
}
for User: Eq {
fn eq(self, other: string) -> boolean {
self.name == other
}
// neq is inherited from the default implementation
}

A type can implement multiple traits:

for User: Display {
fn display(self) -> string { self.name }
}
for User: Eq {
fn eq(self, other: string) -> boolean { self.name == other }
}

Traits are erased at compile time. for User: Display compiles to exactly the same TypeScript as for User — the trait just tells the checker that a contract is satisfied.

// Floe
for User: Display {
fn display(self) -> string { self.name }
}
// Compiled TypeScript (identical to plain for-block)
function display(self: User): string { return self.name; }

Record types can auto-derive trait implementations with deriving. This generates the same code as a handwritten for block with no runtime cost:

type User {
id: string,
name: string,
email: string,
} deriving (Display)

This generates:

  • display(self) -> string - string representation like User(id: abc, name: Ryan, email: r@t.com)
TraitGenerated implementation
DisplayTypeName(field1: val1, field2: val2) format
type User { name: string, age: number } deriving (Display)
// Generated TypeScript
type User = { name: string; age: number };
function display(self: User): string {
return `User(name: ${self.name}, age: ${self.age})`;
}
  1. deriving only works on record types (not unions)
  2. A handwritten for block overrides a derived implementation
  3. Only Display is derivable — Eq is built-in via ==
  1. All required methods (those without default bodies) must be implemented
  2. Default methods are inherited unless overridden
  3. Traits are compile-time only - no runtime representation
  4. No orphan rules - scoping via imports handles conflicts
  5. No trait objects or dynamic dispatch - traits are a static checking tool