Data Types & Formulas
Flame is statically typed with strong type inference. Types can either be explicitly declared or deduced automatically by the compiler.
Primitive Types
Section titled “Primitive Types”| Type | Description | Example |
|---|---|---|
Int |
64-bit signed integer | let age: Int = 28 |
Float |
64-bit IEEE 754 floating-point number | let pi: Float = 3.14159 |
Byte |
8-bit unsigned integer (0–255) | let b: Byte = 255 |
Bytes |
Contiguous dynamic byte buffer | let buf: Bytes = byte.fromBytes([70, 77, 80]) |
String |
UTF-8 encoded text string | let name: String = "Flame" |
Bool |
Boolean value (true or false) |
let is_ready: Bool = true |
Unknown |
A dynamically resolved type when inference is unavailable | let data: Unknown = fetch() |
Nil |
Represents the absence of a value (void / null) | let empty = nil |
String Interpolation
Section titled “String Interpolation”Flame supports expressive string interpolation using the $ prefix:
let user = "Alice"let score = 98let message = $"Player {user} scored {score} points!"print(message)Escaping Literal Braces
Section titled “Escaping Literal Braces”When you need to output literal curly braces { or } inside an interpolated string without triggering expression evaluation, Flame provides two intuitive escaping mechanisms:
- Backslash Escape (
\{and\}):let json = $"JSON: \{{ \"user\": \"{user}\" \}}"// Evaluates to: JSON: { "user": "Alice" } - Double Brace Escape (
{{and}}) (familiar to developers coming from Rust, C#, and Python):let template = $"Formatting {{literal_braces}} with variable {score}"// Evaluates to: Formatting {literal_braces} with variable 98
Nested Expressions & Braces
Section titled “Nested Expressions & Braces”String interpolation expressions inside { ... } are fully aware of brace nesting depth. You can freely embed nested blocks, closures, and if expressions directly inside an interpolated string without prematurely terminating interpolation:
let count = 5let status = $"Status: { if count > 0 { "Active" } else { "Idle" } }"Multiline Strings
Section titled “Multiline Strings”Flame natively supports multiline strings using triple quotes ("""). They automatically strip common leading indentation, so you can keep your code neatly indented without breaking string formatting:
let str = """ This is a multiline string. It preserves internal indentation. """Multiline Interpolation
Section titled “Multiline Interpolation”You can also combine interpolation with multiline strings using $"""...""". It supports the exact same escaping and nested expression rules:
let user = "Alice"let score = 100let str = $""" \{{ "user": "{user}", "score": {score}, "status": "{ if score >= 80 { "Pass" } else { "Fail" } }" \}} """The Unknown Type
Section titled “The Unknown Type”Flame is statically typed, but there are situations where the compiler cannot definitively infer a variable’s type at compile time—especially when parsing untyped JSON, working with untyped dynamic arrays (like [] without a type annotation), or interfacing with certain dynamic APIs.
In these cases, Flame uses the Unknown type.
Unknownacts as a dynamic type fallback.- You can store any value inside an
Unknownvariable. - Operations on
Unknownvalues bypass strict static type checking and are evaluated dynamically at runtime.
// An empty array has no inferred type, so it becomes [Unknown]let mut data = []data.push(42)data.push("String") // Allowed, because the array holds Unknown typesComposite Types
Section titled “Composite Types”Dynamic Vectors ([T])
Section titled “Dynamic Vectors ([T])”A dynamically sized collection of elements sharing type T.
let mut numbers: [Int] = [10, 20, 30]
// Common vector methodsnumbers.push(40)let last = numbers.pop() // 40let length = numbers.len() // 3
// Functional transformationslet doubled = numbers.map((x: Int) { return x * 2 })let filtered = numbers.filter((x: Int) { return x > 15 })
print(doubled) // [20, 40, 60]print(filtered) // [20, 30]Vector Methods Reference
Section titled “Vector Methods Reference”| Method | Description | Flame Example |
|---|---|---|
.len() / .length() |
Element count | arr.length() |
.isEmpty() |
Returns true if length is 0 |
arr.isEmpty() |
.first() |
First item or nil |
arr.first() |
.last() |
Last item or nil |
arr.last() |
.get(i) |
Index access (supports negative indices) | arr.get(-1) |
.contains(x) |
Checks membership | arr.contains(42) |
.index(val_or_closure) |
Position of item or predicate match (-1 if missing) |
arr.index((x: Int) { return x > 10 }) |
.map(closure) |
Transforms each element | arr.map((x: Int) { return x * 2 }) |
.filter(closure) |
Retains elements matching predicate | arr.filter((x: Int) { return x % 2 == 0 }) |
.any(closure) |
True if any element matches predicate | arr.any((x: Int) { return x > 5 }) |
.all(closure) |
True if all elements match predicate | arr.all((x: Int) { return x > 0 }) |
.find(closure) |
First item matching predicate or nil |
arr.find((x: Int) { return x > 3 }) |
.count([val_or_closure]) |
Total count, value matches, or predicate matches | arr.count((x: Int) { return x % 2 != 0 }) |
.reverse() |
Returns reversed vector | arr.reverse() |
.sort() |
Returns sorted vector | arr.sort() |
.min() |
Minimum value | arr.min() |
.max() |
Maximum value | arr.max() |
.sum() |
Sum of numeric items | arr.sum() |
.join(sep) |
Joins elements into a string | arr.join(", ") |
.equals(other) |
Value equality comparison | arr.equals([1, 2, 3]) |
.concat(other) |
Concatenates two vectors | arr.concat([4, 5]) |
.slice(start, end) |
Slices a range of elements | arr.slice(1, 3) |
Tuples
Section titled “Tuples”Ordered collections of fixed size that can contain distinct types:
let point: (Int, Int) = (100, 250)let record: (String, Int, Bool) = ("Server", 8080, true)
// Access tuple memberslet (x, y) = pointlet (host, port, is_running) = record
println(x) // 100println(y) // 250println(host) // "Server"println(port) // 8080println(is_running) // trueComplex Tuples & Data Extraction
Section titled “Complex Tuples & Data Extraction”Flame relies heavily on Tuples for returning multiple values efficiently without needing boilerplate classes or formulas. Let’s look at a complex function signature as an example:
fn process_events(events: [(Int, Int, Int, Float, Int)]) -> (Int, Float, Float, Float, [Float], Int, [(Float, Int)])The Input: events: [(Int, Int, Int, Float, Int)]
This signifies that events is a dynamically sized Vector ([...]) containing strict Tuples ((...)). Each tuple is exactly 5 elements long. When iterating, we can extract all 5 elements instantly using Tuple Destructuring:
for event in events { let (event_id, timestamp, user_id, value, category) = event // ...}The Output: -> (Int, Float, Float, Float, [Float], Int, [(Float, Int)])
This is a massive return tuple containing primitive stats, an array of floats, an integer, and an array of smaller 2-element tuples. The caller can cleanly extract all 7 distinct items simultaneously using destructuring:
let result = process_events(events)let (count, total, minimum, maximum, category_totals, checksum, processed) = resultUnion Types (A | B)
Section titled “Union Types (A | B)”Flame supports union type annotations using the pipe (|) operator. Union types allow a variable, parameter, or return type to hold any one of several specified types.
// Accept an Int, a String, or Nilfn format_identifier(id: Int | String | Nil) -> String { if id == nil { return "N/A" } return $"ID: {id}"}
// Return multiple possible typesfn parse_number(input: String) -> Int | Float | Nil { // ...}The Flame code formatter preserves readable spacing around | in type annotations:
fn test_param(a: Int | String | Nil) -> Int | String { return a}Standard Types
Section titled “Standard Types”Flame includes several built-in types to handle common language patterns like error handling and optional values robustly, mimicking modern system languages.
Result<T, E>
Section titled “Result<T, E>”Result is an enum used for returning and propagating errors. It has two variants:
Ok(value): Indicates a successful execution and contains the success value.Err(error): Indicates a failure and contains the error value.
fn divide(a: Int, b: Int) -> Result<Int, Error> { if b == 0 { return Err(Error { message: "Division by zero", code: 1 }) } return Ok(a / b)}
let res = divide(10, 2)Option<T>
Section titled “Option<T>”Option is an enum used when a value might be absent. It has two variants:
Some(value): Contains the value.None: Indicates the absence of a value.
let user_id: Option<Int> = Some(10)let missing_id: Option<Int> = NoneError is a standard built-in struct containing structured information about a failure.
let err = Error { message: "File not found", code: 404}Closure Types
Section titled “Closure Types”Flame supports first-class closure type annotations for function parameters and variable declarations:
// 1. Higher-order function with closure parameter (implicit Nil return)fn for_each(items: [String], action: (item: String)) { for item in items { action(item) }}
// 2. Closure with parameters and explicit return typefn process_text(text: String, transform: (input: String) -> String) -> String { return transform(text)}
// 3. Mathematical closure operationfn compute(a: Int, b: Int, op: (x: Int, y: Int) -> Int) -> Int { return op(a, b)}
// Main executionlet languages = ["Flame", "Rust", "TypeScript"]
// Iterate using a closurefor_each(languages, (lang: String) { println($"• {lang}")})
// Transform string using an inline closurelet formatted = process_text("hello flame closures", (input: String) -> String { return $"[PROCESSED]: {input}"})println(formatted)
// Compute values using closure arithmeticlet sum = compute(20, 22, (x: Int, y: Int) -> Int { return x + y})println($"Sum: {sum}")Syntax Variations
Section titled “Syntax Variations”| Syntax | Description | Example |
|---|---|---|
(Type, Type) -> Ret |
Positional parameter types with explicit return type | (Int, Int) -> Int |
(name: Type, ...) |
Named parameters with implicit Nil return |
(client: ServerClient, bytes: Byte) |
(name: Type) -> Ret |
Named parameters with explicit return type | (x: Float, y: Float) -> Float |
() -> Ret |
Empty parameter list with return type | () -> String |
Type Safety & Dynamic Flexibility
Section titled “Type Safety & Dynamic Flexibility”In the current version of Flame, closure types are designed primarily for developer experience and IDE ergonomics:
- Flexible Type Safety: At call sites, closure type checking is currently non-strict and behaves flexibly (essentially like
Unknown). This ensures you can pass any closure(c, b) { ... }or existing function handler without running into rigid function-pointer type mismatch errors. - Hover Docs & Intelligent Autocomplete: The declared parameter types are utilized by the Flame Language Server and IDE type engine:
- Hover Cards: Hovering over a callback or higher-order function displays the expected parameters and return value.
- Parameter Type Inference: Inside the closure body, parameters automatically infer their declared types without manual annotations. For example, in
s.onBinary((client, bytes) { ... }), typingclient.suggests allServerClientmethods, andbytes.suggestsBytemethods.
Strict Closure Checking (Optional)
Section titled “Strict Closure Checking (Optional)”Strict compile-time closure signature verification can be enabled on-demand when you want rigorous type safety for closure parameters and return types:
- CLI Flag: Run or build with
--strict-closures:Terminal window flame run src/main.fm --strict-closuresfmp run --strict-closures - Project Configuration (
flame.toml): Setclosure-types = "strict"under[options]:[options]closure-types = "strict" # "strict" | "default" (default is flexible)
When strict closure checking is enabled, the compiler verifies parameter counts, parameter types, and return type compatibility, emitting compile-time type errors whenever signatures do not match.
Object vs Formula Literals
Section titled “Object vs Formula Literals”Flame provides two dynamic, map-like data structures: Objects and Formulas. While they share similar runtime semantics, they have distinct use cases and syntax.
Objects ({ ... })
Section titled “Objects ({ ... })”Objects use standard curly braces and are the preferred syntax for general-purpose dynamic records, data destructuring, and JSON payloads.
let user: Object = { name: "Soham", stats: { age: 17 }}
// Object destructuring is fully supportedlet { name, stats } = userprint($"User {name} is {stats.age} years old.")Formulas (formula { ... })
Section titled “Formulas (formula { ... })”The formula literal is a specialized keyword-prefixed structure. It behaves identically to Objects but is intended for use in places where explicit disambiguation is required, such as within annotation payloads.
let config = formula { host: "127.0.0.1", port: 3000, // Duplicate keys overwrite seamlessly: port: 8080, // They can store closures/anonymous functions: on_connect: () { print("Connected!") }}Formulas with Annotations
Section titled “Formulas with Annotations”Formulas are primarily used as metadata payloads for custom annotations where { ... } might conflict with block syntax:
@Entity(formula { table: "users", cache_ttl: 3600 })struct UserRecord { id: Int, name: String}