Skip to content

Data Types & Formulas

Flame is statically typed with strong type inference. Types can either be explicitly declared or deduced automatically by the compiler.


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

Flame supports expressive string interpolation using the $ prefix:

let user = "Alice"
let score = 98
let message = $"Player {user} scored {score} points!"
print(message)

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

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 = 5
let status = $"Status: { if count > 0 { "Active" } else { "Idle" } }"

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.
"""

You can also combine interpolation with multiline strings using $"""...""". It supports the exact same escaping and nested expression rules:

let user = "Alice"
let score = 100
let str = $"""
\{{
"user": "{user}",
"score": {score},
"status": "{ if score >= 80 { "Pass" } else { "Fail" } }"
\}}
"""

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.

  • Unknown acts as a dynamic type fallback.
  • You can store any value inside an Unknown variable.
  • Operations on Unknown values 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 types

A dynamically sized collection of elements sharing type T.

let mut numbers: [Int] = [10, 20, 30]
// Common vector methods
numbers.push(40)
let last = numbers.pop() // 40
let length = numbers.len() // 3
// Functional transformations
let 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]
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)

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 members
let (x, y) = point
let (host, port, is_running) = record
println(x) // 100
println(y) // 250
println(host) // "Server"
println(port) // 8080
println(is_running) // true

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) = result

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 Nil
fn format_identifier(id: Int | String | Nil) -> String {
if id == nil {
return "N/A"
}
return $"ID: {id}"
}
// Return multiple possible types
fn 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
}

Flame includes several built-in types to handle common language patterns like error handling and optional values robustly, mimicking modern system languages.

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 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> = None

Error is a standard built-in struct containing structured information about a failure.

let err = Error {
message: "File not found",
code: 404
}

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 type
fn process_text(text: String, transform: (input: String) -> String) -> String {
return transform(text)
}
// 3. Mathematical closure operation
fn compute(a: Int, b: Int, op: (x: Int, y: Int) -> Int) -> Int {
return op(a, b)
}
// Main execution
let languages = ["Flame", "Rust", "TypeScript"]
// Iterate using a closure
for_each(languages, (lang: String) {
println($"• {lang}")
})
// Transform string using an inline closure
let formatted = process_text("hello flame closures", (input: String) -> String {
return $"[PROCESSED]: {input}"
})
println(formatted)
// Compute values using closure arithmetic
let sum = compute(20, 22, (x: Int, y: Int) -> Int {
return x + y
})
println($"Sum: {sum}")
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

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) { ... }), typing client. suggests all ServerClient methods, and bytes. suggests Byte methods.

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-closures
    fmp run --strict-closures
  • Project Configuration (flame.toml): Set closure-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.


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 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 supported
let { name, stats } = user
print($"User {name} is {stats.age} years old.")

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 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
}