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