What features of Odin do you dislike?

As the opposite of this post https://forum.odin-lang.org/t/what-is-your-favourite-odin-feature/, I thought I’d ask which features/constructs/aspects of Odin do people dislike.

7 Likes

Having to name return tuples in order to use or_return is a big one for me. I get why it is the way it is, and the current logic makes a lot of sense, but I still dislike it.

3 Likes

It’s not the feature itself, but lacks of documentation when working with WebAssembly including WASM compiler target flags.

seriously, i don’t know how to compile odin code to WASM and working with WASM until i watch gordon2d stream on bill’s youtube channel

7 Likes

Not a “feature”, but debugging support could use some love. Few issues I’ve found so far:

  • Can’t watch members of structs. I get “class … has no member …”.
  • Can’t watch or access variables/procs in imported packages
  • Can’t access whole maps like I can with arrays. I need to watch them key by key with “map.data[key]”
6 Likes
  • can’t import inside when, i have files with just one proc inside… it’s stupid

    • nb: we can import things wich won’t be compiled if not used, so there is that… but, it often fails due to the parser (missing type), for example when wanting to compile to wasm but you have a dangling import there, compiler could help a little bit here
  • llvm, since there is no such thing as incremental compilation in odin, so i’m not sure if i’m willing to use it for any big projects, compilation speed will be slow

  • lack of interface/trait system, wich mean you rely on ducktyping function pointers, it both is annoying, and is not error proof, so i’d love some compiler help in there, a very basic trait system would be nice to have, just to make sure the signature is correct

  • context system, i never use it, on paper it’s a good idea, but in practice i prefer to pass an allocator when needed, would love to be able to mark an entire module: “contextless”

That’s it, everything else in the language is perfect, it’s literally the best better C language out there, and there is an LSP that’s also perfect, great stuff

I don’t like attribution requirements in the standard library, in case of that being considered a feature.

I understand the “dislike” but it’s also it cannot be any other way. It’s kind of complaining about something where there is no alternative :expressionless:

1 Like

@gingerBill I watched a interview where you told that you don’t like using inside a proc to put members of a struct into the current scope. Do you ever consider to remove it?

I think @(require_results) being the default and having @(optional_results) instead would have better suited Odin.

4 Likes

I think that multiple return semantics are not flexible enough. I’d love to be able to do something like a, b := cast(f64, int)some_proc(). Having to store the initial variable, then make new ones converted to the type you want is kinda annoying.

4 Likes

This might work for you:

package main

@(require) import "core:fmt"

// One such each for the number of return values.
cast2 :: #force_inline proc($R1: typeid, $R2: typeid, a: $A, b: $B) -> (r1: R1, r2: R2) {
	return R1(a), R2(b)
}

p2 :: proc() -> (u8, f32) {
	return 42, 21
}

main :: proc() {
	a, b := cast2(int, f64, p2())
	fmt.printfln("%T: %v, %T: %v", a, a, b, b) // int: 42, f64: 21
}
5 Likes

Not a bad shout. Thanks!

1 Like

One of the main reasons that it’s opt-in to @(require_results) is purely because Odin is trying to be a C alternative and because of that, a lot of Odin code will act like and interact with C code.

I know what it’s like from other languages that do opt-out approach and it’s honestly painful to program in.

1 Like

We are going to change the licence soon. We just haven’t decided exactly what it should be!

3 Likes
  • The reason import is not allowed within when is that people don’t realize how either construct is evaluated. It might be annoying that there is “just one proc inside” each file, but that’s because we want you to keep the platform specific code in their respective and separate files. All packages are always checked at all times (due to Odin’s out-of-order type checking) which means conditional imports would never work the way you’d expect them to do within a when statement. The only thing that it would do is make the import name be conditional.
    • I know a lot of people are used to doing conditional imports in C but that’s literally doing conditional checking of the contents too because #include literally joins the token streams together.
  • A lot of people may ask for “incremental compilation” because they view that as a “solution” to a problem of slow compilation times, but the real solution is to just not use LLVM. We probably have one of the largest (if not the largest) Odin codebase at work, and even 600KLOC compiles is about 2s for me. I think the worry of compilation speed being slow is a concern but it is much less so that C++ projects.
    • We are working on the new backend so that compile times will be a lot faster. You can see with -show-timings or -show-more-timings what the bottlenecks are and it isn’t the Odin frontend.
  • The lack of interface/trait system is by design. Odin is an unabashed procedural language with no methods, operator overload, nor anything like that. This means adding an typeclass system makes very little sense considering how other C-likes do it. I understand the complaints that it isn’t easy to restrict with where clauses sometimes. I am personally not a fan of duck typing in general, and I find that it is a bit of an antipattern.
  • This might sound weird but the point of the context system is that most people do/should NOT use it at all. I expect the vast majority of programmers, even C programmers, to still do malloc/free like allocations and as such just rely on whatever context defaults to. It’s not there to be used to pass allocators around but rather override allocators used by third-party code. I try my best to explain it succinctly in the Overview: https://odin-lang.org/docs/overview/#implicit-context-system. A lot of people
    • If you are using custom allocators in the first place, I do recommend passing them around, or even just using them directly rather than the runtime.Allocator interface.
