Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Traits

Traits define shared interfaces. They are intentionally simple in Flint: method signatures only, used primarily for library and MCU abstraction.

Declaring a Trait

trait Writer {
    fn write(mut self, bytes: slice<u8>) -> Result<usize, Error>
    fn flush(mut self) -> Result<(), Error>
}

Rules:

  • Traits contain method signatures only, with no default implementations.
  • Methods may use self or mut self.
  • Self in a trait method signature names the eventual implementing type.
  • owned self is not supported on trait methods.
  • No associated types, associated constants, or trait inheritance.

Implementing a Trait

impl Writer for UartPort {
    fn write(mut self, bytes: slice<u8>) -> Result<usize, Error> {
        return self.write_bytes(bytes)
    }

    fn flush(mut self) -> Result<(), Error> {
        return self.flush_tx()
    }
}

A type may have both inherent methods (impl Type { ... }) and trait implementations (impl Trait for Type { ... }). At most one implementation of a given trait per type is allowed.

Using Traits as Parameters

Trait types may appear in parameter position only:

use core/io

fn write_line(mut out: io.Writer, text: string) -> Result<(), Error> {
    out.write(text.bytes())?
    return out.flush()
}

The caller passes any concrete type that implements io.Writer:

let mut port = uart.open(0)?
write_line(port, "Hello, Flint!")

let mut buf = io.Buffer.new()
write_line(buf, "buffered output")

Current Limitations

  • Trait types are not valid in return position, local variable annotations, struct fields, or collections. Use concrete types there.
  • No generic traits or generic impl blocks.
  • No trait bounds on functions (fn foo<T: Trait> is not supported).
  • No owned trait parameters.
// Ok: trait in parameter position
fn print_all(mut out: io.Writer, items: List<string>) -> Result<(), Error> { ... }

// Error: trait in return position
fn make_writer() -> io.Writer { ... }

// Error: trait in struct field
struct Printer {
    out: io.Writer    // not supported
}

When to Use Traits

Traits are most useful when:

  • An official library needs a stable interface that multiple types can implement (e.g., io.Writer).
  • A peripheral driver needs to be interchangeable (e.g., different UART implementations sharing a Uart trait).
  • You want to write one function that works with any type satisfying an interface.

For most application code, concrete types are simpler and preferred. Do not reach for traits to solve problems that named functions or composition solve better.

Built-In Traits

Flint 1.0 has a small set of standard traits that the language and official packages rely on directly.

Clone is the important ownership-related one:

struct Buffer {
    data: [u8; 1024]
}

impl Clone for Buffer {
    fn clone(self) -> Self {
        return Self { data: self.data }
    }
}
  • Clone is defined by core/clone.
  • Clone is in the global prelude, so bare Clone resolves without an import.
  • Clone is the standard interface for explicit duplication of non-Copy values.
  • Copy remains a compiler-known structural property, not a normal trait authors implement.

Standard Traits

Official packages provide canonical trait interfaces. Notable ones:

TraitPackagePurpose
Clonecore/cloneExplicit duplication of non-Copy values
io.Writercore/ioWriteable sink
io.Readercore/ioReadable source

Flint 1.0 does not expose user-defined Eq, Hash, or Ord traits. Clone is the standard built-in duplication trait, while Copy stays structural and compiler-known.