-
Notifications
You must be signed in to change notification settings - Fork 0
Common Mistakes
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;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;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 typeProblem: 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);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 explicitlyProblem: 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) { ... }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");
}
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);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
}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-initializedProblem: 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 {
}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]);
}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
}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);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 addressProblem: 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 correctProblem: 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 pointerProblem: 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);
}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;
}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");
}
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
}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
}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;Problem: Freeing the same memory twice.
// ❌ WRONG - Double free
data := alloc(size_of(MyStruct));
// ... use data ...
free(data);
// ... later ...
free(data); // CRASH! Already freedSolution: Free the memory only once.
// ✅ CORRECT
data := alloc(size_of(MyStruct));
// ... use data ...
free(data);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);
}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);
}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];
}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 unnecessarilySolution: 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
}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 {
}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;
}