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
selformut self. Selfin a trait method signature names the eventual implementing type.owned selfis 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
implblocks. - No trait bounds on functions (
fn foo<T: Trait>is not supported). - No
ownedtrait 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
Uarttrait). - 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 }
}
}
Cloneis defined bycore/clone.Cloneis in the global prelude, so bareCloneresolves without an import.Cloneis the standard interface for explicit duplication of non-Copyvalues.Copyremains a compiler-known structural property, not a normal trait authors implement.
Standard Traits
Official packages provide canonical trait interfaces. Notable ones:
| Trait | Package | Purpose |
|---|---|---|
Clone | core/clone | Explicit duplication of non-Copy values |
io.Writer | core/io | Writeable sink |
io.Reader | core/io | Readable 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.