Declarations
Formatting rules for top-level declarations: functions, constants, types, traits, implementations, and tests.
Functions
Inline Format
Used when the complete signature fits in 100 characters:
@add (a: int, b: int) -> int = a + b
@greet (name: str) -> str = "Hello, " + name + "!"
@transform (user_id: int, transform: (User) -> User) -> Result<User, Error> = do_work()
Broken Parameters
When the signature exceeds 100 characters, break parameters one per line:
@send_notification_to_user (
user_id: int,
notification: Notification,
preferences: NotificationPreferences,
retry_config: RetryConfig,
) -> Result<void, NotificationError> = do_notify()
Broken Return Type
If the ) -> ReturnType = line still exceeds 100 after breaking parameters, break the return type.
Body placement: Body on same line if it fits, otherwise indented on next line.
// Body fits on return type line
@long_function_name (
first: int,
second: str,
) -> Result<HashMap<UserId, Preferences>, ServiceError> = do_work()
// Body exceeds 100, indent to next line
@very_long_function_name (
first_parameter: int,
second_parameter: str,
) -> Result<HashMap<UserId, NotificationPreferences>, NotificationServiceError> =
compute_something_complex(input: data)
Generics
Inline if they fit:
@identity<T> (x: T) -> T = x
@transform<T, U, V> (a: T, b: U, c: V) -> Result<V, Error> = do_it()
Default type parameters have space around =:
@process<T, Output = T> (input: T) -> Output = do_work(input)
Break if exceeding 100 characters:
@complex_transform<
InputType,
OutputType,
ErrorType,
ConfigurationType,
> (input: InputType) -> Result<OutputType, ErrorType> = do_work()
Where Clauses
Inline if short:
@sort<T> (items: [T]) -> [T] where T: Comparable = do_sort()
Break with where on new line if multiple or long:
@process<T, U> (items: [T], f: (T) -> U) -> [U]
where T: Clone + Debug,
U: Default + Printable = do_it()
Capabilities
Inline if they fit:
@fetch (url: str) -> Result<str, Error> uses Http = http_get(url)
@fetch_and_log (url: str) -> Result<str, Error> uses Http, Logger = do_work()
Break uses to new line if exceeding 100:
@complex_operation (input: Data) -> Result<Output, Error>
uses Http, FileSystem, Logger, Cache = do_it()
Function Clauses
Multiple clauses for pattern matching in parameters:
@factorial (0: int) -> int = 1
@factorial (n) -> int = n * factorial(n - 1)
@fib (0: int) -> int = 0
@fib (1: int) -> int = 1
@fib (n) -> int = fib(n - 1) + fib(n - 2)
Each clause follows the same breaking rules independently.
Constants
Constants at module level use let $name:
let $timeout = 30
let $max_retries = 3
let $api_url = "https://api.example.com"
Group related constants with blank lines between groups:
let $api_base = "https://api.example.com"
let $api_version = "v1"
let $timeout = 30s
let $max_retries = 3
let $debug_mode = false
Type Definitions
Structs
Inline if fits:
type Point = { x: int, y: int }
type User = { id: int, name: str, email: str }
Break fields one per line if exceeding 100:
type UserProfile = {
id: int,
name: str,
email: str,
created_at: Timestamp,
preferences: UserPreferences,
}
Sum Types
Inline if fits:
type Color = Red | Green | Blue
type Status = Pending | Running | Complete | Failed
type Result<T, E> = Ok(value: T) | Err(error: E)
Break variants one per line with leading | if exceeding 100:
type Event =
| Click(x: int, y: int)
| KeyPress(key: char, modifiers: Modifiers)
| Scroll(delta_x: float, delta_y: float, source: ScrollSource)
Type Aliases
type UserId = int
type UserMap = {int: User}
Attributes
Attributes appear on their own line above the type:
#derive(Eq, Clone, Debug)
type Point = { x: int, y: int }
#derive(Eq, Clone)
#deprecated("use NewType instead")
type OldType = { value: int }
Traits
Opening brace on same line, methods indented:
trait Printable {
@to_str (self) -> str
}
trait Default {
@default () -> Self
}
Default Type Parameters
Traits may have type parameters with defaults. Space around =:
trait Add<Rhs = Self> {
@add (self, rhs: Rhs) -> Self
}
trait Transform<Input = Self, Output = Input> {
@transform (self, input: Input) -> Output
}
trait Container<T, Default = int> {
@get (self) -> T
@default_value (self) -> Default
}
Bounds combined with defaults (bounds first, then =):
trait WithBounds<T: Clone = Self> {
@clone_and_process (self, value: T) -> T
}
Multiple methods separated by blank line (except single-method blocks):
// Single method - no blank line
trait Printable {
@to_str (self) -> str
}
// Multiple methods - blank lines between
trait Iterator {
type Item
@next (self) -> (Option<Self.Item>, Self)
@count (self) -> int = {
let (item, rest) = self.next()
match item {
None -> 0
Some(_) -> 1 + rest.count()
}
}
}
Trait inheritance:
trait DoubleEndedIterator: Iterator {
@next_back (self) -> (Option<Self.Item>, Self)
}
Implementations
Trait Implementations
impl Point: Printable {
@to_str (self) -> str = `({self.x}, {self.y})`
}
Inherent Implementations
impl Point {
@new (x: int, y: int) -> Point = Point { x, y }
@distance (self, other: Point) -> float = {
let dx = self.x - other.x
let dy = self.y - other.y
sqrt(float(dx * dx + dy * dy))
}
@translate (self, dx: int, dy: int) -> Point =
Point { x: self.x + dx, y: self.y + dy }
}
Generic Implementations
impl<T: Clone> Clone for Option<T> {
@clone (self) -> Self = match self {
Some(value) -> Some(value.clone())
None -> None
}
}
Tests
Tests follow function formatting rules. The tests clause stays with the signature:
@test_add tests @add () -> void = {
assert_eq(actual: add(a: 1, b: 2), expected: 3)
assert_eq(actual: add(a: -1, b: 1), expected: 0)
}
@test_math tests @add tests @subtract () -> void = {
assert_eq(actual: add(a: 2, b: 2), expected: 4)
assert_eq(actual: subtract(a: 5, b: 3), expected: 2)
}
@test_integration tests _ () -> void = {
let user = create_test_user()
let result = process_user(user)
assert_ok(result: result)
}
Test Attributes
#skip("not implemented yet")
@test_future tests @future_feature () -> void =
assert(condition: false)
#compile_fail("expected type error")
@test_type_error tests _ () -> void = {
let x: int = "not an int"
}
Imports
Stdlib imports first, then relative imports, separated by blank line:
use std.collections { HashMap, Set }
use std.math { abs, sqrt }
use std.time { Duration }
use "../utils" { format }
use "./helpers" { compute, validate }
use "./local" { helper }
Items sorted alphabetically:
use std.math { abs, cos, sin, sqrt, tan }
Break to multiple lines if exceeding 100:
use std.collections {
BTreeMap,
BTreeSet,
HashMap,
HashSet,
LinkedList,
}
Module aliases:
use std.net.http as http
use "./internal" { LongTypeName as Short }