Skip to content

Variables & Data Types

Chuks provides strictly typed data containers with type inference support.

// Mutable variable with explicit type
var age: int = 25;
// Mutable variable with type inference
var name = "Alice";
// Immutable constant
const PI = 3.14159;
// Constants must be initialized and cannot be reassigned
const MAX_SIZE: int = 100;

Chuks provides a comprehensive set of data types for precise memory and performance control.

CategoryTypesDescriptionExample
Integersint, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, byte, runeSigned & unsigned whole numbers42, -7, 0
Floatsfloat, float32, float64Floating-point numbers3.14, -0.5
Complexcomplex64, complex128Complex numbers1 + 2i
StringstringText strings (UTF-8)"hello", "world"
BooleanboolBoolean valuestrue, false
DynamicanyDynamic type (use sparingly)Any value
VoidvoidNo return valueUsed in signatures
NullnullAbsence of valuenull

Unlike JavaScript/TypeScript where uninitialized variables are undefined, Chuks zero-initializes typed fields. Every primitive type has a well-defined default value:

TypeDefault ValueNotes
int0All integer variants (int8, int64, uint, etc.)
float0.0Both float32 and float64
string""Empty string, not null
boolfalse
T?nullNullable types default to null
[]TMust be explicitly initialized ([])
map[K]VMust be explicitly initialized ({})
class User {
var name: string; // defaults to ""
var age: int; // defaults to 0
var score: float; // defaults to 0.0
var active: bool; // defaults to false
var bio: string?; // defaults to null
constructor() {}
}
var u = new User();
println(u.name == ""); // true (not null!)
println(u.age == 0); // true
println(u.score == 0.0); // true
println(u.active == false); // true
println(u.bio == null); // true

In addition to standard decimal integers and floats, Chuks supports hexadecimal, octal, and binary integer literals.

PrefixBaseDigitsExampleDecimal Value
0xHexadecimal0-9, a-f0xFF255
0oOctal0-70o7763
0bBinary0-10b101010
// Hexadecimal (base 16)
var hex = 0xFF; // 255
var color = 0xABCD; // 43981
var lower = 0xff; // 255 (lowercase also accepted)
// Octal (base 8)
var perm = 0o755; // 493
var oct = 0o77; // 63
// Binary (base 2)
var flags = 0b1010; // 10
var byte_val = 0b11111111; // 255
var bit = 0b10000000; // 128

All numeric literal forms work seamlessly in arithmetic and bitwise expressions:

println(0xFF + 1); // 256
println(0o10 * 2); // 16
println(0b1010 - 5); // 5
println(0xFF & 0x0F); // 15 (bitwise AND)
println(0b11110000 | 0b00001111); // 255 (bitwise OR)

You can store them in typed variables just like any other integer:

var hex: int = 0xCAFE; // 51966
var oct: int = 0o644; // 420
var bin: int = 0b10101010; // 170

For detailed documentation on collection types, see:

  • Strings — Immutable character sequences
  • Arrays — Ordered, dynamic collections
  • Maps — Key-value pairs

dataType is a lightweight structure for grouping data fields. It’s similar to structs in Go or records in TypeScript. Unlike classes, dataType is intended for simple data containers.

dataType User {
id: int;
name: string;
isActive: bool;
}
// Instantiate using object literal syntax
var u: User = {
"id": 1,
"name": "Alice",
"isActive": true
};
print(u.name); // "Alice"

Data types support generics for flexible data structures.

dataType Point<T> {
x: T;
y: T;
}
dataType Pair<K, V> {
first: K;
second: V;
}
function main() {
// Point of integers
var p1: Point<int> = { "x": 10, "y": 20 };
// Point of strings
var p2: Point<string> = { "x": "ten", "y": "twenty" };
// Pair of different types
var kv: Pair<string, int> = { "first": "age", "second": 30 };
print(p1.x); // 10
print(kv.second); // 30
}

dataType does not have constructors. Instead, you initialize instances using map literals { key: value }. Types are checked against the dataType schema.

dataType is a fields-only container, similar to structs in Go or records in TypeScript. It cannot contain methods, access modifiers, or any behavior. The compiler rejects them at parse time:

// ❌ Error: methods are not allowed inside a 'dataType'
dataType Vector {
x: int;
y: int;
function magnitude(): float { // parse error
return 0.0;
}
}

If you need behavior alongside your data, use a class instead. This keeps the dataType vs. class distinction crisp: dataType describes shape, class carries behavior.

A dataType can embed other dataTypes to compose its shape. Fields from the embedded type are promoted into the outer type, so you can read and write them directly without a wrapper field. This is the same pattern Go uses for struct embedding, and replaces inheritance for data shapes.