5 Likes

I kind of get why, but I wish you could do e.g. foo :: proc(arr: $T/[$N]$E) -> (a: [N + 1]E) even if it required adding a where to ensure N was an integer type instead of an enum. But since those are the only two options it feels like it should be possible.

Also +1 on the casting. it feels like there should be some way of casting n return types without needing n castN functions. Mostly because I usually only want to cast one of the return values

  • This is a standard library design complaint: allocator := context.allocator is pretty handy when you are writing one-off programs, but it becomes a pretty sad footgun when you are quickly prototyping and you eventually don’t have a way to tell what functions allocated when you are just looking at the code. I’m not sure if requiring the explicitness here would be desired for everyone, so my suggestion would be, if technically possible, more -vet flags. What I think could help people are -vet-implicit-context-allocator. In the same idea of “catching dumb stuff you did while prototyping”, i would also suggest -vet-optional-ok and -vet-optional-allocator-error

  • union still feels to me quite clunky, and not a full replacement of #raw_union + tag in terms of ergonomics. Some people suggested discriminating on the field rather than the type, but i feel both approaches are a matter of compromises. I can’t pinpoint right now what are all the pain points of using it, but even something really small like if Type in my_union could help a bit to make it feel more natural (i know the argument against it is that you usually need the values inside aswell, but it’s a small feature that could help when interating and debugging things). The other suggestion was maybe to integrate #raw_union + tag pattern into union as an alternative way of using them, or optional field names.

  • or_return seems to be a “quick way” to get an error out of the way, but it seems like you need to plan quite a bit ahead before being able to benefit from it. I don’t really have any suggestions for it, my only “complaint” being that the situation where you can use it is very specific, requiring you to have the same error types.

  • v/$T pattern to catch distinct types. Feel free to correct me if i’m wrong in the technicals of this, but what i understand is that this requires you to create a parapoly procedure when all you want is to catch distinct types. Generic functions are usually more annoying to work with, and if that’s all that was desired, maybe something like #any_distinct could be added?

This is all that comes to mind. I have a 10x times larger list for the things i like:) . I don’t care much about most of the things there, but i think my first point is something that’s worth having an extended discussion on.

1 Like

I can imagine. The standard library is also a different beast from other libraries, if it’s statically compiled. You’d end up with some GPL+compile exception (eew!), or 0BSD, if you want to avoid attribution (because you can assume usage by being the standard library), right?

This might not be the right topic, but I think for me the thing that throws me off the most is the lack of high-level networking packages. A lot of the work I do revolves around making http calls and even more so websocket stuff.

Based on the documentation it looks like the net package has some of this planned, but it seems like it’s been that way for a while. :slight_smile:

So I guess to confirm, are there plans to include high-level networking clients for http and websockets?

Id even be willing to pay bounties to get these done :slight_smile:

  • Is a good point. I’ll have to think about it a bit more to know what to do.
  • There are a few aspects to this which I’d like to break down:
    • union might feel clunky but I don’t think the alternatives would be any less clunky
    • The alternative would be Pascal-like unions where you specify what the tag is directly and then specify how each value of the tag relates to a type.
    • if Type in my_union has been suggested numerous times but as you correctly point out, people want it but then always want the value of it straight afterwards. It’s effectively an anti-pattern feature if it was to exist.
    • The current approach is also a lot safer in its design because it forces you to handle the variants correctly.
    • A Pascal-like approach would be less visible that an assert would happen. e.g. u.(Type).x is lot clearer that it asserts than doing u.label.x. I didn’t want to hide the type assertion operation.
  • or_return having friction is by design. The friction here isn’t the mechanism itself but the lack of a degenerate error type, which is what I find to be THE problem in a lot of other programming languages. It devolves into “Pokémon Error Handling” where you’ve “gotta catch them all” in a single place—meaning you don’t actually handle anything ever but just crash/exit/assert.
  • The $T/Foo pattern is to catch distinct variants as well as capturing the actual type with T. It might feel weird purely because distinct is not common in other languages, so the practice of adding the $T/ like prefix is easily forgotten.