ddonche/goblin-lang
0.46.24
1
0
docs reference
[[data-types]]

Data Types


Data types describe what kind of value something is. When you write a string, Goblin knows it is text. When you write a number, Goblin knows it is numeric. When you create an array, Goblin knows it is a collections. Every value in Goblin has a type.

Most of the time, you do not need to think about types. Goblin figures them out automatically from the values you write.

name   | "Daniel"
age    | 46
active | true
name   | "Daniel"
age    | 46
active | true


Type Inspection

You can inspect the type of any value using value type, which has four equivalent forms:

age.vt
age.valtype
vt(age)
valtype(age)
age.vt
age.valtype
vt(age)
valtype(age)

All four do the same thing. Use whichever reads most naturally in context. Output is a string representation of the type — int, str, bool, and so on. What gets reported depends on how the variable was declared, which is covered in Type-Locked Variables.

Note
vt is just shorthand for valtype, or "value type".

Primitive Types

Primitive types represent a single value.

Strings

A string is text — any sequence of characters wrapped in double quotes.

name    | "Daniel"
greeting | "Hello, world"
empty   | ""
name    | "Daniel"
greeting | "Hello, world"
empty   | ""

Type: str

Booleans

A boolean is a value that is either true or false. Nothing else.

active  | true
deleted | false
active  | true
deleted | false

Type: bool

Booleans are the result of comparisons and conditions throughout Goblin. You rarely assign true or false directly — more often a boolean comes from evaluating something.

Integers

An integer is a whole number. No decimal point, no fractional component — just a complete number.

Goblin has two flavors of integer: signed and unsigned.

Signed integers can hold positive or negative values. The word "signed" refers to the sign — the + or in front of a number. A signed integer can carry that sign.

temperature | -12
altitude    | 3400
balance     | -500
temperature | -12
altitude    | 3400
balance     | -500

Type: int

The specific signed integer types are i8, i16, i32, and i64. The number indicates how many bits are used to store the value, which determines its range. i32 can hold values from roughly −2 billion to +2 billion. i64 extends that to numbers large enough to cover any practical use. When you write a plain integer and do not specify a type, Goblin infers int, which maps to i64 by default.

Unsigned integers can only hold zero or positive values. There is no negative sign — hence "unsigned." This makes them appropriate for values that can never logically be negative, such as counts, indexes, or sizes.

item_count | 42
byte_size  | 1024
item_count | 42
byte_size  | 1024

The unsigned integer types are u8, u16, u32, and u64. The same bit-width rules apply. A u8 holds 0 through 255. A u64 holds 0 through an enormous positive ceiling.

Floating Point Numbers

A float is a number that can have a decimal component.

score    | 99.5
ratio    | 0.333
latitude | 40.7128
score    | 99.5
ratio    | 0.333
latitude | 40.7128

Type: float

The specific float types are f32 and f64. The difference is precision — f64 gives you more decimal places before the value becomes approximate. When you write a decimal number without specifying a type, Goblin infers float, which maps to f64.

Floats are stored as binary approximations of decimal values, which means they are not perfectly precise for every number. For financial calculations or anything requiring exact decimal arithmetic, use money or big instead.

Arbitrary Precision Numbers

The big type holds integers of unlimited size — well beyond what i64 can store. If you are working with values that reach into the hundreds of digits (cryptographic work, combinatorics, astronomical counts), big handles them without overflow.

factorial_100 | 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
factorial_100 | 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000

Type: big

Unlike floats, big is exact — no approximation. It is however slower than native integer types, so use it only when you actually need it.


Collection Types

Collections store multiple values.

Arrays

An array is an ordered list of values.

nums | [1, 2, 3]
nums | [1, 2, 3]

Type: array

By default, an array can hold any mix of types.

mixed | [1, "two", true]
mixed | [1, "two", true]

To restrict an array so that every element must be a specific type, declare the type at creation.

zip_codes.int | [90210, 80924]
zip_codes.int | [90210, 80924]

Type: array(int)

Once type-locked, Goblin validates every element on creation and on every future retether. A value that cannot be converted to the declared type is rejected.

zip_codes |= [10001, 94102]  -- valid
zip_codes |= ["bad"]         -- error: "bad" cannot be converted to int
zip_codes |= [10001, 94102]  -- valid
zip_codes |= ["bad"]         -- error: "bad" cannot be converted to int

Retethering replaces the entire array. There is no partial update — the old value is gone and the new one takes its place.

Maps

A map stores key-value pairs, where each key is unique.

person | {
    name: "Daniel"
    age:  46
}
person | {
    name: "Daniel"
    age:  46
}

Type: map

Values can be any type. Keys are always strings.

To restrict all values in a map to a specific type, declare the type at creation.