dataType Auditable {
createdAt: int;
updatedAt: int;
}
dataType Identifiable {
id: string;
}
// `User` gets all fields from Auditable + Identifiable, plus its own
dataType User {
Auditable;
Identifiable;
username: string;
email: string;
}
var u: User = {
"id": "u_1",
"username": "alex",
"email": "alex@example.com",
"createdAt": 1700000000,
"updatedAt": 1700000000,
};
print(u.id); // "u_1" — promoted from Identifiable
print(u.createdAt); // 1700000000 — promoted from Auditable
print(u.username); // "alex"

Rules:

  • Embedded fields are addressed by their own name (u.id, not u.Identifiable.id).
  • Duplicate field names across embedded types are a compile error — pick a unique name in the outer type or rename the embedded field.
  • Embedding chains: an embedded dataType can itself embed others; all promoted fields surface on the outermost type.
  • Embedding only composes shape, not behavior. dataType still cannot contain methods.

Fields can carry one or more @name("arg1,arg2,...") annotations placed after the type. Annotations attach metadata that the compiler emits as tags and the runtime uses for introspection.

dataType CreateUser {
email: string @validate("required,email"),
username: string @validate("required,min=3,max=32"),
age: int @validate("gte=0,lte=150"),
role: string @validate("oneof=admin user guest") @json("user_role"),
}

Built-in annotations

AnnotationPlacementArgumentPurpose
@validatedataType field"rule1,rule2,..."Attaches validation rules; consumed by the validate(x) builtin
@jsondataType field"name"Renames the field for json.stringify / json.parse
@validatortop-level function(none) or "ruleName"Registers a custom rule for @validate("ruleName")

Multiple annotations can stack on the same field. Order is not significant.

Attaches validation rules to a field. The runtime builtin validate(x) walks every field of a typed dataType instance, evaluates its rules, and returns a []string of error messages (empty when the value is valid).

RuleApplies toMeaning
requiredanynon-empty / non-zero
emailstringRFC 5322 address
urlstringabsolute URL
min=N, max=Nnumbersinclusive bounds
gt=N, gte=Nnumbersstrict / inclusive greater-than
lt=N, lte=Nnumbersstrict / inclusive less-than
len=Nstring / arrayexact length
oneof=a b cstringmust equal one of the listed values
regex=PATTERNstringmatches the Go regular expression PATTERN
pattern=PATTERNstringalias for regex=
uuidstringRFC 4122 UUID (any version)
alphastringASCII letters only (A–Z, a–z)
alphanumstringASCII letters and digits
alphanumericstringalias for alphanum
numericstringASCII digits only (0–9)
asciistringall characters in the 7-bit ASCII range
custom nameanydispatched to a @validator-tagged function
var u: CreateUser = { "email": "bad", "username": "ab", "age": 200, "role": "root" };
var errs: []string = validate(u);
print(errs.length); // 4
for (var i: int = 0; i < errs.length; i = i + 1) { print(errs[i]); }

Multiple @validate(...) annotations on a single field are merged. validate(x) returns [] for plain (non-dataType) maps — only typed dataType literals carry the class metadata required for introspection.

Any rule name that isn’t built in (e.g. strongPassword below) is dispatched to a user-defined function registered with the @validator annotation:

dataType Account {
handle: string @validate("required,alphanum,min=3,max=20"),
password: string @validate("required,strongPassword"),
}
@validator
function strongPassword(v: any): string {
var s: string = string(v);
if (s.length < 8) {
return "password must be at least 8 chars"
}
return "" // empty string ⇒ valid
}
function main() {
var a: Account = { "handle": "alex", "password": "short" };
print(validate(a)); // ["password must be at least 8 chars"]
}

How it works

  • @validator desugars to a module-level register_validator("strongPassword", strongPassword) call that runs before main() executes — even when the validator lives in an imported module. Just import the module and the rule is available.
  • A validator function returns "" for valid input, or an error message string for invalid input.
  • The rule name defaults to the function name. Pass an explicit name to use a different rule key: @validator("password").
  • Two arities are supported:
    • function fn(value: any): string — for rules used as a bare name (@validate("strongPassword")).
    • function fn(value: any, arg: string): string — for parameterized rules (@validate("strongPassword=12") passes "12" as arg).
  • The same @validator works in both VM and AOT, in single-module and cross-module programs.

If you prefer to register imperatively (e.g. when building a validator dynamically), call register_validator(name: string, fn: any): void directly — @validator is just sugar for that call.

Renames the field for JSON serialization. The argument becomes the JSON key emitted by json.stringify(x) and the lookup key consumed by json.parse(s, T).

