Variables & Data Types
Chuks provides strictly typed data containers with type inference support.
Variables and Constants
Section titled “Variables and Constants”// Mutable variable with explicit typevar age: int = 25;
// Mutable variable with type inferencevar name = "Alice";
// Immutable constantconst PI = 3.14159;
// Constants must be initialized and cannot be reassignedconst MAX_SIZE: int = 100;Basic Data Types
Section titled “Basic Data Types”Chuks provides a comprehensive set of data types for precise memory and performance control.
| Category | Types | Description | Example |
|---|---|---|---|
| Integers | int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, byte, rune | Signed & unsigned whole numbers | 42, -7, 0 |
| Floats | float, float32, float64 | Floating-point numbers | 3.14, -0.5 |
| Complex | complex64, complex128 | Complex numbers | 1 + 2i |
| String | string | Text strings (UTF-8) | "hello", "world" |
| Boolean | bool | Boolean values | true, false |
| Dynamic | any | Dynamic type (use sparingly) | Any value |
| Void | void | No return value | Used in signatures |
| Null | null | Absence of value | null |
Default Values
Section titled “Default Values”Unlike JavaScript/TypeScript where uninitialized variables are undefined, Chuks zero-initializes typed fields. Every primitive type has a well-defined default value:
| Type | Default Value | Notes |
|---|---|---|
int | 0 | All integer variants (int8, int64, uint, etc.) |
float | 0.0 | Both float32 and float64 |
string | "" | Empty string, not null |
bool | false | |
T? | null | Nullable types default to null |
[]T | — | Must be explicitly initialized ([]) |
map[K]V | — | Must 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); // trueprintln(u.score == 0.0); // trueprintln(u.active == false); // trueprintln(u.bio == null); // trueNumeric Literals
Section titled “Numeric Literals”In addition to standard decimal integers and floats, Chuks supports hexadecimal, octal, and binary integer literals.
| Prefix | Base | Digits | Example | Decimal Value |
|---|---|---|---|---|
0x | Hexadecimal | 0-9, a-f | 0xFF | 255 |
0o | Octal | 0-7 | 0o77 | 63 |
0b | Binary | 0-1 | 0b1010 | 10 |
// Hexadecimal (base 16)var hex = 0xFF; // 255var color = 0xABCD; // 43981var lower = 0xff; // 255 (lowercase also accepted)
// Octal (base 8)var perm = 0o755; // 493var oct = 0o77; // 63
// Binary (base 2)var flags = 0b1010; // 10var byte_val = 0b11111111; // 255var bit = 0b10000000; // 128All numeric literal forms work seamlessly in arithmetic and bitwise expressions:
println(0xFF + 1); // 256println(0o10 * 2); // 16println(0b1010 - 5); // 5println(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; // 51966var oct: int = 0o644; // 420var bin: int = 0b10101010; // 170Collections
Section titled “Collections”For detailed documentation on collection types, see:
- Strings — Immutable character sequences
- Arrays — Ordered, dynamic collections
- Maps — Key-value pairs
The dataType Construct
Section titled “The dataType Construct”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.
Basic Usage
Section titled “Basic Usage”dataType User { id: int; name: string; isActive: bool;}
// Instantiate using object literal syntaxvar u: User = { "id": 1, "name": "Alice", "isActive": true};
print(u.name); // "Alice"Generic Data Types
Section titled “Generic Data Types”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}Constructor-less Initialization
Section titled “Constructor-less Initialization”dataType does not have constructors. Instead, you initialize instances using map literals { key: value }. Types are checked against the dataType schema.
Fields Only — No Methods
Section titled “Fields Only — No Methods”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.
Embedding
Section titled “Embedding”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 owndataType 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 Identifiableprint(u.createdAt); // 1700000000 — promoted from Auditableprint(u.username); // "alex"Rules:
- Embedded fields are addressed by their own name (
u.id, notu.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
dataTypecan itself embed others; all promoted fields surface on the outermost type. - Embedding only composes shape, not behavior.
dataTypestill cannot contain methods.
Field Annotations
Section titled “Field Annotations”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
| Annotation | Placement | Argument | Purpose |
|---|---|---|---|
@validate | dataType field | "rule1,rule2,..." | Attaches validation rules; consumed by the validate(x) builtin |
@json | dataType field | "name" | Renames the field for json.stringify / json.parse |
@validator | top-level function | (none) or "ruleName" | Registers a custom rule for @validate("ruleName") |
Multiple annotations can stack on the same field. Order is not significant.
@validate("rules,...")
Section titled “@validate("rules,...")”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).
| Rule | Applies to | Meaning |
|---|---|---|
required | any | non-empty / non-zero |
email | string | RFC 5322 address |
url | string | absolute URL |
min=N, max=N | numbers | inclusive bounds |
gt=N, gte=N | numbers | strict / inclusive greater-than |
lt=N, lte=N | numbers | strict / inclusive less-than |
len=N | string / array | exact length |
oneof=a b c | string | must equal one of the listed values |
regex=PATTERN | string | matches the Go regular expression PATTERN |
pattern=PATTERN | string | alias for regex= |
uuid | string | RFC 4122 UUID (any version) |
alpha | string | ASCII letters only (A–Z, a–z) |
alphanum | string | ASCII letters and digits |
alphanumeric | string | alias for alphanum |
numeric | string | ASCII digits only (0–9) |
ascii | string | all characters in the 7-bit ASCII range |
| custom name | any | dispatched to a @validator-tagged function |
var u: CreateUser = { "email": "bad", "username": "ab", "age": 200, "role": "root" };var errs: []string = validate(u);print(errs.length); // 4for (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.
Custom validators with @validator
Section titled “Custom validators with @validator”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"),}
@validatorfunction 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
@validatordesugars to a module-levelregister_validator("strongPassword", strongPassword)call that runs beforemain()executes — even when the validator lives in an imported module. Justimportthe 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"asarg).
- The same
@validatorworks 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.
@json("name")
Section titled “@json("name")”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).
| Argument | Required | Applies to | Effect |
|---|---|---|---|
"name" | yes | any dataType field | Use 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. @jsoncomposes 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
Section titled “Nullable Types”Nullable types allow a variable to hold either a value of a specific type or null. Append ? to any type to make it nullable.
Basic Usage
Section titled “Basic Usage”var name: string? = "Alice";name = null; // Valid — string? accepts null
var age: int? = null;age = 25;Function Parameters and Return Types
Section titled “Function Parameters and Return Types”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;}In DataTypes
Section titled “In DataTypes”dataType User { id: int; name: string?; email: string?;}
var user: User = { "id": 1, "name": "Alice", "email": null};In Classes
Section titled “In Classes”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);In Interfaces
Section titled “In Interfaces”interface UserService { findById(id: int): User?; getName(id: int): string?;}Nullable Functions
Section titled “Nullable Functions”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 nullThe ? goes after function, not after the return type. See Nullable Function Types for full details.
Nullable Arrays
Section titled “Nullable Arrays”// Array of nullable stringsvar tags: []string? = ["hello", null, "world"];
// Nullable array itselfvar items: []int? = null;Type Narrowing
Section titled “Type Narrowing”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.
Guard Pattern (Early Return)
Section titled “Guard Pattern (Early Return)”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.
Positive Check
Section titled “Positive Check”function greet(name: string?): string { if (name != null) { // name is narrowed to `string` inside this block return "Hello, " + name.toUpperCase() + "!"; } return "Hello, stranger!";}Else Branch
Section titled “Else Branch”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); }}Narrowing with DataTypes
Section titled “Narrowing with DataTypes”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);}Multiple Variable Narrowing
Section titled “Multiple Variable Narrowing”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;}Narrowing in Loops
Section titled “Narrowing in Loops”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;}Type Conversions
Section titled “Type Conversions”Chuks provides Go-style builtin functions for converting between types:
| Function | Returns | Description | Example |
|---|---|---|---|
int(value) | int | Convert to integer (truncates) | int(3.14) → 3 |
float(value) | float | Convert to float | float(42) → 42.0 |
string(value) | string | Convert any value to string | string(42) → "42" |
bool(value) | bool | Convert to boolean (truthy) | bool(1) → true |
Conversion Rules
Section titled “Conversion Rules”// Float to int (truncates)var x: int = int(3.99) // 3
// Int to floatvar y: float = float(42) // 42.0
// String to intvar z: int = int("123") // 123
// String to floatvar w: float = float("3.14") // 3.14
// Any value to stringvar s: string = string(42) // "42"var s2: string = string(true) // "true"
// Boolean conversionsvar b1: int = int(true) // 1var b2: int = int(false) // 0var b3: float = float(true) // 1.0
// Convert to bool (truthy evaluation)var t1: bool = bool(1) // truevar t2: bool = bool(0) // falsevar t3: bool = bool("hello") // truevar t4: bool = bool("") // falsevar t5: bool = bool(null) // falseThese conversions work in both the VM interpreter and AOT-compiled code.