Type System

ScarScript is statically typed with full compile-time inference. Every expression has a known type before the program runs.

Primitive Types

TypeGo BackingLiteralsExample
intint42, 0, -7val n = 42
decfloat643.14, 0.5val pi = 3.14
strstring"hello"val s = "hi"
boolbooltrue, falseval ok = true
void—no literalfunctions with no return

int

Signed 64-bit integer. Supports arithmetic, comparison, and modulo.

val count = 42
val sum = 10 + 32       // 42
val half = 42 / 2        // 21 (integer division)

dec (decimal)

64-bit floating point. Use for prices, measurements, and any fractional value.

val price = 99.99
val rate = 0.05
val pi = 3.14159

// Int literals widen to dec automatically
val x: dec = 4           // 4.0
val y = 1 + 2.5         // 3.5
dec vs float: The keyword is dec, not float. The lexer tokenizes 1..5 as a range (1 .. 5), so decimals always require a single dot: 1.5.

str

Immutable UTF-8 string with built-in methods.

val name = "Zawiyar"
val greeting = "Hello, {name}!"

name.len()            // 7
name.upper()          // "ZAWIYAR"
name.lower()          // "zawiyar"
name.contains("awi")  // true

bool

Two values: true and false. Produced by comparisons and logical operators.

val adult = age >= 18
val ok = a == b and c != d
val off = not enabled

Type Inference

Annotations are optional. The checker infers the type from the right-hand side:

val a = 42           // inferred int
val b = 3.14         // inferred dec
val c = "hi"         // inferred str
val d = true         // inferred bool
val e = [1, 2, 3]   // inferred array of int

// Explicit annotation still works and is checked
val f: str = 42      // ERROR: expected str, got int
Inference failure: If the right side has no type (e.g. an empty array with no context), the compiler reports cannot infer type and asks for an annotation.

Tuples & Multiple Returns

Functions can return multiple values as a tuple. Destructure at the call site:

func divide(a: int, b: int) (int, bool) {
    if b == 0 { return 0, false }
    return a / b, true
}

val result, ok = divide(10, 2)   // result=5, ok=true

// Or with explicit types
val q: int, ok: bool = divide(10, 0)

You can also destructure multiple declarations in one statement:

val a, b = 1, 2

Arrays

Homogeneous collections. The element type is inferred from the first element.

val nums = [1, 2, 3]        // []int
val names = ["a", "b"]     // []str
val mixed = [1, "a"]       // ERROR: heterogeneous array

nums.len()               // 3
nums[0]                  // 1

Nullable / Optional

Variables can be declared without a value. The or operator provides the fallback:

val config: str = sys.env("CONFIG") or "default"
val status = data.status or "unknown"

Static Checking Rules

The type checker enforces these rules at compile time. The first error aborts compilation.

Assignment compatibility

val x: str = 42
// error: expected str, got int (line 1, col 11)

Argument checking

math.abs(3.14)
// error: argument 1 expects int, got dec

Missing return

func f() int {
    if true { return 1 }
    // error: missing return
}

Unreachable code

return 1
print("never")
// error: unreachable code

Arithmetic type mismatch

val s = 1 + "a"
// error: cannot add int and str
Error format: All errors follow file:line:col: error: message — compatible with most editors' error parsers.