ArgumentRequiredApplies toEffect
"name"yesany dataType fieldUse name as the JSON key on output and as the lookup key on input.

Notes

  • Without @json, the field’s source name is used as-is for both serialization and parsing.
  • Only one @json(...) per field. Stacking multiple is a parse error.
  • The renamed key is also what json.parse(input, T) looks up — input that uses the original field name will not populate the field.
  • @json composes with @validate: validation runs against the field value regardless of the JSON key.
dataType ApiUser {
id: string @json("_id"),
createdAt: int @json("created_at"),
role: string @json("user_role"),
}
var u: ApiUser = { "id": "u_1", "createdAt": 1700000000, "role": "admin" };
print(json.stringify(u)); // {"_id":"u_1","created_at":1700000000,"user_role":"admin"}

The annotation system is generic: any @name("…") produces a corresponding name:"…" entry in the underlying Go struct tag, so future annotations (@db, @form, custom ones) compose with the same syntax.

Nullable types allow a variable to hold either a value of a specific type or null. Append ? to any type to make it nullable.

var name: string? = "Alice";
name = null; // Valid — string? accepts null
var age: int? = null;
age = 25;
function findUser(id: int): string? {
if (id == 1) {
return "Alice";
}
return null;
}
function greet(name: string?): string {
if (name == null) {
return "Hello, stranger!";
}
return "Hello, " + name;
}
dataType User {
id: int;
name: string?;
email: string?;
}
var user: User = {
"id": 1,
"name": "Alice",
"email": null
};
class UserProfile {
var name: string;
var bio: string?;
constructor(name: string, bio: string?) {
this.name = name;
this.bio = bio;
}
}
var profile = new UserProfile("Alice", null);
interface UserService {
findById(id: int): User?;
getName(id: int): string?;
}

Use function? to declare a function-typed variable or field that can be null:

var handler: function?(msg: string): void = null
handler = function(msg: string): void {
println(msg)
}
handler("hello") // hello
handler = null // reset to null

The ? goes after function, not after the return type. See Nullable Function Types for full details.

// Array of nullable strings
var tags: []string? = ["hello", null, "world"];
// Nullable array itself
var items: []int? = null;

Chuks performs control flow analysis on nullable types. When you check for null, the compiler automatically narrows the type inside the guarded block — no explicit cast needed.

function process(name: string?): string {
if (name == null) {
return "unknown";
}
// name is narrowed to `string` here
return name.toUpperCase();
}

After the if (name == null) { return } guard, the compiler knows name cannot be null for the rest of the function.

function greet(name: string?): string {
if (name != null) {
// name is narrowed to `string` inside this block
return "Hello, " + name.toUpperCase() + "!";
}
return "Hello, stranger!";
}
function describe(value: int?): string {
if (value == null) {
return "no value";
} else {
// value is narrowed to `int` in the else branch
return "value is " + string(value * 2);
}
}
dataType Config {
host: string?
port: int?
}
function getAddress(cfg: Config?): string {
if (cfg == null) { return "default:80"; }
// cfg is narrowed to Config
var host: string = cfg.host ?? "localhost";
var port: int = cfg.port ?? 80;
return host + ":" + string(port);
}
function combine(a: string?, b: string?): string {
if (a == null || b == null) { return "incomplete"; }
// both a and b are narrowed to string
return a + "+" + b;
}
function filterNulls(items: []any): []string {
var result: []string = [];
for (var item of items) {
if (item == null) { continue; }
// item is narrowed to non-null
result.push(string(item));
}
return result;
}

Chuks provides Go-style builtin functions for converting between types:

FunctionReturnsDescriptionExample
int(value)intConvert to integer (truncates)int(3.14)3
float(value)floatConvert to floatfloat(42)42.0
string(value)stringConvert any value to stringstring(42)"42"
bool(value)boolConvert to boolean (truthy)bool(1)true
// Float to int (truncates)
var x: int = int(3.99) // 3
// Int to float
var y: float = float(42) // 42.0
// String to int
var z: int = int("123") // 123
// String to float
var w: float = float("3.14") // 3.14
// Any value to string
var s: string = string(42) // "42"
var s2: string = string(true) // "true"
// Boolean conversions
var b1: int = int(true) // 1
var b2: int = int(false) // 0
var b3: float = float(true) // 1.0
// Convert to bool (truthy evaluation)
var t1: bool = bool(1) // true
var t2: bool = bool(0) // false
var t3: bool = bool("hello") // true
var t4: bool = bool("") // false
var t5: bool = bool(null) // false

These conversions work in both the VM interpreter and AOT-compiled code.