Proposal: Formattable Trait and Format Specs

Status: Approved Approved: 2026-01-30 Author: Eric (with AI assistance) Created: 2026-01-30 Affects: Compiler, type system, string formatting


Summary

This proposal formalizes the Formattable trait and format specification syntax for customized string formatting.


Problem Statement

The spec mentions Formattable but leaves unclear:

  1. Trait definition: What is the exact signature?
  2. Format spec syntax: What specifiers are supported?
  3. Relationship to Printable: How do they differ?
  4. Integration: How does formatting work in templates?
  5. Custom formats: How to implement for user types?

Formattable Trait

Definition

trait Formattable {
    @format (self, spec: FormatSpec) -> str
}

FormatSpec Type

type FormatSpec = {
    fill: Option<char>,
    align: Option<Alignment>,
    sign: Option<Sign>,
    width: Option<int>,
    precision: Option<int>,
    format_type: Option<FormatType>,
}

type Alignment = Left | Center | Right

type Sign = Plus | Minus | Space

type FormatType = Binary | Octal | Hex | HexUpper | Exp | ExpUpper | Fixed | Percent

These types are in the prelude, allowing custom Formattable implementations without explicit imports.


Format Spec Syntax

Format specifications use the syntax:

[[fill]align][sign][#][0][width][.precision][type]

Components

ComponentSyntaxDescription
FillAny characterPadding character (default: space)
Align< > ^Left, right, center alignment
Sign+ - Sign display for numbers
##Alternate form (prefix for hex, etc.)
00Zero-pad (implies right-align)
WidthIntegerMinimum field width
Precision.NDecimal places or max string length
TypeLetterFormat type (b, o, x, X, e, E, f, %)

Examples

SpecInputOutput
{:>10}"hello"" hello"
{:<10}"hello""hello "
{:^10}"hello"" hello "
{:*^10}"hello""**hello***"
{:08}42"00000042"
{:+}42"+42"
{:.2}3.14159"3.14"
{:b}42"101010"
{:x}255"ff"
{:X}255"FF"
{:#x}255"0xff"
{:e}1234.5"1.2345e+03"
{:.0%}0.75"75%"

Format Types

Integer Types

TypeDescriptionExample
bBinary42"101010"
oOctal42"52"
xHex (lowercase)255"ff"
XHex (uppercase)255"FF"

Float Types

TypeDescriptionExample
eScientific (lowercase)1234.5"1.2345e+03"
EScientific (uppercase)1234.5"1.2345E+03"
fFixed-point1234.5"1234.500000"
%Percentage0.75"75%"

Alternate Form (#)

TypeWithout #With #
b"101010""0b101010"
o"52""0o52"
x"ff""0xff"
X"FF""0xFF"

String Template Integration

Basic

let name = "world"
`hello {name}`  // Uses Printable.to_str()

With Format Spec

let n = 42
`value: {n:08}`      // "value: 00000042"
`hex: {n:#x}`        // "hex: 0x2a"

let pi = 3.14159
`pi = {pi:.2}`       // "pi = 3.14"
`pi = {pi:>10.2}`    // "pi =       3.14"

Escaping

`literal braces: {{}}`  // "literal braces: {}"

Relationship to Printable

Printable

trait Printable {
    @to_str (self) -> str
}
  • Used for basic string conversion
  • Required for {x} in templates (no format spec)
  • Returns human-readable default representation

Formattable

  • Used for formatted string conversion
  • Required for {x:spec} in templates
  • Accepts format specification

Blanket Implementation

All Printable types have a blanket Formattable implementation:

impl<T: Printable> Formattable for T {
    @format (self, spec: FormatSpec) -> str = {
        let base = self.to_str()
        apply_format(s: base, spec: spec)
    }
}

This applies width, alignment, and fill. Type-specific formatting (binary, hex, etc.) is only available for types that implement Formattable directly.


Standard Implementations

int

impl int: Formattable {
    @format (self, spec: FormatSpec) -> str = match spec.format_type {
        Some(Binary) -> format_int_base(n: self, base: 2, spec: spec)
        Some(Octal) -> format_int_base(n: self, base: 8, spec: spec)
        Some(Hex) -> format_int_base(n: self, base: 16, lowercase: true, spec: spec)
        Some(HexUpper) -> format_int_base(n: self, base: 16, lowercase: false, spec: spec)
        _ -> format_int_decimal(n: self, spec: spec)
    }
}

float

impl float: Formattable {
    @format (self, spec: FormatSpec) -> str = match spec.format_type {
        Some(Exp) -> format_scientific(n: self, uppercase: false, spec: spec)
        Some(ExpUpper) -> format_scientific(n: self, uppercase: true, spec: spec)
        Some(Fixed) -> format_fixed(n: self, spec: spec)
        Some(Percent) -> format_percent(n: self, spec: spec)
        _ -> format_float_default(n: self, spec: spec)
    }
}

str

impl str: Formattable {
    @format (self, spec: FormatSpec) -> str = {
        let s = match spec.precision {
            Some(n) -> self.take(count: n)
            None -> self
        }
        apply_alignment(s: s, spec: spec)
    }
}

Custom Implementation

For User Types

type Money = { cents: int }

impl Money: Formattable {
    @format (self, spec: FormatSpec) -> str = {
        let dollars = self.cents / 100
        let cents = self.cents % 100
        let base = `{dollars}.{cents:02}`
        apply_alignment(s: base, spec: spec)
    }
}
let price = Money { cents: 1995 }
`Price: {price:>10}`  // "Price:     $19.95"

Delegating to Inner Value

type UserId = int

impl UserId: Formattable {
    @format (self, spec: FormatSpec) -> str = self.inner.format(spec: spec)
}

Parsing Format Specs

The compiler parses format specs at compile time:

`{value:>10.2f}`
// Parsed to:
// FormatSpec {
//     fill: None,
//     align: Some(Right),
//     sign: None,
//     width: Some(10),
//     precision: Some(2),
//     format_type: Some(Fixed),
// }

Invalid specs are compile errors:

`{value:abc}`  // ERROR: invalid format spec

Error Messages

Invalid Spec Syntax

error[E0970]: invalid format specification
  --> src/main.ori:5:15
   |
 5 | let s = `{n:xyz}`
   |               ^^^ unknown format type 'xyz'
   |
   = note: valid types: b, o, x, X, e, E, f, %

Type Mismatch

error[E0971]: format type not supported
  --> src/main.ori:5:15
   |
 5 | let s = `{name:x}`
   |               ^ hex format not supported for `str`
   |
   = note: hex format only works with integers

Not Formattable

error[E0972]: `MyType` does not implement `Formattable`
  --> src/main.ori:5:10
   |
 5 | let s = `{value:>10}`
   |          ^^^^^^^^^^^ trait not implemented
   |
   = help: implement `Formattable` or `Printable` for `MyType`

Spec Changes Required

Update 07-properties-of-types.md

Add Formattable trait section with:

  1. Trait definition
  2. FormatSpec, Alignment, Sign, FormatType types
  3. Blanket implementation for Printable

Update 12-modules.md (Prelude)

Add to prelude types:

  • FormatSpec, Alignment, Sign, FormatType
  • Formattable trait

Update CLAUDE.md

Update Formattable entry with full format spec syntax:

  • [[fill]align][sign][#][0][width][.precision][type]
  • Include all format types (b, o, x, X, e, E, f, %)

Summary

AspectDetails
Traittrait Formattable { @format (self, spec: FormatSpec) -> str }
Spec syntax[[fill]align][sign][#][0][width][.precision][type]
Alignments< left, > right, ^ center
Int typesb binary, o octal, x/X hex
Float typese/E scientific, f fixed, % percent
Alternate# adds prefix (0b, 0o, 0x)
BlanketPrintable types get basic Formattable