scores.int | { alice: 95, bob: 87 }
scores.int | { alice: 95, bob: 87 }

Type: map(int)

When type-locked, every value must match the declared type. Keys are not affected by the lock. Retethering replaces the entire map.


Domain Types

Note
Planned — Domain types are designed and reserved in Goblin but not yet implemented. The syntax and type names below reflect the intended final form.

Most languages represent money as a float and dates as strings or integers. Goblin treats them as distinct first-class types because they have rules that raw numbers do not. Money has currency semantics and rounding behavior. Dates have calendar semantics. A percentage is not 0.0825 — it is 8.25%, and those are meaningfully different values.

Percentages

tax | 8.25%
tax | 8.25%

Type: pct

Money

price | $19.99
price | $19.99

Type: money

Dates

birthday | 1981-07-28
birthday | 1981-07-28

Type: date

Times

start | 14:30
start | 14:30

Type: time

DateTimes

meeting | 2026-06-07 14:30
meeting | 2026-06-07 14:30

Type: datetime

Durations

travel_time | 3h
cooldown    | 30s
travel_time | 3h
cooldown    | 30s

Type: duration


Enumerations

An enumeration defines a fixed set of named values. Once you define an enum, any variable using it can only ever hold one of those names — nothing else is valid.

enum Status
    idle
    loading
    ready
    error
end
enum Status
    idle
    loading
    ready
    error
end

Enum values are accessed through the enum name.

status | Status::idle
status | Status::idle

Type: enum

Enums pair naturally with judge when each possible value should produce a different result.

judge status using Status
    idle:    :say("System is idle")
    loading: :say("Processing...")
    ready:   :say("Ready to proceed")
    error:   :say("Error occurred")
end
judge status using Status
    idle:    :say("System is idle")
    loading: :say("Processing...")
    ready:   :say("Ready to proceed")
    error:   :say("Error occurred")
end


Objects

Objects are instances of classes. They combine named fields with actions into a single structured value.

<>Kingdom | name: "", gold: 0
rome <> Kingdom | name: "Rome", gold: 1000
<>Kingdom | name: "", gold: 0
rome <> Kingdom | name: "Rome", gold: 1000

Type: object

Objects are covered in depth in the Classes section.


Inferred Variables

Variables are inferred by default. Goblin looks at the value on the right and determines the type automatically.

score | 100
score | 100

Because no type was declared, score is not locked. It can be retethered to a completely different type later.

score |= "one hundred"  -- valid
score |= "one hundred"  -- valid


Type-Locked Variables

When you want a variable to always hold a specific type, declare the type at creation using dot notation.

score.i32 | 100
score.i32 | 100

From that point on, every assignment must produce a value compatible with i32. Goblin will attempt a conversion if needed.

score |= 101    -- valid
score |= "102"  -- valid: "102" converts to i32
score |= "abc"  -- error: cannot convert to i32
score |= 101    -- valid
score |= "102"  -- valid: "102" converts to i32
score |= "abc"  -- error: cannot convert to i32

Type locks work on any type, including collections.

zip_codes.int  | [90210, 80924]
scores.float   | { alice: 9.8, bob: 8.5 }
zip_codes.int  | [90210, 80924]
scores.float   | { alice: 9.8, bob: 8.5 }

What .vt reports changes based on whether a variable is locked.

age | 46
age.vt      -- int  (runtime type)

age.i32 | 46
age.vt      -- i32  (declared lock type)

zip_codes.int | [90210, 80924]
zip_codes.vt  -- array(int)
age | 46
age.vt      -- int  (runtime type)

age.i32 | 46
age.vt      -- i32  (declared lock type)

zip_codes.int | [90210, 80924]
zip_codes.vt  -- array(int)


Immutable Variables

Immutable variables are assigned once and cannot be changed.

imm version | "1.0.0"
version |= "1.0.1"  -- error
imm version | "1.0.0"
version |= "1.0.1"  -- error

Type locks and immutability can be combined.

imm max_retries.i32 | 5
imm max_retries.i32 | 5


Casting

Casting converts a value from one type to another.

A temporary cast returns the converted value without modifying the original variable.

str(age)    -- returns age as a string
age.str     -- same thing, postfix form

f64(age)    -- returns age as a float
age.f64
str(age)    -- returns age as a string
age.str     -- same thing, postfix form

f64(age)    -- returns age as a float
age.f64

A recast permanently changes the variable's stored value and type. The ! marks it as a mutation.

str!(age)   -- age is now a string
age.str!    -- same thing, postfix form
str!(age)   -- age is now a string
age.str!    -- same thing, postfix form

A type-locked variable can only be recast to its own declared type. Trying to recast to a different type raises an error.