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

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: Vec<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]

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 host = record.0
let port = record.1

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