alt.hn

12/26/2025 at 7:10:12 AM

Odin: Moving Towards a New "core:OS"

https://odin-lang.org/news/moving-towards-a-new-core-os/

by ksec

12/31/2025 at 7:08:12 AM

I like Odin and hope for it to gain more momentum.

I have an important metric for new "systems" languages: does the language allow NULL for it's commonly used pointer type. Rust by default does not (References may _not_ be NULL). Here is where I think Odin makes a mistake.

In the linked blog post Odin mentions ^os.File which means pointer to File (somewhat like *FILE in C). Theoretically the pointer can be NULL. In practice, maybe I would need to check for NULL or maybe I would not (I would have to scan the Odin function's documentation to see what the contract is).

In Rust, if a function returns &File or &mut File or Box<File> etc. I know that it will never be NULL.

So Odin repeats the famous "billion-dollar mistake" (Tony Hoare). Zig in comparison is bit more fussy about NULL in pointers so it wins my vote.

Currently this is my biggest complaint about Odin. While Odin packages a lot of power programming idioms (and feels a bit higher level and ergonomic than Zig) it makes the same mistake that Golang, C and others make regarding allowing NULL in the default pointer type.

by sidkshatriya

12/31/2025 at 9:36:49 AM

One thing I think worth considering for systems languages on this point: if you don't want to solve every expressiveness issue downstream of Result/Option/etc from the outset, look at Swift, which has nullable types.

MyObject can't be null. MyObject? can be null. Handling nullability as a special thing might help with the billion-dollar mistake without generating pressure to have a fully fleshed out ADT solution and everything downstream of that.

To people who would dismiss ADTs as a hard problem in terms of ergonomics: Rust makes it less miserable thanks to things like the question-mark shorthand and a bazillion trait methods. Languages like Haskell solve it with a monads + do syntax + operating overload galore. Languages like Scala _don't_ solve it for Result/Option in any fun way and thus are miserable on this point IMHO

by rtpg

12/31/2025 at 10:53:45 AM

I like to think about how many problems a feature solves to judge whether it's "worth it". I believe that the Sum types solve enough different problems that they're worth it, whereas nullability solves only one problem (the C-style or Java-style null object) the Sum types can solve that with Option<T> and also provide error handling with Result<T, Err> and control flow with ControlFlow<Continue, Break> among others so that's already a better deal.

Nullability is a good retro-fit, like Java's type erased generics, or the DSL technology to cram a reasonable short-distance network protocol onto the existing copper lines for telephones. But in the same way that you probably wouldn't start with type erased generics, or build a new city with copper telephone cables, nullability isn't worth it for a new language IMO.

by tialaramex

12/31/2025 at 2:58:34 PM

I'm an advocate for "both".

- `Option<T>` and `Result<T,E>` at core;

- `?T` and `T!E` as type declaration syntax that desugars to them;

- and `.?` and `.!` operators so chains like `foo()?.bar()!.baz()` can be written and all the relevant possible return branches are inserted without a fuss.

Having `Option` and `Result` be simply normal types (and not special-casing "nullable") has benefits that are... obvious, I'd say. They're just _normal_. Not being special cases is great. Then, having syntactic sugars to make the very, _very_ common cases be easy to describe is just a huge win that makes correct typing more accessible to many more people by simply minimizing keystrokes.

The type declaration sugar is perhaps merely nice to have, but I think it really does change the way the average programmer is willing to write. The chaining operators, though... I would say I borderline can't live without those, anymore.

Chaining operators can change the SLOC count of some functions by as much as... say, 75%, if we consider a language like Go with it's infamous "if err not nil" clause that is mandated to spread across three lines.

by heavenlyhash

12/31/2025 at 1:33:37 PM

Erased generics give parametricity, which most PL people think is fairly important. See https://en.wikipedia.org/wiki/Parametricity or https://www.cl.cam.ac.uk/teaching/1617/L28/parametricity.pdf

by noelwelsh

12/31/2025 at 2:18:16 PM

I mean, yeah, type erasure does give parametricity, but, you can instead design your language so that you monomorphize but insist on parametricity anyway. If you write stable Rust your implementations get monomorphized but you aren't allowed to specialize them - the stable language doesn't provide a way to write two distinct versions of the polymorphic function.

And if you only regard parametricity as valuable rather than essential then you can choose to relax that and say OK, you're allowed to specialize but if you do then you're no longer parametric and the resulting lovely consequences go away, leaving it to the programmers to decide whether parametricity is worth it here.

by tialaramex

12/31/2025 at 2:44:03 PM

I don't understand your first paragraph. Monomorphization and parametricity are not in conflict; the compiler has access to information that the language may hide from the programmer. As an existance proof, MLTon monomorphizes arrays while Standard ML is very definitely parametric: http://www.mlton.org/Features

I agree that maintaining parametricity or not is a design decision. However, recent languages that break it (e.g. Zig) don't seem to understand what they're doing in this regard. At least I've never seen a design justification for this, but I have seen criticism of their approach. Given that type classes and their ilk (implicit parameters; modular implicits) give the benefits of ad-hoc polymorphism while mantaining parametricity, and are well established enough to the point that Java is considering adding them (https://www.youtube.com/watch?v=Gz7Or9C0TpM), I don't see any compelling reason to drop parametricity.

by noelwelsh

12/31/2025 at 3:38:27 PM

My point was that you don't need to erase types to get parametricity. It may be that my terminology is faulty, and that in fact what Rust is doing here does constitute "erasing" the types, in that case what describes the distinction between say a Rust function which is polymorphic over a function to be invoked, and a Rust function which merely takes a function pointer as a parameter and then invokes it ? I would say the latter is type erased.

by tialaramex

12/31/2025 at 10:38:31 AM

The Scala solution is the same as Haskell. for comprehensions are the same thing as do notation. The future is probably effect systems, so writing direct style code instead of using monads.

It's interesting that effect system-ish ideas are in Zig and Odin as well. Odin has "context". There was a blog post saying it's basically for passing around a memory allocator (IIRC), which I think is a failure of imagination. Zig's new IO model is essentially pass around the IO implementation. Both capture some of the core ideas of effect systems, without the type system work that make effect systems extensible and more pleasant to use.

by noelwelsh

1/1/2026 at 1:03:49 PM

> for comprehensions are the same thing as do notation

The ergonomics of scala for comprehensions are, in my mind, needlessly gnarly and unpleasant to use despite the semantics being the same

by rtpg

12/31/2025 at 9:48:24 AM

I personally don't enjoy the MyObject? typing, because it leads to edge cases where you'd like to have MyObject??, but it's indistinguishable from MyObject?.

E.g. if you have a list finding function that returns X?, then if you give it a list of MyObject?, you don't know if you found a null element or if you found nothing.

It's still obviously way better than having all object types include the null value.

by _flux

1/1/2026 at 10:54:54 AM

When you want to distinguish `MyObj??` then you'll have to distinguish the optionality of one piece of code (wherever your `MyObj?` in the list came from) with some other (list find) before "mixing" them. E.g. by first mapping `MyObj?` to `MyObj | NotFoundInMyMap` (or similar polymorphic variant/anonymous sum types) and then putting it in a list. This could be easily optimized away or be a safe no-op cast.

Common sum types allow you to get around this, because they always do this "mapping" intrinsically by their structure/constructors when you use `Either/Maybe/Option` instead of `|`. However, it still doesn't always allow you to distinguish after "mixing" various optionalities - if find for Maps, Lists, etc all return `Option<MyObj>` and you have a bunch of them, you also don't know which of those it came from. This is often what one wants, but if you don't, you will still have to map to another sum type like above. In addition, when you don't care about null/not found, you'll have the dual problem and you will need to flatten nested sum types as the List find would return `Option<Option<MyObj>>` - `flatten`/`flat_map`/similar need to be used regularly and aren't necessary with anonymous sum types that do this implicitly.

Both communicate similar but slightly different intent in the types of an API. Anonymous sum types are great for errors for example to avoid global definitions of all error cases, precisely specify which can happen for a function and accumulate multiple cases without wrapping/mapping/reordering. Sadly, most programming languages do not support both.

by k_g_b_

12/31/2025 at 4:08:28 PM

> E.g. if you have a list finding function that returns X?, then if you give it a list of MyObject?, you don't know if you found a null element or if you found nothing.

This is a problem with the signature of the function in the first place. If it's:

  template <typename T>
  T* FindObject(ListType<T> items, std::function<bool(const T&)> predicate)
Whether T is MyObject or MyObject?, you're still using nullpointers as a sentinel value;

  MyObject* Result = FindObject(items, predicate);
The solution is for FindObject to return a result type;

  template <typename T>
  Result<T&> FindObject(ListType<T> items, std::function<bool(const T&)> predicate)
where the _result_ is responsible for the return value wrapping. Making this not copy is a more advanced exercise that is bordering on impossible (safely) in C++, but Rust and newer languages have no excuse for it

by maccard

12/31/2025 at 2:36:40 PM

Different language, but I find this Kotlin RFC proposing union types has a nice canonical example (https://youtrack.jetbrains.com/projects/KT/issues/KT-68296/U...)

    inline fun <T> Sequence<T>.last(predicate: (T) -> Boolean): T {
        var last: T? = null
        var found = false
        for (element in this) {
            if (predicate(element)) {
                last = element
                found = true
            }
        }
        if (!found) throw NoSuchElementException("Sequence contains no element matching the predicate.")
        @Suppress("UNCHECKED_CAST")
        return last as T
    }
A proper option type like Swift's or Rust's cleans up this function nicely.

by gm678

12/31/2025 at 11:52:36 AM

Your example produces very distinguishable results. e.g. if Array.first finds a nil value it returns Optional<Type?>.some(.none), and if it doesn't find any value it returns Optional<Type?>.none

The two are not equal, and only the second one evaluates to true when compared to a naked nil.

by ecedeno

12/31/2025 at 1:22:36 PM

What language is this? I'd expect a language with a ? -type would not use an Optional type at all.

In languages such as OCaml, Haskell and Rust this of course works as you say.

by _flux

12/31/2025 at 1:57:27 PM

This is Swift, where Type? is syntax sugar for Optional<Type>. Swift's Optional is a standard sum type, with a lot of syntax sugar and compiler niceties to make common cases easier and nicer to work with.

by _rend

12/31/2025 at 2:16:21 PM

Right, so it's not like a union type Type | Null. Then naturally it works the same way as in the languages I listed.

by _flux

12/31/2025 at 11:41:34 AM

Well, in a language with nullable reference types, you could use something like

  fn find<T>(self: List<T>) -> (T, bool)
to express what you want.

But exactly like Go's error handling via (fake) unnamed tuple, it's very much error-prone (and return value might contain absurd values like `(someInstanceOfT, false)`). So yeah, I also prefer language w/ ADT which solves it via sum-type rather than being stuck with product-type forever.

by lock1

12/31/2025 at 1:25:19 PM

How does this work if it is given an empty list as a parameter?

I guess if one is always able to construct default values of T then this is not a problem.

by _flux

12/31/2025 at 4:10:35 PM

> I guess if one is always able to construct default values of T then this is not a problem.

this is how go handles it;

  func do_thing(val string) (string, error)
is expected to return `"", errors.New("invalid state")` which... sucks for performance and for actually coding.

by maccard

12/31/2025 at 12:58:18 PM

I like go’s approach on having default value, which for struct is nil. I don’t think I’ve ever cared between null result and no result, as they’re semantically the same thing (what I’m looking for doesn’t exist)

by skydhash

12/31/2025 at 1:37:06 PM

In Go, the default (zero) value for a struct is an empty struct.

by onionisafruit

12/31/2025 at 1:47:15 PM

Eh, it’s not uncommon to need this distinction. The Go convention is to return (res *MyStruct, ok bool).

An Option type is a cleaner representation.

by Cyph0n

12/31/2025 at 8:21:11 PM

I think the notion that "null" is a billion dollar mistake is well overblown. NULL/nil is just one of many invalid memory addresses, and in practice most of invalid memory address are not NULL. This is related to the drunkard’s search principle (a drunk man looks for his lost keys at night under a lamppost because he can see in that area). I have found that null pointers are usually very easy to find and fix, especially since most platforms reserve the first page of (virtual) memory to check for these errors.

In theory, NULL is still a perfectly valid memory address it is just that we have decided on the convention that NULL is useful for marking a pointer as unset.

Many languages (including Odin) now have support for maybe/option types or nullable types (monads), however I am still not a huge fan of them in practice as I rarely require them in systems-level programming. I know very well this is a "controversial" opinion, but systems-level programming languages deal with memory all the time and can easily get into "unsafe" states on purpose. Restricting this can actually make things like custom allocators very difficult to implement, along with other things.

n.b. Odin's `Maybe(^T)` is identical to `Option<&T>` in Rust in terms of semantics and optimizations.

by gingerBill

1/1/2026 at 8:18:01 AM

> I have found that null pointers are usually very easy to find and fix, especially since most platforms reserve the first page of (virtual) memory to check for these errors.

This is true. However, you have done these fixes after noticing them at runtime. This means that you have solved the null problem for a certain control + data state in code but you don't know where else it might crop up again. In millions of lines of code, this quickly becomes a whack-a-mole.

When you use references in Rust, you statically prove that you cannot have null error in a function for all inputs the function might get if you use Rust style references. This static elimination is helpful. Also you force programmers to distinguish between &T and Option<&T> and Result<&T,E> -- all of which are so common in system's code.

Today it is safe to assume that a byte is 8 bits. Similarly it is safe to assume that the first page in virtual memory is non-readable and non-writable -- why not make use of this fore knowledge ?

> This is related to the drunkard’s search principle (a drunk man looks for his lost keys at night under a lamppost because he can see in that area).

This is a nice example and I do agree in spirit. But then I would offer the following argument: Say, a problem (illegal/bad virtual address) is caused 60% by one culprit (NULL dereference) and 40% by a long tail of culprits (wrong virtual memory address/use after free etc). One can be a purist and say "Hey, using Rust style references" only solves the 60% case, addresses can be bad for so many other reasons ! Or one can pragmatic and try to deal with the 60%.

I cringe every time I see *some_struct in Linux kernel/system code as function argument/return. Does NULL indicate something semantically important ? Do we need to check for NULL in code that consumes this pointer ? All these questions arise every time I see a function signature. Theoretically I need to understand the whole program to truly know whether it is redundant/necessary to check for NULL or not. That is why I like what Rust and Zig do.

by sidkshatriya

1/1/2026 at 2:16:00 PM

Here's what I said in another reply: https://news.ycombinator.com/item?id=46454185

But to answer your general points here: Odin is a different language with a different way of doing things compared to others, so their "solutions" to specific "problems" do not usually apply to a language like Odin.

The problem with solving the "60% case" means you now have introduced a completely different way of programming, which might solve that, but the expense of so many other cases. It's a hard language design question and people focusing on this specific case have not really considered how it effects anything else. Sadly, language features and constructs are rarely isolated from other things, even the architecture of the code the programmer writes.

As for C code, I agree it's bad that there is no way to know if a pointer uses NULL to indicate something or not, but that's pretty much not a problem in Odin. If people want to explicitly state that, they either use multiple return values, which is much more common in Odin (which is akin to Result<&T, E> in Rust, but of course not the same for numerous reasons), or they use `Maybe(^T)` (akin to Option<&T> in Rust).

I understand the problems that C programmers face, I am one, which is why I've tried to fix a lot of them without trying to be far from the general C "vibe".

by gingerBill

1/1/2026 at 3:50:47 PM

Thanks for your reply.

> It's a hard language design question and people focusing on this specific case have not really considered how it effects anything else. Sadly, language features and constructs are rarely isolated from other things, even the architecture of the code the programmer writes

And my suggestion is that Rust has got the balance right when it comes to pointers. I can use the traditional unsafe nullable pointer *SomeStruct when it can be null and use &SomeStruct when it cannot be NULL in Rust. Initialization can be a bit painful in Rust but the wins are worth it. Yes, initialization can be less efficient in Rust but then most of the time spent is spend in algorithms when they run, not during initialization of the data structure.

Rust has needless complexity when it comes to asynchronous programming. The complexity is off the charts and the language just becomes unusable. But the non-async subset of Rust feels consistent and well engineered from a programming theory perspective.

In summary, Rust has not compromised itself by using non-null references as the default pointer type and neither will Odin, if it takes a different approach towards references. Take the example of OCaml - it also takes a very principled approach towards NULL. OTOH Java suffers from NULL problems as every object could be null in disguise.

Nevertheless Odin is a remarkably clean and powerful language and I'm following its progress closely ! Thanks for building it !

by sidkshatriya

1/1/2026 at 7:46:14 PM

Unfortunately I don't think you've understand what I was I trying to say.

Rust was designed from day-0 around explicit individual-value based initialization. Odin from day-0 was not designed around this (explicitly).

This ever so minor choice might not seem like a big thing to you, as you have stated in your comment, but it leads to MASSIVE architectural decisions later on when the user programs.

Odin could not "just add" non-nil pointers and it be "fine". It would actually not be Odin any more, and the entire language would not even be a C alternative any more, and feel much more like C++ or Rust. Rust and OCaml (which Rust is based off) are different kinds of languages to Odin and their approach does not translate well to what Odin (or C) is trying to do.

Unfortunately I will not be able to explain this to you in a comment or article, and it is something that takes a while to understand since it is a subtle effect of locality affecting globality. The best video on this might be from Casey Muratori: https://www.youtube.com/watch?v=xt1KNDmOYqA

> Yes, initialization can be less efficient in Rust but then most of the time spent is spend in algorithms when they run, not during initialization of the data structure.

Sadly this isn't as true as you think it is. I've written a lot of C++ code before and doing its boilerplate for ctors/dtors actually leads to much slower code in general, and I'd argue this does apply to Rust too. Most of the time isn't necessarily spent in "algorithms", especially when "algorithms" also include initialization of values. You've turned something which could be O(1) into at best O(N), which does not help when things scale, especially with "algorithms".

by gingerBill

1/1/2026 at 4:36:36 AM

> get into "unsafe" states on purpose

see, this seems like something that's nice to actually put into the types; a Ptr<Foo> is a real pointer that all the normal optimizations can be done to, but cannot be null or otherwise invalid, and UnsafePtr makes the compiler keep its distance and allows whatever tricks you want.

by remexre

12/31/2025 at 8:49:30 PM

It's just weird to defend getting less help from the compiler in a situation where that help is so easy to give.

by 01HNNWZ0MV43FF

1/1/2026 at 7:40:01 AM

Isn't it possible for there to be downsides to doing things even when they're easy?

by leecommamichael

1/1/2026 at 8:01:00 AM

Yes it's the burden of proof. That's why writing Rust is harder than C++. Or why Python is easier than anything else. As a user and customer, I'd rather pay more for reliable software though.

by grumpyprole

1/1/2026 at 2:01:47 PM

n.b. Sorry for the long reply, this is actually a really complex and complicated topic in terms of language design, trade-offs, and empirics.

It's a trade-off in design which is not immediately obvious. If you want to make pointers not have a `nil` state by default, this requires one of two possibilities: requiring the programmer to test every pointer on use, or assume pointers cannot be `nil`. The former is really annoying, and the latter requires something which I did not want to (which you will most likely not agree with just because it doesn't _seem_ like a bad thing from the start): explicit initialization of every value everywhere.

The first option is solved with `Maybe(^T)` in Odin, and that's fine. It's actually rare it is needed in practice, and when it is needed, it's either for documenting foreign code's usage of pointers (i.e. non-Odin code), or it's for things which are not pointers at all (in Odin code).

The second option is a subtle one: it forces a specific style and architectural practices whilst programming. Odin is designed around two things things: to be a C alternative which still feels like C whilst programming and "try to make the zero value useful", and as such, a lot of the constructs in the language and core library which are structured around this. Odin is trying to be a C alternative, and as such it is not trying to change how most C programmers actually program in the first place. This is why you are allowed to declare variables without an explicit initializer, but the difference to that of C is that variables will be zero-initialized by default (you can do `x: T = ---` to make it uninitialized stack memory, so it is an opt-in approach).

Fundamentally this idea of explicit individual-value based initialization everywhere is a viral concept which does lead to what I think are bad architectural decisions in the long run. Compilers are dumb---they cannot do everything for you, especially know the cost of the architectural decisions throughout your code, which requires knowing the intent of your code. When people argue that a lot of the explicit initialization can be "optimized" out, this is only thinking form a local position of individual values, which does total up to being slower in some cases.

To give an example of what I mean, take `make([]Some_Struct, N)`, in Odin, it just zeroes the memory because in some cases, it is literally free (i.e. `mmap` must zero). However, when you need to initialize each value of that slice, you are not turning a O(1) problem into a O(N) problem. And it can get worse if each field in the struct also needs its own form of construction.

But as I said in my original comment, I do not think the `nil` pointer problem, especially in Odin since it has other array constructs, is actually an empirical problem in practice. I know a lot of people want to "prove" things whatever they can at compile-time, but I still have to test a lot of code in the first place, and for this very very specific example, it is a trivial one to catch.

P.S. This "point" has been brought up a lot before, and I do think I need to write an article on the topic explaining my position because it is a bit tiring rewriting the same points out each time.

P.P.S. I also find this "gotcha" people bring up is the most common one because it is _seems_ like an obvious "simple" win, and I'd argue it's the exact opposite of either "simple" and even a "win". Language designing is all about trade-offs and compromises as there is never going to be a perfect language for anyone's problem. Even if you designed the DSL for your task, you'll still have loads of issues, especially with specific semantics (not just syntax).

by gingerBill

12/31/2025 at 12:50:50 PM

Odin’s design is informed by simplicity, performance and joy and I hope it stays that way. Maybe it needs to stay a niche language under one person’s control in order to do so since many people can’t help but try to substitute their own values when touring through a different language.

by throwthrowuknow

12/31/2025 at 2:54:32 PM

I wonder if some day we'll look back differently on the "billion-dollar mistake" thing. The key problem with null references is that it forces you to either check any given reference to see if it's null if you don't already know, or you would have to have a contract that it can't be null. Not having null references really does solve that problem, but still in every day programs you often wind up with situations where you actually can know from the outside that some function will return a non-empty value, but the function itself is unable to make that guarantee in a way that the compiler can enforce it; in those cases, you have no choice but to face the same dilemma. In Rust this situation plays out with `unwrap()`, which in practice most reasonably-sized codebases will end up with some. You could always forbid it, but this is only somewhat of an improvement because in a lot of cases there also isn't anything logical to do once that invariant hasn't held. (Though for critical production workloads, it is probably a good idea to try to find something else to do other than let the program entirely crash in this event, even if it's still potentially an emergency.)

In other words: after all this time, I feel that Tony Hoare framing null references as the billion-dollar mistake may be overselling it at least a little. Making references not nullable by default is an improvement, but the same problem still plays out so as long as you ever have a situation where the type system is insufficient to be able to guarantee the presence of a value you "know" must be there. (And even with formal specifications/proofs, I am not sure we'll ever get to the point where is always feasible to prove.) The only real question is how much of the problem is solved by not having null references, and I think it's less than people acknowledge.

(edit: Of course, it might actually be possible to quantify this, but I wasn't able to find publicly-available data. If any organization were positioned to be able to, I reckon it would probably be Uber, since they've developed and deployed both NullAway (Java) and NilAway (Go). But sadly, I don't think they've actually published any information on the number of NPEs/panics before and after. My guess is that it's split: it probably did help some services significantly reduce production issues, but I bet it's even better at preventing the kinds of bugs that are likely to get caught pre-production even earlier.)

by jchw

12/31/2025 at 4:41:41 PM

I think Hoare is bang on because we know the only similar values in many languages are also problematic even though they're not related to memory.

The NaNs are, as their name indicates, not numbers. So the fact this 32-bit floating point value parameter might be NaN, which isn't even a number, is as unhelpful as finding that the Goose you were passed as a parameter is null (ie not actually a Goose at all)

There's a good chance you've run into at least one bug where oops, that's NaN and now the NaN has spread and everything is ruined.

The IEEE NaNs are baked into the hardware everybody uses, so we'll find it harder to break away from this situation than for the Billion Dollar Mistake, but it's clearly not a coincidence that this type problem occurs for other types, so I'd say Hoare was right on the money and that we're finally moving in the correct direction.

by tialaramex

1/1/2026 at 2:50:41 AM

What I'm saying is, I disagree that "we know" these things. We know that there are bugs that can be statically prevented by having non-nullable types to enforce contracts, but that doesn't in itself make null the actual source of the problem.

A language with non-nullability-by-default in its reference types is no worse than a language with no null. I say this because, again, there will always be situations where you may or may not have a value. For example, grabbing the first item in a list; the list may be empty. Even if you "know" the list contains at least one item, the compiler does not. Even if you check the invariant to ensure that it is true, the case where it is false may be too broken to handle and thus crashing really is the only reasonable thing to do. By the time the type system has reached its limits, you're already boned, as it can't statically prevent the problem. It doesn't matter if this is a nullable reference or if its an Option type.

Because of that, we're not really comparing languages that have null vs languages that don't. We're comparing languages that have references that can be non-nullable (or functionally equivalent: references that can't be null, but optional wrapper types) versus languages that have references that are always nullable. "Always nullable" is so plainly obviously worse that it doesn't warrant any further justification, but the question isn't whether or not it's worse, it's how much worse.

Maybe not a billion dollars worse after all.

P.S.: NaN is very much the same. It's easy to assign blame to NaN, and NaN can indeed cause problems that wouldn't have existed without it. However, if we had signalling NaNs by default everywhere, I strongly suspect that we would still curse NaN, possibly even worse. The problem isn't really NaN. It's the thing that makes NaN necessary to begin with. I'm not defending null as in trying to suggest that it isn't involved in causing problems, instead I'm suggesting that the reasons why we still use null are the true root issue. You really do fix some problems by killing null, but the true root issue still exists even after.

by jchw

1/1/2026 at 8:07:06 AM

It's overblown until it isn't. Hoare didn't pluck that number from thin air. This is now a solved problem in modern programming languages. If Odin doesn't have this and other essential memory safety features, it's certainly not worth the massive retooling effort.

by grumpyprole

1/1/2026 at 2:45:09 PM

But that's not really what I mean. What I am trying to say is that "solving" the problem doesn't really fully solve it after all.

by jchw

12/31/2025 at 7:45:56 AM

Odin offers a Maybe(T) type which might satisfy your itch. It's sort of a compromise. Odin uses multiple-returns with a boolean "ok" value for binary failure-detection. There is actually quite a lot of syntax support for these "optional-ok" situations in Odin, and that's plenty for me. I appreciate the simplicity of handling these things as plain values. I see an argument for moving some of this into the type-system (using Maybe) when it comes to package/API boundaries, but in practice I haven't chosen to use it in Odin.

by leecommamichael

12/31/2025 at 8:55:10 AM

All the standard libraries use naked ^T .

Maybe(T) would be for my own internal code. I would need to wrap/unwrap Maybe at all interfaces with external code.

In my view a huge value addition from plain C to Zig/Rust has been eliminating NULL pointer possibility in default pointer type. Odin makes the same mistake as Golang did. It's not excusable IMHO in such a new language.

by sidkshatriya

12/31/2025 at 11:06:29 AM

Both Odin and Go have the "zero is default" choice. Every type must have a default and that's what zero signifies for that type. In practice some types shouldn't have such a default, so in these languages that zero state becomes a sentinel value - a value notionally of this type but in fact invalid, just like Hoare's NULL pointer, which means anywhere you didn't check for it, you mustn't assume you have a valid value of that type. Sometimes it is named "null" but even if not it's the same problem.

Even ignoring the practical consequences, this means the programmer probably doesn't understand what their code does, because there are unstated assumptions all over the codebase because their type system doesn't do a good job of writing down what was meant. Almost might as well use B (which doesn't have types).

by tialaramex

12/31/2025 at 1:12:26 PM

not every new language needs to conform to your ideas of what is or isn’t excusable / acceptable.

by binary132

12/31/2025 at 2:37:30 PM

> not every new language needs to conform to your ideas of what is or isn’t excusable / acceptable.

That's true. Which is why I wrote "IMHO" i.e. "In My Humble Opinion".

I'm not looking to create any controversy. _I_ don't like Odin's treatment of NULL. If you're cool with it, that's fine too.

by sidkshatriya

1/1/2026 at 8:59:54 AM

Not a Odin user, but iirc odin also has Go like zero values. There is no practical option unless you have null. Like a string cant be null, its "at least" an empty string. But whats a base value for a pointer? A function? An interface? Either you wrap (ocaml style) or use null. Its pragmatism vs correctness, a balance as old as computing.

by phplovesong

1/1/2026 at 2:04:49 PM

Odin's type system is just different to many other languages, and trying to compare it to others doesn't always work out very well.

`Maybe` does exist in Odin. So if you want a `nil` string either use `Maybe(string)` or `cstring` (which has both `nil` (since it is a pointer) and `""` which is the empty string, a non-nil pointer). Also, Odin doesn't have "interface"s since it's a strictly imperative procedural language.

As for you question about base values, I am not sure what you mean by this. Odin hasn't got a "everything is a pointer internally" approach like many GC'd languages. Odin follows in the C tradition, so the "base value" is just whatever the type is.

by gingerBill

12/31/2025 at 7:02:48 AM

I've been actively toying with Odin in past few days. As a Gopher, the syntax is partially familiar. But as it is a lower level language wiht manual-ish memory management, simple things require much more code to write and a ton of typecasting. Lack of any kind of OOP-ism, like inheritance(bad), encapsulation(ok), or methods(nobrainer), is very spartan and unpleasant in 2025, but that's just a personal preference. I don't think I ever used fully procedural language in my entire life. It requires a complete rewiring on one's brain. Which I'd say is a huge obstacle for most programmers, definitely from the younger crowd. For low-level guys, this is quite a nice and powerful tool. For everyone else, it's a bit of a head ache(even Zig has methods and interfaces). The language still lacks basic things like SQL drivers, solid HTTPS stack, websockets, and essentially anything related to web and networking(which has the bare bones functionality). As a Gopher, I am biased, but the web rules the world, so it is objective complaint. In the end, this is a solid language with great support for 2D and 3D graphics and advanced mathematics, which naturally makes it a very niche language for making games or anything to do with visual programming. Definitely try it out.

PS: I just read a funny comment on YT video: "Odin feels like a DSL for writing games masquerading as a systems language."

by gethly

12/31/2025 at 9:51:28 AM

i'm kinda glad it's lacking typical webdev stuff at the moment. if nothing else for developers focus. its absolutely excellent for game development. i have written 2 complete games in odin and working on a third. all using just vendor raylib and absolutely flying. build time, language server, debug cycles. i complete entire features every session, very productive language. i look forward to its maturity

by sitzkrieg

12/31/2025 at 11:14:56 AM

I think Odin should market itself for aforementioned games and graphics. Otherwise it will become very niche language. Even now, I think there is only about 5k Odin repositories on github while it is essentially a complete language. Contrast it with Zig, which is still evolving and has breaking changes, being still at 0.x without clear sight of 1.0, and it has over 27k repositories and big projects like Ghostty, Bunt or Tiger beetle are written in it.

Once Jonathan Blow's Jai comes out next year, the language that inspired conception of both of these, Odin will likely have no chance competing on the marketing side of things with programmers and will be taken over by Jai, and Zig in a large extent as well. So the future of the language might not be as solid as it might seem and it might end up just as an internal tool for JangaFX, which is how it originated.

Having the "web stuff" can attract literally millions of developers whom can elevate the language into more stable and broadly used language. More documentation would become available, libraries, youtube videos, internet presence in general.

by gethly

12/31/2025 at 1:23:50 PM

Did Jon state that he intends to release as open source? I am not sure he is the guy to the stressful route. If it all he would probably go long release cycles without considering public feedback too much.

He also stated recently that he doesn't care too much about language design at a syntax level, or better said it's not his top focus as the overarching concepts are more important to him.

I think there is a chance that people may have a hard time to adopt to the language. His strong focus on gamedev will further cut down the audience. It will certainly draw a lot of attention but a massive adoption is highly questionable.

by Yokohiii

12/31/2025 at 2:24:02 PM

He said that for some period, the compiler will not be open source, the language will. Whether the compiler will become OS or not, I am not sure. But I think he just wants to get the spec done beforehand. I think he mentioned that the compiler code might be purchased via paid licence? I am not certain but as I am not a AAA studio, I do not care either way.

by gethly

12/31/2025 at 12:37:36 PM

> the language that inspired conception of both of these

I haven’t heard this before. Do you have a source on this?

by _bohm

12/31/2025 at 2:21:46 PM

I do not, but John's old videos on the idea of making a language and addressing the modern problems of archaic languages has been cited many times.

Here is a list with his videos where he goes from conception to something tangible: https://www.youtube.com/watch?v=TH9VCN6UkyQ&list=PLmV5I2fxai...

The first video is from September 17, 2014.

Zig's first commit was Aug 5, 2015.

Odin has first commit in Jul 7, 2016.

by gethly

12/31/2025 at 4:26:59 PM

tbf there was a general systems language renaissance circa 2015, following Mozilla sponsoring Rust in 2009 and its impending 1.0 in 2015. Jai, Zig, and Odin were all contemporaries in that wave.

by ModernMech

12/31/2025 at 1:07:30 PM

The language is in closed beta, there isn't exhaustive details available. You can see interviews and some details on YT if you look up Jon(athan) Blow with suitable topics.

by Yokohiii

12/31/2025 at 2:25:59 PM

I meant that I had not heard that both Zig and Odin were inspired by Jai

by _bohm

1/1/2026 at 11:39:33 AM

Oh at least ginger bill does, I remember bill talking about odin's context feature which was loosely inspired by jai.

by Yokohiii

12/31/2025 at 1:06:20 PM

Popularity is not required for a language to be "successful". An argument can be made that popularity can bring fragmentation, a lack of focus, security and usability issues via poor quality code and outdated information, and overwhelm core developers. Just take a look at JavaScript and Python ecosystems. Whereas less popular languages like Nim, Zig, Odin, etc., thrive within their niches.

by imiric

12/31/2025 at 7:32:45 AM

> even Zig has methods and interfaces

Zig doesn't have interfaces as a language level feature. It uses manually implemented vtables and wrapper methods.

You can do the same in Odin with wrapper functions around a vtable.

by messe

12/31/2025 at 7:39:20 AM

There's even syntax-sugar for it in Odin with the `->` operator.

by leecommamichael

12/31/2025 at 12:22:15 PM

This gets you dynamic dispatch, roughly via the C++ route (inline vtables in implementing types). This means you must always pay for this on the types which provide it, even if you rarely use the feature, removing those vtables makes it unavailable everywhere.

A lot of programmers these days want static dispatch for its ergonomic value and Odin doesn't help you there. Odin thinks we should suck it up and write alligator_lay_egg_on(gator, egg, location) not gator.lay_egg_on(egg, location)

If we decide we'd prefer to type gator->lay_egg_on(egg, location) then Odin charges us for a vtable in our Alligator type, which we didn't need or want, and then we incur a stall every time we call that because we need to go via the vtable.

by tialaramex

12/31/2025 at 8:57:41 AM

Oh, nice. I have to admit I'm not all that familiar with Odin, because I've been all-in on Zig for a long time. I've been meaning to try out a game dev project in Odin for a while though, but haven't had the time.

by messe

12/31/2025 at 11:17:06 AM

I have not looked much into it. Someone mentioned it once, so i just remembered it.

by gethly

12/31/2025 at 2:32:07 PM

I am using Odin to write a video game.... why would I want tn HTTPS stack, SQL Drivers, Websockets or any of that? Maybe eventually I might need some websockets if I want multiplayer. But I can also just make bindings to a C library so no real issue there.

Odin is explicitly made for video games.

by ecshafer

12/31/2025 at 8:22:55 PM

Directly from the FAQ: https://odin-lang.org/docs/faq/#is-odin-just-a-language-for-...

> Is Odin “just” a language for game development? # > No. It is a common misconception that Odin is “just” for game development (“gamedev”) due to the numerous vendor packages that could be used in the aid of the development of a game. However, gamedev is pretty much the most wide domain possible where you will do virtually every area of programming possible. > > Odin is a general purpose language; is capable of being used in numerous different areas from application development, servers, graphics, games, kernels, CLI/TUIs, etc. > > There are many aspects of Odin which do make working with 2D and 3D related operations (which are common in gamedev) much nicer than other languages, especially Odin’s array programming, swizzling, #soa data types, quaternions and matrices, and so much more niceties which other languages do not offer out-of-the-box.

by gingerBill

12/31/2025 at 2:39:06 PM

> Odin is explicitly made for video games.

Ginger Bill vehemently refuses this notion and tries to fight in every podcast, to "sell" Odin as general purpose low level language. But he is failing because of my points and your claim just proves it yet again that Odin has profiled itself as language for games when in reality that was never the intention of Bill. There is nothing wrong with that, it's just the perception among programmers.

by gethly

12/31/2025 at 5:23:21 PM

It's also in the Handmade crowd, and for a lot of people that's intimately connected to video games. I actually think Handmade's approach is helpful for games in a way it isn't for a lot of other software.

Games are art. The key thing is that you have to actually make it. Handmade encourages people who might make some art to actually make something rather than being daunted by the skill needed for a very sophisticated technology. Handmade is like telling a would-be photographer "You already have a camera on your phone. Point it at things and take pictures of them" rather than "Choose your subject, then you will need to purchase either an SLR or maybe a larger camera, and suitable lenses and a subscription for Photoshop and then take a college course in photo composition"

I don't want to use a text editor made by someone who has no idea what they're doing and learned about rope types last week. A dozen handmade text editors, most not as good as pico, are of no value to anybody.

But I do want to play video games by people who have no idea what they're doing. That's what Blue Prince is, for example. A dozen handmade video games means a dozen chances for an entirely unprecedented new game. It'll be rough around the edges, but novelty is worth a lot.

by tialaramex

1/1/2026 at 9:22:11 AM

Of all the real commercial apps written in Odin, few are games. JangaFX tools for example definitely involve graphics (like games), but they aren't games.

And what exactly is a "video game" language? A language that's low level and can do fast maths? Almost all commercial games are written in C++, but no one calls that a "game" language...

by dismalaf

1/1/2026 at 7:39:34 PM

Here's my reply to him so I don't have to repeat it all: https://news.ycombinator.com/item?id=46457272

But in short, my hypothesis is because the Odin compiler bundles many graphics-related packages but does not bundle with an official http package, it is therefore "only" for games. He has no idea what games actually involve to make, and it is the most accidental compliment he could give. And we already have an FAQ answer for this: https://odin-lang.org/docs/faq/#is-odin-just-a-language-for-...

by gingerBill

1/1/2026 at 7:37:13 PM

Thank you for the comment and trying out Odin, but there are a few things in your comment which seem a bit off to me. Odin is trying to be C alternative, not a Go without GC.

Odin does share a lot of similarities to Go, even including it's distinct type system. So requiring more type casting than your Go code is actually a surprise to me because Odin's rules a little more lax than Go's, but it's probably because you don't cast as often in Go for whatever reason, probably because it's not actually a systems-programming language, it's mostly just for web stuff. You're not actually dealing with different sized integers and floats all the time, you're just using `int` and `float64` in Go. If you had to use more, you'd actually realize Go requires even more casts than Odin.

Odin does lack methods BY DESIGN. It is not a design flaw, like you are thinking it is. And adding methods is not a "no-brainer", or you could argue adding them is a "no-brainer" as in you are not thinking about the consequences of them. We have a FAQ section on this topic entirely, so I won't copy and paste it here: https://odin-lang.org/docs/faq/#but-really-why-does-odin-not...

As for the other stuff you are complaining about: they're just libraries...

Odin is going to get an _official_ http package soon, and it has been in the works for a long time. It's going to be based on the native kernel async APIs (IOCP, io_uring, KQueue, etc), and from our preliminary tests, is a heck of a lot faster than any of approaches done with Go (and even many of the Rust approaches too).

There is a reason that I "vehemently [refuse] this notion and [try] to fight in every podcast" that Odin is only for games. You're literally saying because the official Odin compiler lacks a single official library (`core:net/http` which is in works as I said) for dealing with http, it isn't for web dev? Are you actually serious? There are third party libraries that already do this, but as I said, we are working on an official one already, which will be coming out this year for definite.

I highly recommend reading the following FAQ question to regarding the entirety of 'Is Odin "just" a language for game development?": https://odin-lang.org/docs/faq/#is-odin-just-a-language-for-...

But saying it is for gamedev is the most accidental compliment you could give it be gamedev is pretty much the most wide domain possible where you will do virtually every area of programming possible. All your comment told me is that you have no idea what gamedev actually involves.

by gingerBill

12/31/2025 at 10:55:01 AM

lot's of what you say is simply not true. Maybe before sharing opinions educate oneself.

by enigma101

12/31/2025 at 2:47:25 PM

This is a delight to read. I've been doing a survey of languages over the last several days, and Odin is one of the more interesting ones... but looking at the OS and FS related parts of the standard library made me back away at high velocity. They seemed like litanies of generated code that simply describe every quirk of every platform: not an abstraction at all. And while I do want those levels to be _available_, I also don't want to be dragged down there in every program.

Delighted to see more work will be focused there in the future.

by heavenlyhash

12/31/2025 at 9:02:34 AM

Anyone have a good comparison of Odin vs C/C++/Rust/Zig/Hare/etc? I'm particularly interested in how simple it is to implement a compiler in the given language.

by apitman

12/31/2025 at 9:21:11 PM

I wrote a simple benchmark comparing my custom dynamic array implementation in Dlang with dynamic arrays in other languages. The test appends and then removes 100,000,000 integers on a Ryzen 3 2200G with 16 GB RAM. This is just a rough cross-language benchmark and not something serious:

Appending and removing 100000000 items... Testing: ./app_d real 0.16 user 0.03 sys 0.12 Testing: ./app_zig real 0.18 user 0.05 sys 0.13 Testing: ./app_odin real 0.27 user 0.10 sys 0.16

Code and benchmark files are here: https://github.com/Kapendev/joka/tree/main/benchmarks/array_...

by Kapendev

12/31/2025 at 2:35:16 PM

Odin is more similar to Zig or C, very similar to Zig I think, but less boiler plate and more similar to Go syntactically. I have also found Odin to be a bit more friendly than Zig to get compiling and running, less required safety. There is far fewer features so I wouldn't really compare it to C++ or Rust. I am not familiar with Hare.

by ecshafer

12/31/2025 at 1:38:45 PM

Don't have a comparison, but I've written toy languages in a few of them.

I only really feel confident to talk about Rust, Rust stands out when it comes to parsing (functional bros unite), but does suffer when interpreting because of unsafe memory access - although for a compiler that shouldn't be an issue.

Odin is great, but I feel like it doesn't have enough syntax sugar to make languages easy to work on - that being said you can achieve most of what you want in a mostly comparable way if you're willing to write more ugly code. In this way it's very similar to C.

by miningape

12/31/2025 at 8:00:36 PM

Odin has dynamic arrays, dynamic hash tables and generics so imho it’s far from C and closer to D and Go, except for not having GC. It occupies the space between D/Go and C I would say.

by brabel

12/31/2025 at 6:48:46 AM

Odin claims to be pragmatic (what language doesn't lol) but "All procedures that returned allocated memory will require an explicit allocator to be passed". Charitably, is this aimed at c/zig heads?

by MangoToupe

12/31/2025 at 7:35:24 AM

> All procedures that returned allocated memory will require an explicit allocator to be passed

All procedures in core/os. Odin isn't removing the allocator from implicit context in the rest of its APIs.

by messe

12/31/2025 at 6:51:55 AM

I'm guessing it's aimed at game development since Vulkan has a similar pattern in every function call (although optional, the driver does it's own allocation if you pass null).

by BigJono

12/31/2025 at 8:08:02 AM

That's a pretty heavyweight pattern. Wouldn't dynamic scope be better?

by astrange

12/31/2025 at 1:02:08 PM

As another commenter wrote "how do you allocate memory without an allocator?"

Even `malloc` has overhead.

> Wouldn't dynamic scope be better?

Dynamic scope would likely be heavier than what Odin has, since it'd require the language itself to keep track of this - and to an extent Odin does do this already with `context.allocator`, it just provides an escape hatch when you need something allocated in a specific way.

Then again, Odin is not a high level scripting language like Python or JavaScript - even the most bloated abstractions in Odin will run like smooth butter compared to those languages. When comparing to C/Rust/Zig, yeah fair, we'll need to bring out the benchmarks.

by miningape

12/31/2025 at 1:54:21 PM

> and to an extent Odin does do this already with `context.allocator`

It has a temporary allocator as well, which could track memory leaks. Not so much anymore though, IIRC.

> As another commenter wrote "how do you allocate memory without an allocator?

I would like to point out that this is basic knowledge. At first I was wondering if I have gone insane and it really is not the case anymore or something.

by johnisgood

12/31/2025 at 3:30:33 PM

> As another commenter wrote "how do you allocate memory without an allocator?"

You call these things something other than an "allocating" and "allocators". Seriously, few people would consider adding a value to a hashmap an intentionally allocational activity, but it is. Same with adding an element to a vector, or any of the dependent actions on it.

Seriously

by MangoToupe

1/1/2026 at 2:21:34 AM

Yep exactly, at it's simplest all an allocator is doing is keeping track of which memory areas are owned and their addresses. In a sense even the stack pointer is a kind of allocator (you can assign the memory pointed to by the stack pointer and then increment it for the next use)

by miningape

12/31/2025 at 5:53:05 PM

For "adding an element to a vector" it's actually not necessarily what you meant here and in some contexts it makes sense to be explicit about whether to allocate.

Rust's Vec::push may grow the Vec, so it has amortized O(1) and might allocate.

However Vec::push_within_capacity never grows the Vec, it is unamortized O(1) and never allocates. If our value wouldn't fit we get the value back instead.

by tialaramex

12/31/2025 at 9:36:07 AM

How do you allocate memory without an allocator?

by ycombinatrix

12/31/2025 at 5:43:59 PM

The usual thing for languages is to provide a global allocator. That's what C's malloc is doing for example. We're not asked to specify the allocator, it's provided by the language runtime so it's implied everywhere.

In standalone C, or Rust's no_std, there is no allocator provided, but most people aren't writing bare metal software.

by tialaramex

12/31/2025 at 5:57:24 PM

That creates problems when you need to use multiple allocators. IMO allowing an allocator to be specified is a superior design.

by ycombinatrix

1/1/2026 at 7:35:07 AM

If you need multiple allocators, and you aren't already using lisp, who cares? Just use lisp. Anyone who can use allocators against the C abi can clearly see the benefits of using a language that can cater to the developer. Zig can never do this, yea even with its shallow macros. Zig will always be a shitty B knockoff rather than something artisans actually want to use

by MangoToupe

1/1/2026 at 6:50:26 AM

You call the use of memory something other than "allocation". Ez.

I thought that passing down allocators was core to the language. I was mistaken. This is actually quite nice for writing allocation code when you'd like to opt into it.

by MangoToupe

12/31/2025 at 7:37:13 AM

All you've got to do is write `context.allocator` to abide.

by leecommamichael

12/31/2025 at 2:52:49 PM

Language explorers looking for lower level languages like this may also want to take a peek at the V language. https://vlang.io/

I won't say with confidence either is better than the other; but I think both are worth a look.

Odin (iiuc) always makes you manage memory; Vlang permits you to, but does also have linking to the Boehm GC that it will generate for you in most cases.

Vlang and Odin in terms of syntax and legibility goals... well. I suggest if you're interested, just quick look will say more than I can. :)

by heavenlyhash