I've come away from reading Jai documentation, with mixed feelings. It's really more of a C replacement. There is a lot of similarity with Odin, but I think Odin is more thought through and logical as a language. This feels like a very practical language designed to solve the problems for it's developers, as those problems occured. It definately isn't a "big idea" language, and is described as such in Philosophy of Jai. Unfortunately that makes it feel more like a jumble of ideas.
Theres quite a lot to like here. I don't feel it's likely this will become a language in wide spread use though. Both Zig and Odin are out there, are readily available, in a similar space and seem more solid and consistent.
size_of
not sizeof
)ptr: *s32
)void
as zero sized typeas
to make casts to members work implicitlyinterface
/trait
s#
prefixed controlsusing, except(x)
)
vec2 := Vec3.{x = 1, y = 2, z = 3}
ifx
, xx
fdor exampledefer
as a mechanism to clean up()
for specializationalign
only working on fields, but not the struct
(if I understand correctly)Just like C, Jai has pointers with the same exact functionality as C, just using different operators for getting the address of and de-referencing a pointer. You can do pointer arithmetic on pointers as well as compare pointers against each other. Unlike C however, arrays do not cast to pointers, but rather arrays in Jai are objects with a data pointer and a count that counts the number of elements in an array. source
Rust allows raw pointers, but only in unsafe blocks or only when using smart pointers or reference counted pointers. Jai follows the C pointer model which allows pointers all the time. source
Although Jai can do function pointers, virtual functions are not supported in the compiler at all. source
Has struct inclusion with using
. Has function pointers.
Lot's of hot takes
const
- The concept of const can get confusing, and it does not help the compiler with anything.Syntax around constant and variables is very similar to Odin
x : int = 42; // Variable
x : int : 42; // Constant
unsafe, JAI doesn't have anything similar. The argument seems a little weak.
Types are what you'd expect. Uses s
prefix for int. int
is s64
.
Zero initializes by default. Uses ---
to set as uninitialized.
Typical number literal styles, supporting 0x
and 0b
and allowing _
to split.
No character type. Strings are list of bytes.
autocast operator is xx
(?). This is similar to the idea around coerce, but the name xx
doesn't seem right.
Pointers uses *
for reference and <<
to dereference.
Uses the "prefix type style". So it's *int
.
Has built in support for relative pointers
Initializing an array...
array : [4]float = float.[10.0, 20.0, 1.4, 10.0];
Arrays. Oh I have to use C style syntax to manipulate the array(!), for example...
array_add(*myarray, 5); // Add 5 to the end of myarray
Uses #
for built in and attribute like behavior, such as with #complete
.
Uses it
to name the value, and it_index
to name the index.
Has an unusual syntax for labeling break/continue.
x := 0;
while condition := x < 10 {
y := 0;
while y < 3 {
print("x=%, y=%\n", x, y);
y += 1;
if x > 3
break condition; // break out of an outer while loop
}
x += 1;
}
Is that better than the more common C++ label style? Hmm probably not. It looks too much like it's setting up a variable. The ideal of having the "label" part of the statement (as opposed to prefixing it) perhaps has something. Actually the typical label syntax might have problems here because name :
already has a meaning?
Has a remove
statement. That is helpful in so far as that is a fairly common idiom. Having a keyword for that seems a little surprising. Not clear if it only works on arrays, or has some other mechanism to work with other types.
Allows more custom iteration by using for_expansion
. This all seems kind of wacky stuff.
Function declaration...
function :: (arg1: int, arg2: int, arg3: int) -> int {
// write function code here.
}
Has named returned variables, and named parameters it seems.
function :: (arg1: int, arg2: int, arg3: int) -> ret1: int, ret2: int {
// write function code here.
}
ret1, ret2 := function(arg1=1, arg2=2, arg3=3);
Oh actually no, the names are just "reminders".
Has #this
as the name of the current scope.
Has overloading. No closures.
Lambda is defined with =>
, so...
funct :: (a, b) => a + b;
Has SOA through macro expansion. Not sure this really works in general, and seems like pretty weak support.
Like that lower snake is used for built in keywords, like size_of
, type_of
etc.
The #as
concept is interesting, it allows a type to be used as
a member type.
Interesting idea of using void
as a struct marker. In JAI void is always 0 sized. It can be returned, and have 'variables'.
Has operator overloading. Can use #symmetric
if the parameter order can be inverted.
In section on polymorphism describes a variety of ways to limit a $T
. The syntax seems a little odd x : Foo($)
or x : $T/Foo
. The first says T is of the kind Foo, the second allows T to incorporate Foo, T doesn't have to be a Foo.
The Helper
example shows how to calculate a return type.
foo :: (a: $A, b: $B) -> Helper(A,B).T {...}
Here the return type isn't directly a type, it in effect invokes Helper that takes the type of A and B, and produces a result. That style via #run
is more powerful, seems kind of akin to some C++ meta programming style, inserting information into structs. The trick here is that Helper is a type, and it's evaluation (which uses compile time #run
determines the final result, accessed as `.T).
JAI uses ()
for specialization. With a struct
Foo :: struct(x: $T) {...}
a : Foo(42);
Hmm to my eyes that looks a bit to close to calling a function.
Foo :: struct(T: Type) {
some_data : T;
}
f : Foo(int);
Because types are being passed in, is not so bad, but still a little worrying.
It is possible to get the specialization types (and perhaps values) for a specialized generic, by just accessing by their parameter name. This is helpful in later examples, where parameters are dependent on the type of one of the parameters.
Hash_Table :: struct (K: Type, V: Type, N: int) {
keys: [N] K;
values: [N] V;
}
function1 :: (table: Hash_Table($K, $V, $N), key: K, value: V) {
// do stuff
}
function2 :: (table: $T/Hash_Table, key: T.K, value: T.V) {
// do stuff
}
function3 :: (table: $T, key: T.K, value: T.V) {
// do stuff
}
Perhaps it's of note that lower camel is used for built in functions, that for a type Type_Info
is the name used.
The use of #modify
allows for compile time checking.
I like that there is support for context. This is a more powerful context system than is seen in Odin. It is used for memory, logging, assert callback and other information can be added. Not clear how this would work across shared library boundaries.
Enum is interesting in that it doesn't use =
, but the ::
constant syntax.
The #align
behavior seems a bit odd, in that it doesn't align the struct. That does allow usage of bytes after the struct I suppose, but that seems like a more unusual scenario, such that it seems like the default should align the struct, and there are other mechanisms for the special cases.
Meta programming
#
to distinguish the special functionsHas a lot of meta programming power. It's more like Zig than C++. The idea of using "regular programming" to do meta programming is compelling.
Handles compilation with a game or windows-like message loop.
Has "preload" module is akin to a "prelude".
Is a discussion about implementing (interface or trait](https://github.com/Jai-Community/Jai-Community-Library/wiki/Snippets-and--Benchmarks#interfacetrait). It embeds the 'vtbl' directly in the struct. Doesn't have any common interface features such as inheritance.
The EventBus, uses the common C idiom of having a function pointer, and void*.
To do: