Hacker Newsnew | past | comments | ask | show | jobs | submit | gingerBill's commentslogin

In the first case in a language with type inference, I'd argue it would probably be written a bit different. And probably something like the following two cases:

    x := &bar
    if is_foo { x = &foo }
    // or
    x := &foo if is_foo else &bar
I know this is a simple example, but it's kind just a different way to think about the problem. Sometimes the default C way of having to specify the type first is a good enough of a nudge away to not do when you can just infer the type from the expression. Especially when this variable is being used not as a variable in itself but as an alias for another. The place where it is more likely to have a `nil` pointer due to no explicit initialization in Odin would be within a struct, rather than on its own on the stack.

In the second case, Odin's approach would probably just not even do that, but rather `f, b := grab_data()`. The use of `&` is when it needs to be an in-out parameter (i.e. read and write to), which means it has different functionality.


The thing that is at odds with Rust is the second part of the article: the difference in mindsets.

Rust by default doesn't really encourage the group-element mindset and people have to work against the language in order to do it. And what usually happens is people work around the borrow-check as a result, through the use of a handle system. And they do this in order to get the lifetime benefits, performance benefits, and also get around the problems of the ownership semantics.

Of course you can do this in Rust, but the point is that the default approach in Rust does not encourage it at all. The default Rust mindset is the individual-element mindset.


> Rust by default doesn't really encourage the group-element mindset and people have to work against the language in order to do it.

It's true that you don't see stuff like arena allocators very often, but Rust's lifetime/ownership semantics mean it's often best for related pieces of data to be owned by a single parent, which unifies their lifetimes such that they all start and stop being valid at the same time. I suppose that's only part of the equation, since an arena has you allocate everything at once in a continuous block, which has performance advantages.

Still, I can't help but see an underlying conceptual symmetry there. I feel like the scoped arena school of memory management would be nicely complemented by compiler lifetime analysis. Arenas definitely simplify lifetime issues, but that feels like an opportunity to experiment with additional static compiler checks, not a reason to eschew them. I'd love to see a language which required explicit deallocation but enforced it with linear types.


To make `Maybe(T)` feel like a multiple return, you just need to do `.?` so `maybe_foo.? or_return` would just the same.

But as you say, it is more common to do the multiple return value thing in Odin and not need `Maybe` for a return values at all. Maybe is more common for input parameters or annotating foreign code, as you have probably noticed.


The first paragraph from the article:

> *TL;DR* null pointer dereferences are empirically the easiest class of invalid memory addresses to catch at runtime, and are the least common kind of invalid memory addresses that happen in memory unsafe languages. The trivial solutions to remove the “problem” null pointers have numerous trade-offs which are not obvious, and the cause of why people think it is a “problem” comes from a specific kind of individual-element mindset.


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-...


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.


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.


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.


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.


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


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


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.


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).


> 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.


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".


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 !


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".


> 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.


The code example is more to show the scoped copy-on-write behaviour of `context` more than how it is used in practice. I agree it might be a bit confusing and thus needs to be made clearer that is what the code example is meant to show. However, I don't there is any code example that could show "correct usage" of `context` without it defeating the point of it. As I say in the article, it makes more sense to leave it "confusing" because then people will ignore it.

The Overview is just that, an "Overview" and it's not meant be either a spec, a heavily detailed tutorial, nor an explanation of why a language construct is designed the way it is and/or why it exists. The latter of which would probably be as long as the Overview for each construct that exists if that.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: