-
Notifications
You must be signed in to change notification settings - Fork 0
Common Mistakes
Mistakes rooted in Jai's unique syntax — variable declarations, operators, and statement formatting that differ from C, C++, or other languages.
Problem: Placing the variable type declaration before the variable identifier.
// ❌ WRONG - Placing the variable type declaration before the variable identifier
int x;
Solution: Place the variable identifier before the type declaration.
// ✅ CORRECT
x: int;Problem: Using C-style function syntax in Jai.
// ❌ WRONG - This is not valid Jai syntax
int function(int a, int b) {
}
Solution: Format functions using the correct Jai syntax, placing identifiers before types.
// ✅ CORRECT
function :: (a: int, b: int) -> int {
}Problem: Forgetting to end a statement with a semicolon.
// ❌ WRONG - missing semicolon
z := x + y
Solution: End every statement with a semicolon.
// ✅ CORRECT
z := x + y;Problem: Using := to define a function creates a variable holding a function value, not a named constant procedure.
// ❌ WRONG - this declares a variable, not a constant procedure
func := () {
print("hello\n");
}Solution: Use :: to declare a constant procedure.
// ✅ CORRECT
func :: () {
print("hello\n");
}Problem: Using := everywhere out of habit creates a new shadowing variable in the current scope instead of assigning to an existing one.
x := 10;
if true {
// ❌ WRONG - creates a NEW x, shadows the outer x
x := 99;
}Solution: Use = to assign to an existing variable.
x := 10;
if true {
// ✅ CORRECT
x = 99;
}Problem: Attempting to use C-style increment/decrement operators.
// ❌ WRONG - ++/-- are not in the language
i++;
i--;
Solution: Use += 1 and -= 1 instead.
// ✅ CORRECT
i += 1;
i -= 1;Problem: Using C++-style ternary operator syntax.
// ❌ WRONG - C++ style ternary
int x = 1 < 2 ? 1 : 2;
Solution: Use the ifx keyword.
// ✅ CORRECT
x: int = ifx 1 < 2 then 1 else 2;Problem: Using the C++ auto keyword for type inference.
// ❌ WRONG - 'auto' is not valid Jai syntax
auto x = 10;
Solution: Use := for type inference.
// ✅ CORRECT
x := 10;Problem: Separating struct members with commas instead of semicolons.
// ❌ WRONG - commas between struct members
Vec3 :: struct {
x: float,
y: float,
z: float
}Solution: Separate struct members with semicolons.
// ✅ CORRECT
Vec3 :: struct {
x: float;
y: float;
z: float;
}Problem: There is no preprocessing step and no header/implementation split in Jai.
// ❌ WRONG - no #include directive in Jai
#include <stdio.h>
Solution: Use #import for modules and #load for other .jai files.
// ✅ CORRECT
#import "Basic";
#load "my_utils.jai";Problem: Placing a note annotation before the function body like in Java or Python.
// ❌ WRONG - note must not be placed before the function
@note
function :: () {
// function body...
}Solution: Place the note after the closing curly brace of the function body.
// ✅ CORRECT
function :: () {
// function body...
} @noteMistakes related to Jai's strict type system — casting, type mismatches, and implicit conversion rules.
Problem: Assigning a value of the wrong type to a variable.
// ❌ WRONG - cannot assign a string to an int
x: int = "Pacman";Solution: Match the value to the declared type.
// ✅ CORRECT
x: int = 10;Problem: Using C-style or C++-style cast syntax.
// ❌ WRONG - C++ casting syntax
float f = (float)some_int;
float f2 = static_cast<float>(some_int);
Solution: Use cast(type) or xx for autocast.
// ✅ CORRECT
f := cast(float) some_int;
f2 := xx some_int; // autocastProblem: Passing a value to a function without casting it to the required type.
function :: (a: u8) {
// function body...
}
a: int = 127;
// ❌ WRONG - passing an int where u8 is expected
function(a);Solution: Cast the value to the correct type before passing it.
// ✅ CORRECT
function(cast(u8) a);Problem: Expecting C-style implicit narrowing conversions to work silently.
// ❌ WRONG - s64 is bigger than s32
x: s64 = 1000000000000;
y: s32 = x;Solution: Use an explicit cast and be aware of value ranges.
// ✅ CORRECT - explicit cast with range awareness
x: s64 = 1000000000000;
if x >= S32_MIN && x <= S32_MAX {
y: s32 = cast(s32) x;
} else {
// handle error
}Problem: Jai does not allow implicit signed-to-unsigned conversions like C does.
// ❌ WRONG - comparing s32 and u32 directly
a: s32 = -1;
b: u32 = 1;
if a < b { ... }Solution: Make types consistent or handle the sign explicitly.
// ✅ CORRECT
a: s32 = -1;
b: u32 = 1;
if a < 0 || cast(u32) a < b {
print("Clear intent\n");
}Problem: Attempting to use const on function parameters like in C++.
// ❌ WRONG - 'const' is not a keyword in Jai
void foo(const int x) { ... }
Solution: Simply declare the parameter normally. Constants use :: at declaration time.
// ✅ CORRECT
foo :: (x: int) { ... }Problem: Mistaking a function pointer type (int) for an integer type.
// ❌ WRONG - this is a function pointer, not an integer
a: (int) = (1);Solution: Assign a function with the matching signature to the function pointer.
// ✅ CORRECT
function :: (a: int) { }
a: (int) = function;Problem: Assuming (int, int) is a tuple. In Jai it is a function pointer type.
// ❌ WRONG - this is a function pointer, not a tuple
a: (int, int) = (1, 2);Solution: Assign a matching function to the function pointer variable.
// ✅ CORRECT
function :: (a: int, b: int) { }
a: (int, int) = function;Problem: Passing a raw Jai string's .data pointer to a C function expecting a null-terminated string. Jai strings carry a count and a data pointer with no null byte at the end.
// ❌ WRONG - s.data is NOT null-terminated
s := "hello";
c_style_strlen(s.data);Solution: Use to_c_string to convert to a null-terminated string first.
// ✅ CORRECT
s := "hello";
c_style_strlen(to_c_string(s));Problem: Referencing an enum value by its bare name without qualification.
Direction :: enum { NORTH; SOUTH; EAST; WEST; }
// ❌ WRONG - bare name does not resolve
d := NORTH;Solution: Use full qualification, or the . shorthand when the type is known from context.
// ✅ CORRECT - full qualification
d := Direction.NORTH;
// ✅ CORRECT - shorthand when type is known
d: Direction = .NORTH;Mistakes involving loops, branching, if-case statements, defer, and scope behavior.
Problem: Jai has no switch keyword.
// ❌ WRONG - switch does not exist in Jai
switch x {
case 0: print("zero\n");
case 1: print("one\n");
}
Solution: Use if variable == { followed by case statements. Note that each case ends with a semicolon, not a colon.
// ✅ CORRECT
if x == {
case 0; print("zero\n");
case 1; print("one\n");
case; print("other\n"); // default
}Problem: In Jai, break inside an if-case breaks out of an enclosing loop, not the case block. Cases don't fall through by default, so no break is ever needed.
for 0..5 {
if it == {
case 2;
print("two\n");
break; // ❌ WRONG - this breaks the for loop, not the case!
case 3;
print("three\n");
}
}Solution: Remove the break. If you want C-style fallthrough, use #through; explicitly.
// ✅ CORRECT
for 0..5 {
if it == {
case 2; print("two\n");
case 3; print("three\n");
}
}Problem: The if-case syntax requires if variable == {. Omitting == causes a compile error or unexpected behavior.
// ❌ WRONG - missing '=='
if x {
case 0; print("zero\n");
}Solution: Always include == after the variable.
// ✅ CORRECT
if x == {
case 0; print("zero\n");
}Problem: There is no else if inside a if x == {} block. Only case statements are valid there.
// ❌ WRONG - mixing if-else with if-case
if x == {
case 0;
print("zero\n");
else if x == 1 // syntax error
print("one\n");
}Solution: Add another case statement instead.
// ✅ CORRECT
if x == {
case 0; print("zero\n");
case 1; print("one\n");
}Problem: Jai's for loop range a..b is inclusive on both ends. Using for i : 0..arr.count will attempt to access arr[arr.count], which is out of bounds.
// ❌ WRONG - range is inclusive, so this accesses arr[10]
arr: [10] int;
for i : 0..arr.count {
print("%\n", arr[i]);
}Solution: Use arr.count - 1 as the upper bound.
// ✅ CORRECT
arr: [10] int;
for i : 0..arr.count-1 {
print("%\n", arr[i]);
}Problem: When you for over an array normally, it is a copy of the element. Modifications to it don't affect the original array.
array := int.[1, 2, 3, 4, 5];
// ❌ WRONG - modifies a copy, original is unchanged
for array {
it *= 2;
}Solution: Iterate by pointer using for * to modify elements in place.
// ✅ CORRECT
for * array {
it.* *= 2;
}Problem: defer executes at the end of the current scope, not the end of the function. Inside a loop body, a deferred statement runs at the end of each iteration, not when the function returns.
for 0..2 {
defer print("deferred\n");
print("loop body\n");
}
// Output order: loop body, deferred, loop body, deferred, loop body, deferredKeep this in mind when using defer for cleanup: a defer free(ptr) inside a loop frees the pointer every iteration.
Problem: Inner functions cannot access variables from their outer function. Closures are not supported in Jai.
function :: () {
a := 0;
inner_function :: () {
// ❌ WRONG - cannot capture variable from outer scope
a += 1;
}
inner_function();
}Solution: Pass the variable explicitly as a pointer parameter.
// ✅ CORRECT
function :: () {
a := 0;
inner_function :: (a: *int) {
a.* += 1;
}
inner_function(*a);
}Problem: Lambda expressions in Jai do not support closures — they cannot capture variables from the surrounding scope.
function :: () {
a := 0;
// ❌ WRONG - lambdas cannot capture outer variables
lambda :: () => a;
}Solution: Pass the value as a parameter to the lambda instead.
// ✅ CORRECT
function :: () {
a := 0;
lambda :: (a: int) => a;
}Mistakes involving pointers, heap allocation, dynamic arrays, and memory management.
Problem: Using C++'s & operator to take the address of a variable.
// ❌ WRONG - '&' is not the address-of operator in Jai
int a = 5;
int* b = &a;
Solution: Use * to take the address of a variable in Jai.
// ✅ CORRECT
a : int = 5;
b : *int = *a;Problem: Using C's *b prefix syntax to dereference a pointer.
// ❌ WRONG - C-style pointer dereference
int *b;
int val = *b;
Solution: Use the postfix b.* syntax to dereference a pointer in Jai.
// ✅ CORRECT
b: *int;
val := b.*;Problem: C++ uses -> to access struct members through a pointer. Jai uses . everywhere.
// ❌ WRONG - '->' is not valid in Jai
person->name = "Alice";
Solution: Use . whether person is a value or a pointer.
// ✅ CORRECT
person.name = "Alice";Problem: In Jai, New is a regular function, not a built-in keyword like in C++.
// ❌ WRONG - 'new' is not a keyword in Jai
Object* obj = new Object();
delete obj;
Solution: Call New() to heap-allocate, and free() to release.
// ✅ CORRECT
obj := New(Object);
free(obj);Problem: Returning a pointer to a stack-allocated variable. The variable goes out of scope when the function returns, leaving the pointer dangling.
// ❌ WRONG - v goes out of scope, pointer dangles
create_vector :: () -> *Vector3 {
v: Vector3;
v.x = 1.0;
return *v;
}Solution: Return by value, or heap-allocate with New.
// ✅ CORRECT - return by value
create_vector :: () -> Vector3 {
v: Vector3;
v.x = 1.0;
return v;
}Problem: Calling free on the same pointer twice causes a crash.
// ❌ WRONG - double free
data := alloc(size_of(MyStruct));
free(data);
free(data); // CRASHSolution: Free the memory only once.
// ✅ CORRECT
data := alloc(size_of(MyStruct));
free(data);Problem: Dynamic arrays allocate heap memory that is not automatically freed when they go out of scope.
// ❌ WRONG - memory leak
process_data :: () {
items: [..] int;
array_add(*items, 1);
array_add(*items, 2);
// Function ends — memory leaked!
}Solution: Use defer array_free to guarantee cleanup.
// ✅ CORRECT
process_data :: () {
items: [..] int;
defer array_free(items);
array_add(*items, 1);
array_add(*items, 2);
}Problem: Passing a dynamic array by value to a function means array_add modifies a copy, not the original.
// ❌ WRONG - modifies a copy
function :: (arr: [..] int) {
array_add(*arr, 42);
}Solution: Pass the array by pointer so modifications affect the original.
// ✅ CORRECT
function :: (arr: *[..] int) {
array_add(arr, 42);
}Problem: Adding items to a dynamic array one at a time without reserving space causes repeated reallocations as the array grows.
// ❌ WRONG - many allocations as the array grows
arr: [..] int;
for 0..999 array_add(*arr, it);Solution: Reserve space upfront when the count is known.
// ✅ CORRECT - one allocation up front
arr: [..] int;
array_reserve(*arr, 1000);
for 0..999 array_add(*arr, it);Problem: The = --- syntax explicitly marks a variable as uninitialized for performance. Reading such a variable before writing to it is undefined behavior.
// ❌ WRONG - undefined behavior if read before assignment
x: int = ---;
print("%\n", x);Solution: Let Jai zero-initialize by default unless you have a specific performance reason.
// ✅ CORRECT - safely zero-initialized
x: int;Mistakes that come from applying C++, Java, or Rust mental models to Jai's deliberate design choices around OOP, metaprogramming, and assembly.
Problem: In Jai, functions do not "belong" to objects. There is no implicit self or this.
Object :: struct {
x: int;
// ❌ WRONG - no concept of self/this in Jai
set_x :: (x: int) {
self.x = x;
}
}Solution: Write a regular function that takes a pointer to the struct as an explicit parameter.
// ✅ CORRECT
Object :: struct {
x: int;
}
set_x :: (self: *Object, x: int) {
self.x = x;
}Problem: Assuming struct initialization works like a C++ constructor. Jai has no constructors.
// ❌ WRONG - assuming a constructor is called automatically
Object obj;
Solution: Write an explicit init function and call it yourself.
// ✅ CORRECT
obj: Object;
init(*obj);Problem: Jai's print does not use C-style format specifiers like %d or %f. % is a universal placeholder for any type.
// ❌ WRONG - C-style format specifiers are not valid in Jai
printf("Value: %d, float: %f\n", x, y);
Solution: Use % for all values regardless of type.
// ✅ CORRECT
print("Value: %, float: %\n", x, y);Problem: Jai's #asm blocks do not support jmp, labels, or call instructions.
#asm {
// ❌ WRONG - labels and jmp are not supported
label:
jmp label;
}Solution: Use Jai's high-level control flow (while, for, if) for branching. Reserve #asm for arithmetic and SIMD operations.
// ✅ CORRECT
while true {
// loop body
}Problem: Jai's #asm blocks require you to define and declare a register identifier before using it.
#asm {
// ❌ WRONG - unidentified register
mov.64 the_register, 10;
}Solution: Declare the register, then use it in the #asm block.
#asm {
// ✅ CORRECT
the_register: gpr;
mov.64 the_register, 10;
}Problem: Forgetting that assembly statements inside #asm blocks also require a semicolon.
#asm {
// ❌ WRONG - missing semicolon
add x, y, z
}Solution: End every assembly statement with a semicolon.
// ✅ CORRECT
#asm {
add x, y, z;
}Problem: The compiler's by-value move optimization for #asm only works with 128-bit xmm registers. Attempting to use it with 256-bit ymm registers is not supported.
// ❌ WRONG - 256-bit by-value moves are not supported
add :: (a: [8] float, b: [8] float) -> [8] float {
result := a;
#asm AVX2 {
addps.256 result, b;
}
return result;
}Solution: Use 128-bit xmm registers for by-value moves, or use pointer-based memory operands for 256-bit operations.
// ✅ CORRECT - 128-bit xmm registers supported
add :: (a: [4] float, b: [4] float) -> [4] float {
result := a;
#asm {
addps.128 result, b;
}
return result;
}Problem: Repeatedly building strings with sprint in a loop causes quadratic allocations — each call allocates a new string and discards the old one.
// ❌ WRONG - reallocates every iteration
build_message :: (items: [] string) -> string {
result := "";
for item: items {
result = sprint("%% ", result, item);
}
return result;
}Solution: Use String_Builder to append incrementally and convert once at the end.
// ✅ CORRECT
build_message :: (items: [] string) -> string {
builder: String_Builder;
defer free_buffers(*builder);
for item: items {
append(*builder, item);
append(*builder, " ");
}
return builder_to_string(*builder);
}Problem: Using $T polymorphism when you only ever call the function with one concrete type adds compile-time overhead and generates unnecessary specializations.
// ❌ WRONG - unnecessary generic when only floats are used
add :: (a: $T, b: T) -> T {
return a + b;
}
result := add(1.0, 2.0);Solution: Use a concrete type when the set of types is small and known.
// ✅ CORRECT - simple and direct
add :: (a: float, b: float) -> float {
return a + b;
}