Skip to content

Common Mistakes

danieltan1517 edited this page Mar 14, 2026 · 54 revisions

Incorrect Variable Declaration

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 - Place the variable identifier before the type declaration
x: int;

Incorrect Variable Type Inference

Problem: Using the 'auto' keyword to do C++ type inference.

// ❌ WRONG - using C++ 'auto' keyword. this is not correct Jai syntax
auto x = 10;

Solution: use the := operator for Jai type inference

// ✅ CORRECT - uses 'x := 10' and infers the type.
x := 10;

Incorrect Casting Syntax

Problem: Using incorrect cpp casting syntax in Jai

// ❌ WRONG - using cpp casting syntax
float f = (float)some_int;
float f2 = static_cast<float>(some_int);

Solution: cast using cast(float) some_int or xx some_int for autocast.

// ✅ CORRECT - using correct jai casting syntax
f := cast(float) some_int;
f2 := xx some_int;  // autocast — when you don't care about the exact type

No new keyword — it's a regular function New

Problem: In C++, new is a built-in keyword. In Jai, New is just a regular function call. The allocator is controlled through the context, not by overloading operator new:

// ❌ WRONG - there is no 'new' keyword in Jai
Object* obj = new Object();
delete obj;

Solution: Call the New Jai function to dynamically allocate memory.

// ✅ CORRECT - call the 'New' function in Jai.
obj := New(Object);
free(obj);

No constructors fire on struct declaration

Problem: Assuming C++ constructor initialization in Jai. But Jai does not have constructors in the language.

// ❌ WRONG - must call constructor before using the struct
Object obj;

Solution: Create an initialization function to initialize the object. Explicitly handle initialization.

// ✅ CORRECT - call 'init' for the object.
obj: Object;
init(*obj); // call initialization explicitly

No const keyword

Problem: Jai has no const. Constants use :: at declaration time. There's no way to mark a function parameter as const:

// ❌ WRONG - cannot make function parameters 'const'
void foo(const int x) { ... }

Solution: Pass parameters into functions as normal. Do not worry about constants.

// ✅ CORRECT - call the function using Jai syntax.
foo :: (x: int) { ... }

Confusing := and :: for procedures and constants

Problem: Beginners sometimes try to define a function with := instead of ::, which doesn't declare a constant procedure - it declares a variable holding a function value, which is a different thing.

// ❌ WRONG - This defines a constant procedure (correct and typical):
func := () {
    print("hello\n");
}

Solution: Define a function declaration using ::.

// ✅ CORRECT - This declares a variable of function-pointer type (probably not what you wanted):
func :: () {
    print("hello\n");
}

Print Mistakes

Problem: Jai's print doesn't use C-style format specifiers. % is a universal placeholder:

// ❌ WRONG - '%d' and '%f' are C-style format specifiers. not needed in Jai.
printf("Value: %d, float: %f\n", x, y);

Solution: Use only '%' to format the print function correctly.

// ✅ CORRECT - Format functions using '%'
print("Value: %, float: %\n", x, y);

Incorrect := Assign Usage

Problem: Using := everywhere out of habit, accidentally creating a new variable in the current scope instead of assigning to an existing one.

x := 10;
if true {
    // ❌ WRONG - this creates a NEW x, shadows the outer x
    // the outer x is still 10 after this block
    x := 99;
}

Solution: Use = to assign to a currently existing variable instead of accidentally shadowing a current variable.

x := 10;
if true {
    // ✅ CORRECT - Format functions using the correct Jai syntax
    x = 99;   // assigns to the existing x
}

Accidentally leaving variables uninitialised with ---

Problem: Jai gives you the ability to explicitly mark a variable as uninitialized for performance reasons with = ---. Beginners sometimes use this thinking it's just a style thing, not realizing it means the variable genuinely has undefined behavior until written to - reading it before writing is a bug.

// ❌ WRONG - explicitly uninitialized — undefined behavior if read before assignment
x: int = ---;
print("%\n", x); // dangerous!

Solution: use the default integer value of 0.

// ✅ CORRECT 0 If you just want zero, use the default:
x: int; // safely zero-initialized

Incorrect Function Declaration

Problem: Using the wrong C-like functions syntax in Jai

// ❌ WRONG - This is the incorrect function format. This is not C.
int function(int a, int b) {

}

Solution: Format functions using the correct Jai syntax, placing identifiers before types.

// ✅ CORRECT - Format functions using the correct Jai syntax
function :: (a: int, b: int) -> int {

}

Incorrect Range For Loop

Problem: For Loop Range is [x, y] inclusive. A For Loop for i : 0..10 will loop i=10. If one tries to do for i : 0..arr.count, it will trigger an array index out of bounds error.

// ❌ WRONG - for loop range is [x, y] inclusive. 
// this loop will execute arr[10], which will be wrong.
arr: [10] int;
for i : 0..arr.count {
    print("%\n", arr[i]);
}

Solution: Do for i : 0..arr.count-1 to take into account of the [x, y] inclusive for loop.

