the lack of Android support (there is way more people using android than windows) and the slow llvm backend.
Error numbers in os
. It’d be nice to have an enum for that, but I don’t know how wonky that would get between different OSes.
It’s a minor thing, but I don’t have much to complain about.
I’ve said this numerous times before elsewhere but:
core:os
is a mess.
We are trying to improve it a lot with `core:os/os2` which will eventually replace `core:os`. That has a much better, more consistent, and more unified error value system.
We’d love to support Android but it pretty much requires someone to go and implement it officially for us and make the experience painless. We know some people have done it unofficially already.
A HTTP and then websocket package are work in progress!
The whole thing about a ‘directory’ being a ‘package’. Combined with circular dependencies being illegal, this makes directory organization a lot more difficult than it has to be.
Coming from a more C# ecosystem, I’d prefer an approach like this:
- Being able to mark up a directory as a package (via a
json
or something) - The files inside this directory (as well as all its subdirectories) will be treated as a part of the same package.
- This makes it infinitely easier to reorganize files/directories (for refactors) without worrying about how the file will be referenced.
BSD-1-Clause
only requires attribution with source code redistribution. It however lacks the 3rd clause from BSD-3-Clause
related to endorsement. I remember you mentioned on the GitHub discussion regarding this that you changed from 2-Clause to 3-Clause because more people were contributing.
You could perhaps take the source code attribution requirement and the endorsement requirement and make your own license from the two. spdx.org tracks this combination as BSD-Source-Code
so it’s not a totally novel idea. It doesn’t seem to be a frequently used license though.
I haven’t dug too deeply on alternatives, so I haven’t much else to add. There could be better licenses out there.
If it was just me, I’d go to just BSD 1-Clause. But my worry is that going forward, the third clause of 3-Clause would be good. The second clause of 2/3-Clause is the fundamental problem.
So if no one has issue with going forward with 1-Clause, then maybe that’s the best option? I am just not sure.
TL;DR a package
is a directory of .odin
files which can all see each other. A package
is a library and not a way to taxonomize your code.
The main point is that all files within a directory can see each other without needing to explicitly import any. This solves a lot problems from other languages where this is not trivially possible, nor easy to maintain.
Not allowing circular dependencies is a MASSIVE feature. The point is that Odin is trying to have a well defined concept of what a library is, and that’s what packages are: a library.
An “import cycle” means whatever that full cycle is, is the full library. And thus it is now not well defined anymore concept in the language itself.
The other issue is that many programmers like to taxonomize their code thinking that they are “organizing” it when in reality it is just another thing to manage on top of the code itself. With Odin, I have explicitly tried to make things as flat as possible and minimize the possibility of artificial taxonomies and artificial hierarchies. Hierarchies will naturally arise within a program; there is no need to add one artificially.
In Odin, I also wanted a single concept of a what a package was rather than multiple. With your description of C#, that sounds like it has at least 3 ways of describing what a “package” is, which means it does not have a well defined concept of what a package is.
Defining metadata in an external file is not something I wanted BY DEFAULT. This might be necessary writing things like the dependencies and versions of a package, but not to just start working on a package.
As for your last point, you might argue it is “infinitely easier to reorganize files/directories (for refactors) without worrying about how the file will be referenced”, but that is begging the question in the first place. If you keep everything flat, then you don’t need to worry about how things are referenced hierarchically because there isn’t one in the first place.
I kinda wished I could namespace my things within a package.
So in my game project I have entity package. Entity holds things like weapons, player, enemy, items, walls, basically anything a level is made of.
The organizing tinkerer in my head wanted to make these all in their own packages, so I would be calling things like enemy.Update
and so on. But due to cyclical dependencies this got difficult, so I ended up prefixing the similar procs like EnemyUpdate
, PlayerUpdate
with what entity they’re for.
Is this the best way of doing things? Probably not, and you can organize your code is so many ways I don’t think there’s one answer. (No need to tell me your preferred way, my point is not to derail the thread here with that).
So that’s why I wouldn’t mind having a namespace within a package. So I could do things like package.namespace.Procedure()
. Namespace doesn’t really do anything else except adds some organizational layer there.
Now is this really required? Nah, you can just do package.NamespaceProcedure()
, which is fine. But for some reason it doesnt really feel right. Maybe I am just way too used to other languages that allow this.
I understand the desire for adding “just one more level” of taxonomy but it’s not really providing anything useful. And it has the issue that is common in languages like Java and C# where you will have a.b.c.d.e.f
levels of nesting.
I think the feeling as to why it isn’t “right” is just down to you want to make this taxonomy/hierarchy and don’t like that you are embedding the hierarchy directly into the identifier. But in reality, the hierarchy and identifier are never separate and you end up typing it any way.
And if you didn’t notice, this is why Odin’s struct
s only have fields and cannot define any other declarations inside of them. Allowing other kinds of declarations within means you have a namespace (just like how C++ had them before namespace
was added). And this then leads to quite a bit of hell. Even Odin’s choice of separating fields with ,
rather than ;
/newline was an explict choice to prevent even the possibility of nested declarations within the syntax.
This is likely it. Most of my life I have worked with languages like Python etc. that allow nesting to absurd levels, so it’s very much a learned habit.
Like I said, it’s really not a major issue and probably a net positive that I can’t do it… Because otherwise nobody would be able to read my code that is already an organizing nightmare.
Thanks for the good explanation on these choices!
Not being able name ranges, I’d love to have the ability to say something like Valid_Values :: 0..<10
as some form of “anonymous enum”.
Not allowing ranges to be a first-class concept is by design. If they were first-class, there two possibilities that they could be: a type or a value. It’s a rabbit-hole feature I do not want to ever deal with.
-
In the case of a type, you’d also want to allow them in enumerated arrays, and things like
[1..=10]T
would be possible, which is not something I want to even entertain. But just having the pseudo-enum exist does restrict its usefulness. -
In the case of a value, it would have to incorporate both the kind of range and types, and store the values somewhere too. And people would pass ranges around as values, which might sound fine, but also lead down the rabbit-hole of many of many other things I do want to do. It’s also not as useful as you might think in general.
It’s not a small construct.
Disclaimer: I haven’t thought deeply about any of these ideas, or any technical or design implications they may have.
- Anything that gets imported necessarily must have a namespace prefix. Makes working with code that I have in libraries for use in multiple projects more noisy. Something like
import math "mymathpackage"; using math
just so I don’t need to typemath.Vec3
would be cool. My number one nit by far. - Semicolon insertion. I’d prefer to just control stuff myself. Occasionally I get errors from a semicolon being inserted where I didn’t put any, especially when I’m putting stuff on newlines to try and keep things easy on the eyes. e.g.
a := foo <newline> ? something <newline> : something else
. My number two nit. - I something wish things would cast where not lossy e.g. u8 to u16 or something.
- Fixed arrays don’t cast to slices. IMO the sensible default is cast to a slice of the entire array.
- builtin map and dynamic array. idk how they’ll interact with my custom allocators often or there’s stuff I need that they don’t have. I don’t use them often.
- tagged unions. it feels like maintaining an inheritance hierarchy honestly. If two of the variants have something in common, do i factor it out into a struct containing the union? I tend to just do structs that have everything in them and an enum variant if needed, and its obvious from the variant what other fields are useful. I suppose if I need to make something smaller for perf or whatever I can reach for tagged unions as a means of compressing it
A wishlist of things I think would be handy, in descending order of personal interest:
bit_set
s larger than 128 bits e.g. for keyboard input and entity properties (gameplay code)- a flag to error on casts not done with the cast operator
cast
e.g.int(thing)
. Being able to search for casts is a huge win for me and I’d love to be able to enforce that. - specify which collections a set of vet flags applies to (e.g. my own vs third party modules)
- set the type of ix in
for elem, ix in thing
- a general procedure to get the “number of bytes” in something. e.g. the number of bytes in the array pointed to by a slice or the size of a struct in that case etc. nice especially when passing byte buffers to c code
- the ability to use turns instead of radians for trig functions (and for the math libraries in general)
- switch expressions, a natural extension of ternaries and if expressions
intptr
- a signeduintptr
a, b, c := something; a, b, c = something_else
(a, b, and c are the same type and all get the same value).- multiple declarations in the body of a normal for loop
for a := 0, b : ^mytype = nil; blah; blah;
foo :: struct { using bar: [3]f32 }
- if i could dofooinstance.x
that’d be cool- @read_only @static var: int
I don’t think they are fundamentally different from enumerations, or at least not if they are treated as types.
You can already do
Foo :: enum {
A = 1,
B,
C,
}
Foo_Array :: [Foo]int
for foo in Foo { /* ... */ }
min(Foo)
max(Foo)
Which behaves just like [1..=3]int
or for foo in 1..=3
, the only difference is that every value is manually enumerated.
Almost nothing in Odin has annoyed me―and anything that did I understood the reason for easily―but this one definitely takes the cake as the only thing that has really felt like it just caused unnecessary friction.
It’s not deal-breaker, but it’s annoying on the occasion.
- Lack of goto. I like having gotos in order to “sketch out” direct control flow even if I end up factoring them out later, and sometimes my initial idea of how I want a problem to be solved doesn’t fit neatly into if/else blocks.
+
and-
operator for bitsets because it can add overhead for translating from Odin bitsets to Odin bitsets, and since|
and~&
already exist for bitsets and work the same, it’s convenient but redundant.- Obfuscation of lower level details in some higher-level constructs. Like being unable to get and work with the tag value of a tagged union directly, unable to dot access a slice’s pointer or length (which could get people mistaken that a slice is voodoo instead of just a pointer+length).
- Implicit broadcasting a single value into an array, for proc parameters that take arrays as arguments. There’s luckily #no_broadcast, but I feel like #no_broadcast should be the default behavior for proc parameters, since it’s far harder to remember/see from a glance what’s going on behind a proc call, versus seeing variables declared with broadcasted values.
The successor to os (os2) will fix this I believe.
- There is no
goto
, but there are labeled breaks.
path: {
do_something()
if cond_b {
break path
}
// The rest of the block does not run if cond_b is true.
do_something_else()
}
finalize()
- You can transmute a slice back and forth between
Raw_Slice
or any equivalent structure. I haven’t tried to manually get a union’s tag value before, but it might be possible doing something like that.