My Impressions

Positives

  • Many! This has been my favorite language so far.

  • It's the most fun I had with a language!

  • Solves many problems I had with Zig, Rust, C/C++.

  • (2025-04-20) I really like the big focus on NAMES in the syntax :

    • I found the syntax very weird initially, but the reality is it is ultra intuitive and I have liked it a lot.

      my_var := 123
      my_proc :: proc() {}
      
  • (2025-04-20) No need for ;  and don't miss it :

    • I never missed ; , after all what made the experience positive are the { } , not the ; .

    • You can use ;  if you want, though.

  • (2025-04-20) No need for ( )  in expressions :

    • Much better than in Zig and C, nice.

  • (2025-04-20) Enum access is simple, similar to Swift :

    • Can be used as .A  instead of MyEnum.A , in the correct context.

  • (2025-04-20) No need to specify return type for void  when there is no return :

    • Nice.

  • (2025-04-20) No methods :

    • Great.

  • (2025-04-20) Excellent built system :

    • Anything compared to C/C++ is excellent, to be fair.

    • Either way, it's by fair the easy language to compile I've seen.

  • (2025-04-20) The package system really seems very good, with folders :

    • Inspired by Go.

    • After seeing Ginger Bill's explanation in this video {26:50 -> 34:30} , I found it very nice.

    • Really seems to be a very good solution for managing exports/imports.

Negatives

  • I explored "fixing" a lot of these negatives in my Odin fork ; check the README.md for an overview of the changes.

    • "fixing" is quoted as some people may argue that some changes were bad, but of course, that depends on how you enjoy programming and how much you care about safety and explicitness.

  • (2025-12-12) I don't like the context  system at all .

    • I have lots of critiques around it.

    • See Context  for a in-depth discussion about this.

  • (2025-12-13) I don't like @(init)  and @(fini) .

    • Quoting a snippet from the discord thread about that I found interesting and agree with:

    • Barinzaya:

      • I think @(init)  procs are kind of an anti-pattern. I dislike the "this proc is now always going to be called whether you want it or not" nature of it.

    • Caio:

      • I completely agree. The only reason I used @(init)  in this situation, was because other libraries do. I had to place the profiler earlier than all of them, so the only way to do it is by also being @(init)  or going before than _startup_runtime .

  • (2025-11-13) I don't like how @(require_results)  is NOT the default way of handling results; I would prefer the opposite .

    • By default, errors can be ignored. Not good. Things like #optional_ok  and #optional_allocator_error  exists, but the main problem is actually how @(require_results)  is optional. By the default a procedure will not require the results to be handled. I wish the opposite was true: you have to opt out of required results and use something like @(optional_results) ; the priorities should have been inverted.

    • There's also the annoyance of having to add @(require_results)  for every math function and similar, etc.

    • I made a suggestion for something like #+vet explicit-returns , as way to have every unhandled return be treated as an error, even for #optional_ok  or #optional_allocator_error  , as well as a compiler flag. This would just be an optional flag, per file (even tho I prefer per library), but it was denied :/

  • (2025-11-13) I don't like the implicit usage of context.allocator  around A LOT of libraries, basically being the standard in Odin .

    • This has led me to more bugs that it has helped anything.

    • Also, this leads to code that focuses heavily on "constructors" and "destructors", as by default the context.allocator  is a runtime.heap_allocator() , which is just a wrap around malloc .

      • Some libraries are ok with you using an arena in its place, but other libraries use defer delete()  implicitly and that makes it incompatible with a more straight forward and optimized design of managing memory, focused on lifetimes with arenas.

    • Currently, to improve this:

      • I use panic_allocator  as the default for context.allocator , by using the -default-to-panic-allocator  flag. I don't ever reassign the context.allocator . I use it as the panic_allocator  during all the application, so if I forget to be explicit about an allocation, the app crashes. This is far from perfect, as this is a runtime check, but it's better than losing track of your memory.

      • I use #+vet explicit-allocators  on top of every  file. This make it so allocator := context.allocator  gives an error. So new(int)  will give an error, but new(int, allocator)  will not. Also not perfect, as I'd prefer this to be a compilation flag, etc.

    • Both improvements above just hides the problem a bit. I don't like how I had to go around one of the main language design just so I have safer and sane code.

    • Even if context  was removed, code could go from allocator := context.allocator  to allocator := runtime.allocator  (thread local global variable, as I suggested). So it's not much of a context  thing, but more about how the language heavily favors design of default allocators, and implicitness.

    • I can think of this either being solved by removing default parameters in procedures, or having a code style that enforces explicit allocators instead of the opposite.

  • (2025-11-13) I would prefer if there was no default parameters in procedures .

    • This sounds a bit wild, but I came to realize how little I actually need default parameters.

    • They result in implicit behavior, which I believe leads to worse code.

    • Meanwhile, working without default parameters is actually an interesting challenge to solve that I think results in much better APIs.

  • (2025-04-20) I don't like using  outside of structs :

    • Ginger Bill also considers this a mistake.

    • Read the using  section for more information.

  • (2025-04-20) Lack of keywords for concurrency is somewhat annoying ( async  / await ) .

    • Maybe this is ok, but I do have to investigate a bit more about this.

    • (2025-11-13)

      • Well, I made a library for that, so problem solved. I much rather having my library then using something built in the language now, I think.

  • (2025-04-20) Down-casting can be complex :

    • I cannot compare subtypes, like in GDScript, with is :

    func _detect_hitbox(area: Area2D) -> void:
        if not (area is Hitbox):
            Debug.red('(%s | Hurtbox) The area is not a Hitbox.' % _name)
            return
    
    • It is necessary to use advanced idioms, with Unions / Enums, etc., to get the desired information.

    • See Odin#Advanced Idioms, Down-Cast and Up-Cast  for more information.

    • (2025-11-13)

      • I think I'm ok with this. It's actually really rare I have to use something like the code shown in GDScript, and avoiding these situations led the code to be more understandable.

      • It's a lower level thing, but once you get used to it, I think it's ok.

  • (2025-04-20) Having to use ->  for function return :

    • Minor, but I feel it could be hidden.

    • (2025-07-03)

      • Genuinely, I don't care at all.

  • (2025-04-20) Having to use case  keyword for switches :

    • Minor, but I think the keyword shouldn't exist.

    • (2025-07-03)

      • Genuinely, I don't care at all.

      • I actually kind of like it.

  • (2025-04-20) Having to use proc  keyword for procedures :

    • Ultra minor, I got used to the keyword and it's convenient when considering how similar the syntax is to:

      my_proc :: proc() {}
      
      my_struct :: struct {}
      
    • (2025-07-03)

      • JAI opts not to use the keyword, but I have come to appreciate its use.