// ✅ CORRECT - for loop range is [x, y] inclusive. 
// this loop will end at arr[9], which is the end of the loop.
arr: [10] int;
for i : 0..arr.count-1 {
    print("%\n", arr[i]);
}

Iterating by value and expecting to modify the array

Problem: When you for over an array normally, it is a copy of the element. Modifications to it don't affect the original array. To modify elements in place, iterate by pointer using for *:

array := int.[1, 2, 3, 4, 5];
// ❌ WRONG - modifies a copy, array is unchanged
for array {
    it *= 2;
}

Solution: Iterate by pointer using for * to modify elements in place.

// ✅ CORRECT - iterate by pointer
for * array {
    <<it *= 2;  // or it.* *= 2
}

Not using array_reserve before large batch additions to dynamic arrays

Problem: Beginners will add items to a dynamic array one at a time in a loop without reserving space first. This causes repeated reallocations as the array grows. If you know the count upfront, reserving is far more efficient.

// ❌ WRONG - Inefficient: many allocations as the array grows
arr: [..] int;
for 0..999  array_add(*arr, it);

Solution: If you know the count upfront, reserving is far more efficient.

// ✅ CORRECT - one allocation up front
arr: [..] int;
array_reserve(*arr, 1000);
for 0..999  array_add(*arr, it);

Wrong Pointer Address Of Operation

Problem: Using the C++ address of operator instead of the Jai address of operator.

// ❌ WRONG - This is C++ style declaration '&' operator. Need to use proper Jai syntax.
int a = 5;
int* b = &a;

Solution: Use the appropriate Jai address of operator.

// ✅ CORRECT - using the '*' for getting the address.
a : int = 5;
b : *int = *a;   // * takes the address

Wrong Pointer Dereference Operation

Problem: Using the C++ dereference operator instead of the Jai dereference operator.

// ❌ WRONG - This is C++ style declaration '*' operator. Need to use proper Jai syntax.
int *b;
int val = *b;

Solution: Use the appropriate Jai address of operator.

// ✅ CORRECT - using the '*' for getting the address.
b: *int;
val := b.*;   // also correct

No -> for pointer member access

Problem: C++ uses -> to access struct members through a pointer. Jai just uses . everywhere:

// ❌ WRONG - This is C++ style struct member pointer '->' operator. Need to use proper Jai syntax
person->name = "Alice";

Solution: Use Jai struct member . access operator.

// ✅ CORRECT - using the '.' for getting the address.
person.name = "Alice";  // works whether person is a value or a pointer

Forgetting to pass dynamic arrays by pointer

Problem: Dynamic arrays must be passed by pointer (*[..] T) if you want to modify them inside a function. If you pass them by value, you're working on a copy and array_add calls won't affect the original:

// ❌ WRONG - adds to a copy, original is unchanged
function :: (arr: [..] int) {
    array_add(*arr, 42);
}

Solution: Pass the dynamic array by pointer to modify the array inside a function.

// ✅ CORRECT - pass array to function by pointer
function :: (arr: *[..] int) {
    array_add(arr, 42);
}

No Member Functions

Problem: In Jai, functions do not "belong" to particular objects.

Object :: struct {
    x: int;
    // ❌ WRONG - this is a regular function, not a member function. there is no concept of this/self
    set_x :: (x: int) {
        self.x = x;
    }
}

Solution: Add a parameter that points to the struct that you want to modify. Use a regular function.

Object :: struct {
    x: int;
}

// ✅ CORRECT - regular function
set_x :: (self: *Object, x: int) {
    self.x = x;
}

Unsigned Signed Type Mismatch

Problem: Jai is stricter than C and does not allow implicit unsigned to signed type conversion.

// ❌ WRONG - Confusing comparison
a: s32 = -1;
b: u32 = 1;
if a < b {
    print("This might not print when you expect!\n");
}

Solution: Be explicit about conversions and comparisons.

// ✅ CORRECT - Explicit handling
a: s32 = -1;
b: u32 = 1;
if a < 0 || cast(u32) a < b {
    print("Clear intent\n");
}

// Or make sure the types match
a: s32 = -1;
b: s32 = 1;
if a < b {
    print("Clear intent\n");
}

Dangling Pointers After Scope

Problem: Returning pointers to stack-allocated memory.

// ❌ WRONG - Dangling pointer!
create_vector :: () -> *Vector3 {
    v: Vector3;
    v.x = 1.0;
    v.y = 2.0;
    v.z = 3.0;
    return *v;  // DANGER! v goes out of scope
}

Solution: Allocate on heap or return by value.

// ✅ CORRECT - Return by value
create_vector :: () -> Vector3 {
    v: Vector3;
    v.x = 1.0;
    v.y = 2.0;
    v.z = 3.0;
    return v;  // Safe, copies the value
}

// Or allocate on heap
create_vector_heap :: () -> *Vector3 {
    v := New(Vector3);  // Heap allocation
    v.x = 1.0;
    v.y = 2.0;
    v.z = 3.0;
    return v;  // Safe, but caller must free
}

Implicit Type Conversions

Problem: Check the numerical bounds of the integer before casting. Be aware of the ranges of the integers.

// ❌ WRONG - s64 is bigger than s32
x: s64 = 1000000000000;
y: s32 = x;
print("y = %\n", y);

Solution: Use explicit casts and be aware of ranges.

// ✅ CORRECT - Explicit cast with awareness
x: s64 = 1000000000000;
y: s32 = cast(s32) x;  // Still truncates, but explicit
// Better: Check range first
if x >= S32_MIN && x <= S32_MAX {
    y: s32 = cast(s32) x;
} else {
    // Handle error
}

No ++ or -- operators

Problem: Attempting to use ++/-- operators. But Jai does not have such operators.

// ❌ WRONG - ++/-- are not in the language.
i++;
i--;

Solution: Jai has no increment/decrement operators. Use += 1:

// ✅ CORRECT - use += 1 and -= 1 instead.
i += 1;
i -= 1;

Double Free

Problem: Freeing the same memory twice.

// ❌ WRONG - Double free
data := alloc(size_of(MyStruct));
// ... use data ...
free(data);
// ... later ...
free(data);  // CRASH! Already freed

Solution: Free the memory only once.

// ✅ CORRECT
data := alloc(size_of(MyStruct));
// ... use data ...
free(data);

Forgetting to Free Dynamic Arrays

Problem: Dynamic arrays need explicit cleanup.

// ❌ WRONG - Memory leak
process_data :: () {
    items: [..] int;
    array_add(*items, 1);
    array_add(*items, 2);
    array_add(*items, 3);
    // Function ends - memory leaked!
}

Solution: Always free dynamic arrays or use defer.

// ✅ CORRECT
process_data :: () {
    items: [..] int;
    defer array_free(items);  // Cleanup guaranteed
    
    array_add(*items, 1);
    array_add(*items, 2);
    array_add(*items, 3);
}

String Concatenation in Loops

Problem: Repeatedly reallocating strings.

// ❌ WRONG - Quadratic complexity
build_message :: (items: [] string) -> string {
    result := "";
    for item: items {
        result = sprint("%% ", result, item);  // Reallocates every time!
    }
    return result;
}

Solution: Use String_Builder.

// ✅ CORRECT - Linear complexity
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);
}

Premature Bounds Checking Removal

Problem: Using unchecked array access without verification.

// ❌ WRONG - Dangerous assumption
process :: (data: [] int, index: int) {
    value := data[index];  // What if index is out of bounds?
    // Process value
}

Solution: Check bounds or document assumptions clearly.

// ✅ CORRECT - Explicit bounds check
process :: (data: [] int, index: int) {
    if index < 0 || index >= data.count {
        log_error("Index out of bounds: %", index);
        return;
    }
    
    value := data[index];
    // Process value
}

// Or use assertions for debug builds
process :: (data: [] int, index: int) {
    assert(index >= 0 && index < data.count, "Index out of bounds");
    value := data[index];
}

Unnecessary Polymorphic Dispatch

Problem: Using $T polymorphism when concrete types would suffice.

// ❌ WRONG - Unnecessary generic overhead
add :: (a: $T, b: T) -> T {
    return a + b;
}

// Used only with floats
result := add(1.0, 2.0);  // Creates specialized version unnecessarily

Solution: Use concrete types when the set of types is known and small.

// ✅ CORRECT - Simple and direct
add :: (a: float, b: float) -> float {
    return a + b;
}

// Only use polymorphism when truly needed
add_generic :: (a: $T, b: T) -> T {
    return a + b;  // Used with many different types
}

Cannot Jump in Assembly

Problem: Attempting to use the jmp instruction in an #asm block.

#asm {
   // ❌ WRONG - Cannot use labels and jmp in assembly language.
   label:
   //
   jmp label;
}

Solution: Use the structured looping mechanisms to perform branching behavior.

// ✅ CORRECT - use the while loop to expression branching
while true {

}

Assembly AVX2 cannot be Referenced

Problem: Attempting to use the by-reference / by-value distinction so that the compiler can optimize some moves such that they can be avoided during code generation. However, only 128-bit xmm registers are supported. Due to LLVM complexity issues, ymm registers (256-bit SIMD registers) and zmm registers (512-bit SIMD registers) are NOT supported.

add :: (a: [8] float, b: [8] float) -> [8] float {
  // ❌ WRONG - Only 128-bit registers are supported.
  result := a;
  #asm AVX2 {
    addps.256 result, b;
  }
  return result;
}

Solution: Use 128-bit xmm registers, since those are only registers supported by the compiler.

add :: (a: [4] float, b: [4] float) -> [4] float {
  // ✅ CORRECT - use 128-bit xmm registers
  result := a;
  #asm {
    addps.128 result, b;
  }
  return result;
}

Clone this wiki locally