I like Odin
hasenjudy.wordpress.com
hasenjudy.wordpress.com
Odin's niche is the kind of high performance programming that's done in games and other real time visualization applications.
Other than fixing C's known issues, like having proper tagged unions, fixing macros, and the crazy compilation model among others, it has a bunch of features, that make it appeal to game programmers.
Features, such a structure of arrays support for data oriented design, builtin support for matrices, vectors, and shader-like syntax for 3D math, allocators and the like.
And it certainly doesn't hurt that unlike many of these upstart languages, Odin has a real, successful, commercial product built in it, EmberGen, which is used for CG smoke and flame simulations:
// inferred type:
a := 23
// explicitly typed, the type is squeezed between the : and =
a : u32 = 23
OTH constants now require special syntax: a :: 23
...but at least this is consistent with other places in the language, like functions: my_func :: proc(...)
...and 'squeezing in the type' also works as expected for constants (which admittedly looks a bit weird, but is also hardly needed): a : u32 : 23 <const_name> : [type] : <value>
<var_name> : [type] = <value>
where the type is always optional as it can be inferred?That's why `a :: 32` declares a constant, but `a := 32` declares a variable...
This is pretty neat, actually.
> If your “dread” of C comes from fear of memory management, then Odin is probably not for you, and dare I say, maybe systems programming is not for you.
I have to say that my dread of C definitely comes manual memory management. The awkward syntax and compilation model I can tolerate. But having your program expose critical security vulnerabilities because you forgot a weird edge case while managing a pointer is really worrying. For simple programs is not that complicated, but for complex multi threaded ones it becomes really hard. And the fact that event the most expert programmers make these mistakes, leaves me not much hope.
So perhaps systems programming is not for me? But what exactly is systems programming? Is it developing OS kernels, writing drivers and embedded microcontroller systems? Or is it more.
I'm not very interested in writing any of those. But I do want to write programs that are fast and run as fast as C or C++, and I want a language which allows good control of resources and has minimal overhead. Sometimes these programs are multi-threaded and quite complex, so I want memory and type safety and I want tools to crate abstractions to tame complexity a little bit. Is all this out of the systems programming definition?
That said, the way to deal with memory management in C is to... not do much of it. I know that sounds like a cop-out but the patterns you're supposed to use in languages like Zig and Odin are the same ones you'd use in C to keep your mind sane.
Do not do Reference Counting or Single Owner + Borrowing (RAII), it'll drive you insane without the automation languages like Swift and Rust or C++ give you.
Instead, the way you're supposed to do things, are big "manager objects". Only these manager objects are ever explicitly allocated and freed, everything stored within them is managed by them and they expose only safe handles to the outside world (e.g. generational handles).
Most of the time these manager objects will store some dynamic arrays, hash maps or pools within them that get freed when they are also freed. Note that unlike RAII there is no real "nesting". The managers are responsible for the lifetimes of their whole "object tree".
Any temporary data should be allocated using a temporary allocator (like an Arena allocator) which gets freed at the appropriate place for the application (end of a frame in a game, or end of a request in a web server). Never store pointers to something within the temporary allocator in "long lived" data structures inside the manager object.
Follow these rules and things get manageable, Zig and Odin have lots of facilities in their standard libraries to make this easier, in C you're mostly on your own but there are some libraries you could use like the Apache Portable Runtime.
use std::io::Write;
fn main() {
std::io::stdout().write(b"Hello, world!\n").unwrap();
}
The reason people often use println! is that println! is variadic and can support any number of arguments to format in the string. For example: println!("x is: {}", x);
This also has the benefit of allowing type checking at compile time, as opposed to using a function like printf in C, which does not have such power. Additionally, this allows Rust to take references to the variables entered without the programmer's specification, in order to prevent unnecessary copying.These reasons tie directly into Rust's philosophy of making it easy to write reliable, performant code.
1. Null pointer deref. Can be fixed by having optional types and requiring that possibly null pointers have to be wrapped in them.
2. Out of bounds references. Can be fixed by making the type system track how big all objects are, and having the compiler insert bounds checking.
3. Use after free. Can be fixed by the free function zero'ing heap objects smaller than a page (eg 4KB), and unmapping larger ones so that future accesses are a seg fault. The heap also needs to not create new objects at the same address as deleted ones, but we have 64-bit address spaces, so maybe that's fine.
These all have costs, but so do all solutions to these problems.
I can't think of any reasons that a language with manual memory management has to be less safe than one with a Garbage Collector / ARC.
`Maybe(^T)` exists in Odin.
Bounds checking is on by default for all array-like access. Odin has fixed-length arrays, slices, dynamic arrays, maps, and #soa arrays, all of which support bounds checking. Odin does not have pointer arithmetic nor implicit array-to-pointer demotion which is pretty much removes most of the unsafety that languages like C have.
Odin also has built-in support for custom allocators which allows you do a lot more with extra safety features too beyond the default allocator. Use-after-free is usually also a symptom of an underlying value responsibility and lifetime problem rather than a problem in itself, of which is fundamentally an architectural issue. Ownership semantics in a language in Rust does deal with this issue BUT it does come at a huge cost in terms of architecting the code itself to accommodate this specific way of programming.
There is a common assumption amongst many of the comments that if you have manual memory management, you are defaulting to memory unsafety. This is untrue and memory management and memory safety are kind of unrelated in the grand scheme of things. You could have C with GC/ARC and still have all of its memory unsafety semantics.
One really good approach is to not use pointers in the first place and use handles. I highly recommend this post for more information: https://floooh.github.io/2018/06/17/handles-vs-pointers.html
Because use-after-free is a responsibility problem, handles are a way to make sure that a subsystem has responsibility over that memory directly rather than have it spread out across the program.
This is why Odin nor Zig "solve" this problem: solving it at the language level is not necessarily the best option.
This path leads you to something like Rust, which is safe without garbage collection or ARC, but I also wouldn't call it "manual memory management". The trade off they took for this is complexity in the language.
GP's proposal is to drop lifetimes and RAII (thereby simplifying the language semantics), make the programmer responsible for allocations and frees (as in C), and solve the temporal memory safety problem by doing additional work at runtime to ensure that use-after-frees reliably crash the process instead of overwriting return addresses or doing other arbitrarily bad things. Whether this is more fun to program in than RAII depends on whether you think it's better to suffer from too much abstraction or too little; people have sharply diverging intuitions on this and it's been a holy war since forever and it probably always will be.
The clearer-cut problem is that such a language would be slower than C or Rust, both because freeing memory involves extra work that C and Rust programs don't have to do, and because the requirement that use-after-frees must reliably behave a specific way inhibits optimization, since the compiler can't assume that use-after-frees don't occur. Also, using memory from a small allocation that's been zeroed out doesn't reliably crash the process unless a pointer in that allocation is dereferenced, and even then, this (contra point 1) would require the compiler to assume that null pointer dereferences can happen and must segfault, which, again, inhibits optimization.
Yes.
> are you suggesting that the compiler (or other static analysis) could catch all of the issues you listed?
Yes, the compiler for the first two and the standard library's heap implementation for the third.
> This path leads you to something like Rust
There are stops along this path before you get to Rust. If you just add the 3 things I mention above to a C like language, it would still be perfectly possible to leak memory. But that isn't a safety problem.
The 3 things don't include a borrow checker. You could still make doubly linked list and graph data-structures.
See, having memory safety did not prevent the language from causing arbitrary code execution vulnerabilities. Having the log4j project be open source and popular did not prevent that either (so much for the "enough eye balls" theory).
Going back to Odin, when I think memory safety is not as big a concern as people make it out to be:
Your only source of concern is C.
This would be like judging SQL statements as fundamentally unsafe because websites written in PHP tended to (specially in the early 2000) be written in a very unsafe manner where user input was put directly into SQL strings.
The lesson that people took is not to throw SQL out the window, but to properly sanitize user input before passing to the queries, and to never use plain string concatenation when doing that.
So for manual memory management, the lesson to take from the vulnerabilities that C has caused is not that manual memory mangement is bad. It's that you need some facilities in the language to minimize the chance of them occurring by several orders of magnitude.
Odin does this by providing the slice type (and string type) that have their length known and providing several custom allocators out of the box.
The cool thing about the slice type is not just that the length is known: the language provides facilities for iterating over the slice that automatically never goes out of bound:
for item, index in slice {
// do something
}
This, and providing a "string builder" type into the core library that lets you dynamically construct a string in a safe way (you don't have to write the code to grow the string dynamically because it has already been done).These features make the "fear" of unsafe memory access largely unwarranted anymore.
What remains is a matter of what attracts you to programming: are you interested in having explicit control over a system to make it do what you want, or are you more interested in expressing some abstract ideas in an abstract mathematical virtual machine? If the latter, you might find Haskell or Lisp more appealing.
Σ exploits = Σ memory_corruption + Σ logic_errors
Having Σ memory_corruption ==> 0 is of course much welcomed outcome, even if Σ logic_errors > 0.> This would be like judging SQL statements as fundamentally unsafe because websites written in PHP tended to (specially in the early 2000) be written in a very unsafe manner where user input was put directly into SQL strings.
This is indeed a good example, but supports my argument rather than yours. If we had devised ways to make it impossible (or incredibly hard and weird) to introduce sql injection vulnerabilities at the language level, then that would be excellent. One less thing to worry about!
I don't have a unique grudge against memory safety issues. It's just one type of issue that we've spent a lot of time devising solutions to that don't just boil down to "be very careful" and I'm generally supportive of any solution to any problem like that, if the tradeoffs are acceptable.
But I agree with you that it's a great thing to have language and library support that make memory safety issues significantly less common and problematic (to be clear, I only just heard of Odin from this article, but I think it looks pretty awesome on initial glance), and that that gets to the level of solution that we have for sql injection in practice. I think there are somewhat better alternative solutions available in the case of memory safety, but they have different tradeoffs.
> What remains is a matter of what attracts you to programming: are you interested in having explicit control over a system to make it do what you want, or are you more interested in expressing some abstract ideas in an abstract mathematical virtual machine?
I'm mostly interested in the first thing, but I think these are both false choices. There is a language that provides that explicit control with more memory safety (rust) with a different trade off (language complexity), and most other languages are memory safe without being focused on expressing abstract ideas in an abstract mathematical virtual machine (go, java, python, etc. etc.).
Of course, you also mentioned C's syntax as awkward for you, so you might feel the same with Go's syntax.
There is also the issue of, what is fast enough? A lot of times, people don't really have the speed requirements they say or think they do. To include the code they have written, could be better optimized. It's often better to be more specific about what the requirements are, before making blanket statements about a language not being fast enough. Clearly, many people are fine with how fast Go is.
https://github.com/lotabout/skim/issues/317#issuecomment-652...
While Rust does move a lot of errors from runtime to compile time, there are still a lot of ways to create runtime errors (which must obviously the case if your program handles any input at all).
Rust also does not stop you from creating memory leaks; there's even the `Box::leak()` method[1] that allows you to simply leak a heap allocation.
[1]: https://doc.rust-lang.org/std/boxed/struct.Box.html#method.l...
And yes you can write non-idiomatic Rust that lets you leak memory but it doesn’t happen by accident like in C/C++.
Rust code can still panic and unwind at runtime. There's some ongoing work on supporting guaranteed-not-to-panic code for very specific uses (similar for guaranteed-not-to-leak, which is a related problem), but it's a long way off and will not be applicable to anything that must interact with the system in any way.
Exit code of white smoke signals success.
Be sure to run decompope first though.
And personally I think (!) there is no reason to introduce a new programming language without RAII these days. If you don't solve memory management any more than C did (not), then you are ignoring the biggest problem that needs solving and every replacement that does address this problem looks more attractive.
As for RAII, the article itself showcases some of the alternatives Odin has with the `defer` statement and `deferred_*` attributes. So Odin can have many of the aspects of RAII without tying it directly to a data-type/struct/class, which itself as many costs and trade-offs. RAII has loads of problems to it and many other things which are required to solve those problems.
This language is just not for you, and that is absolutely fine! But don't proclaim that it's "ignoring the biggest problem that needs solving" when you are assuming everyone has the same needs, requires, and desires as yourself.
n.b. I am the creator of the Odin programming language; go use a language that will help you solve your problems with your requirements.
One can use both default RAII in a programming language as well as opt-in unsafe memory management. So you are clearly wrong in your assumptions about what the OP must clearly not require/want.
RAII may not even make sense within the type system of a language too. Languages with RAII (C++, D, Ada, Rust (through Drop), and Vala) all have higher level constructs such as methods, and many have automatic memory management too (Rust's is automatic but at compile time).
First let's take C++ as an example of a RAII language, RAII has numerous flaws to it:
* In practice (but not necessarily) couples allocation/initialization together meaning that allocations are rarely bulked together and are scope-governed (which can be very poor with performance). (And I know placement `new` exists, but that isn't implicit RAII any more and doesn't solve any of the other problems)
* C++ originally only had copy constructors which usually involved loads of implicit allocations everywhere. This lead to copy-elision optimizations and the introduction of move/ownership semantics as a way to minimize these issues.
* RAII is very implicit to the reader that it is even happening, meaning you cannot just read the code and know what is happening.
* Ctors and dtors usually have to assume to never fail, or require exceptions to handle the failure cases. And not everyone wants, or can even have, software exceptions.
In sum, adding RAII is not a minor thing and to make it even useful without too many flaws requires loads of extra things on top. It's not a simple construct.
I also never said anything about memory safety in my comment. You can have memory safety and manual memory management. The question is what level of safety and in what form. Odin has pretty much all the general memory safety features (bounds checking, Maybe types, distinct types, no pointer arithmetic, default to slices, virtual memory protections, and many more). What Odin does not offer is ownership semantics and lifetime semantics, which is an entire discussion itself which I won't talk about in this already long comment.
* You can just have your constructors not initialize your member variables. If they don't have default constructors that do work, then no work is done.
* Yes, originally. Move-semantics are part of the language since C++11. Enough time has passed.
* I read and write C++ every day at work and in my free time and I rarely find this to be a problem. If I see a non-trivial type, I assume it's destructor is called at the end of scope. And whether I have to look up the destructor of some type or a corresponding free function (like in C code usually), makes no difference to me.
* Gamedev can live entirely without exceptions and many other software projects do as well. You just have to write your constructors so that they do little work (which is encouraged anyways) and use methods to initialize them. Having static methods that return an optional<T> is also pretty common.
Of course you can also have constructors that do a ton of work, so you never know what is being done, exception handling everywhere to error handle all that code and not use move semantics or very old C++ versions. There are always ways to use a language in a bad way and get bad results. I am sure you have seen bad C code.
Of course RAII is not simple. It's extremely complicated, which is why I consider it missing. Using a new language needs be justifyable by some significant added value.
So your constructors don't do any construction?---defeating the entire point of a constructor.
> Yes, originally. Move-semantics are part of the language since C++11. Enough time has passed.
First, move-semantics solve a lot of the issues with copy-constructors in C++, as I previously stated. Secondly, what has time got to do with this? It's a solution in C++'s RAII with copy-constructors. Not the only possible solution but the one that the C++ committee settled on.
> I read and write C++ every day at work and in my free time and I rarely find this to be a problem. If I see a non-trivial type, I assume it's destructor is called at the end of scope. And whether I have to look up the destructor of some type or a corresponding free function (like in C code usually), makes no difference to me.
I'm also assuming that your code base uses constructors and destructors all over the place and is absolutely fine with the added costs of constructors and destructors. And that's fine. But they are implicit and when reading the code, you don't necessarily know if a constructor is being called from just reading it. That is just a statement of fact and not really a criticism.
> Gamedev can live entirely without exceptions and many other software projects do as well. You just have to write your constructors so that they do little work (which is encouraged anyways) and use methods to initialize them. Having static methods that return an optional<T> is also pretty common.
Firstly, is the argument here to make constructors only do trivial things in?---(which rarely ever happens in practice, especially if you use anything from the STL). Secondly, many game devs usually have have an explicit `init` method too for the exact reason you can separate allocation and initialization, and have the ability to handle failure cases with `init`, usually with a return value indicating this failure state. You can have static methods, yes, but then you are literally getting around the construct of an implicit constructor and having an EXPLICIT construction call.
RAII itself is actually very simple, but to make it useful is complicated and complex. The issue with RAII is not necessarily the scope-exit semantics but rather coupling this within the type system itself as a way to have scope-hierarchical-based management of resources.
No one is expecting you to rewrite your entire project in Odin but those that are starting new projects have a high value choice with Odin and others.
Plenty of applications that require manual memory management have been written in C++ or Rust, which do provide RAII. If you know that manual memory management is required for some applications, you also know it's not 100% of the code that needs it.
As for RAII, I do explain in another comments (https://news.ycombinator.com/item?id=32629951 & https://news.ycombinator.com/item?id=32631462) why RAII has alternatives and not necessary for every language. If you truly believe that RAII is necessary for you, then use a language that has it (e.g. C++, Rust, D).
I agree with OP, that Odin doesn’t go far enough toward safety in this regard. We do all require and desire security.
Memory safety and memory management are not equivalent concepts. As I state in another reply (https://news.ycombinator.com/item?id=32629951), you have memory safety and manual memory management. And Odin offers numerous memory safety features as well as being designed around the power of custom allocators.
Can’t speak for OP but I also assumed that you were alluding to being able to use unsafe memory management in this part
> > And for you, you clearly don't need manual memory management nor high control over memory, memory layout, and memory access.
Since high control usually means being able to do whatever you want (compilers always have to be a bit conservative).
A language like that, as long as the object code is correct, then hey, it's there for people who want it.
I think Zig, which I understand better but don't use, has a good reason for not building in RAII. Zig has a "no allocation without an allocator" rule, and what's easiest to describe as native valgrind. It also has comptime, and RAII loses some conceptual simplicity when you start doing transformations between what-you-see and what-you-get.
I think it's worth continuing to explore whether that's a sufficient approach to memory safety, rather than doing something which has already been done, prematurely. I happen to think it's a promising approach, favoring clarity and simplicity.
We'll see. Zig can always add RAII later, and so can Odin. But at most once each, so it pays not to be hasty.
Regarding RAII, I explain in another comment (https://news.ycombinator.com/item?id=32629951) the issues with RAII and what it requires to be useful. Odin has the similar rule of "no allocation without an allocator" but has the implicit `context` system which passes the current context's allocators around. Odin also has core library support for valgrind, callgrind, memcheck, and soon helgrind.
Odin has features (which this article does showcase) which are akin to RAII but are not attached to the type system. And as I explained in the previous comment, RAII wouldn't even make much sense in Odin (or Zig for that matter) and would probably never a good idea to add it later. There are better alternatives to RAII for languages such as Odin and Zig.
RAII isn't something I happen to be looking for in a language, it kind of falls between Rust and your work in a way that I don't have a use for.
It's an interesting domain, isn't it? You'll always have people reacting from the hip to the problems C has given us, but they'll be back a couple hours later to talk about the wonders of SQLite.
SQLite got there by being careful and building tools to keep them out of trouble. I see an important role for languages which build those tools in, but don't try to replace a manual transmission with an automatic.
And it is indeed an interesting domain. Something to be very careful before making rash opinions on too. It is such an unexplored field so much potential!
How do you render billions of vertices and maintain a gameplay loop under 8ms for AAA games without precise manual memory/layout management?
Not everyone is writing websites in javascript
https://people.inf.ethz.ch/wirth/ProjectOberon/Sources/Kerne...
For something more modern in D, also a GC enabled systems programming language,
So i don't know what point you are trying to make
D is pragmatic and confirms my point
Modula-2+, Modula-3, Nim, Mesa/Cedar, .NET Native, Sing#, Oberon, Oberon-2, Oberon-07, Active Oberon, Nim, Swift (RC is a GC algorithm per CS definition), and even if debatable what its role might be, Go.
The point I am making is having a GC managed heap doesn't preclude features for manual memory management, only GC haters think otherwise.
Some helpful reading, from people actually working on solving hard problems
https://twitter.com/FilmicWorlds/status/1562090212225716224
> only GC haters think otherwise.
only GC lovers think like that
that's not a good argument
You still don't get GC enabled system programming languages, and apparently I am not the one that is going to make it clear, so whatever.
Odin is 100x better environment to program in than C. C++ is used for AAA games. Rust is not really used if hardly at all because of the highly competitive and time critical nature and very much the need to change things fast.
Also the article itself demonstrates different approaches to achieve behaviour similar to RAII through the `defer` statement and `deferred_*` attributes.
It seems like a small bike-shed level comment, but when I consider the code I have left in me, I'd like it to look as nice as possible.
Obvious example is CSS.
a-variable; a-1, a-(expr()), 1-a-variable (don't do this)
I think the last one illustrates why this will never be popular.I wonder how Go got it's reputation as a systems language. Imo, it occupies the same abstraction level as Java or C#.
https://www.youtube.com/watch?v=rKnDgT73v8s
I would not say it's the same as Java or C#. The crucial difference is that it compiles to a native executable binary file, not something that needs a virtual machine.
I tried to find the video on youtube, but to no avail.
"C Is Not a Low-level Language" (https://queue.acm.org/detail.cfm?id=3212479)
While I feel that article is being too harsh on C, it would be interesting to have cache intrinsics just like we have SIMD intrinsics. I would imagine that would be more complex to implement, however.
The more complicated answer is that, traditionally, systems programming languages were those which are not scripting or assembler languages. If you listen to Pike for more than a few seconds you'll soon notice that he's a bit of a language purist, so whatever modern interpretation you might have for a term is unlikely to match what he is communicating.
More specifically, it was announced as a systems programming language designed for things like web servers and systems of that nature, with more control than Java in some areas. Even with conflicting definitions of systems, there should have been no illusions about it being designed to be in roughly the same space as Java with that context. It was very much suggested from the onset that it was meant to compete with Java.
But, ultimately the game of telephone truncated the context that would have helped with finding the right definition of systems and, I expect exacerbate things, there was Rust coming onto the scene juicing the situation with its fans often claiming that "Go isn't a systems language, Rust is!" leaving some revisionism about people believing that Go was a systems language (in the modern sense).
Unless one doesn't consider writing compilers, linkers, GPU debuggers, container management, syscall emulators, unikernels systems programming.
Since you can write the above with any language, perhaps even Python or Lua if you wanted to, it's not exactly the ability to write the above or the fact of having written the above in a language, that makes a language to be considered a "systems programming language".
In some way, what is called a systems languge it's not a technical capacity thing ("can do X, Y and Z, so it's a systems language").
It's a term applied deliberately, that also includes other aspects.
By "enough detail" and "clear bounds" i mean: a group of people independently arriving at the same sets of classification based on your provided definitions.
There's no such thing - that was the whole point of my comment.
A systems language is not such because of conforming to a definition, it's a delibarate classification ("this language is, that one isn't" as opposed to "this langauge is because it conforms to this definition"). And that "is/isn't" isn't even up to the individual programmer, it's cultural.
There are characteristics that drive this classification, but it's not driven by a strict definition. It's more of "know it when I see it" kind of affair.
Doubly so if what we're concerned about is labelling itself, like "what is and what isn't considered a systems language". Then we're in the word domain, not in the measurement domain.
In fact their holy C can only be used for the daily activities, when going outside the ISO C Bible, tainting itself with unholy compiler extensions.
It is kinda confusing that the term is used for that and for low-level embedded/firmware/kernel programming, despite both areas having little in common with each other.
Is it, though? My impression it only started with Go calling it that way
It's not meant to mean operating systems language, nor embedded systems language.
Rather for writing parts of a systems, such as servers. I would say that the definition is not that far away for Java or C#, but the expectation is simply that it would be "lower level" components of a system, including unix like utilities.
Now it means building higher-level userland tools and internet oriented tools. I think the implicit consensus is the lower level stuff is a "solved problem".
Not powerful enough to be compared to Java.
Probably closer to Node.js, that's to say, it is like JavaScript in the server. Judging by the developers who use it. Except it is compiled.
https://books.google.com/books/about/Votan.html?id=y-OlAwAAQ...
"""
In the second century AD, a Greek nobleman is travelling and living abroad in Germany while carrying on an affair with a military man's wife. When discovered, he takes an emergency business trip to save his life and packs amongst his belongings certain items that lead the people he encounters to think him a Norse God, a fortuitous point of view which he does little to dispel. Forced to keep up the pretence of being a god while staying one step ahead of his lover's jealous husband, Photinus must juggle the severity of his situation with the enjoyment of being a God.
"""
He wrote a couple of sequels that go on to Irish mythos, etc.
I am currently writing a rigid body engine in it. It is perfect for graphics/games programming.
It will always be more difficult to read (and understand) a program than to write it, so, unless this is some throw away code, ease of reading should be the priority.
Unfortunately, the speed/ease of writing is often used instead.
Ease of reading means linear, boring, plain English with minimal use of arcane symbols or regex-like expressions.
In this regard, meta-programming is very often counter-productive, as it can introduce new idioms and constructs in the language that have to be understood before reading the program itself.
Overuse of C macros or C++ templates comes to mind, but I think that lack of readability was also a huge problem for LISP and Forth.
I don't do C professionally and really only used for standard uni classes for algos, data structures, system programming, OS, etc. But seeing Bill's streams highlighting the clarity of the Pascal pointer syntax has been eye opening. A C spiral seems so utterly unnecessary compared to how simple it is to read Odin left-to-right. It's good to have a physicist take on a programming language. Thanks for the hard work!
As I grow older, I find myself wanting to use languages that are more conservative in their designs. Back in my 20s, I was all about expressivity and meta-programming: Scheme, Smalltalk, OCaml, and Haskell were my drugs of choice. I looked down upon those simple peons who used pedestrian languages like Java or PHP and I definitely looked down on those neckbeards who hadn't left their C caverns and didn't know the greatness of closures, higher-kinded types, hygienic macros and all those features that real languages had.
That's how I came to learn about Rust: in the early 2010s, there was a post on Reddit about a new programming language implemented in OCaml. Even before Rust 0.1 was released I was a Rust enthusiast. And what a ride it's been! Rust transformed and evolved a lot since its first days, but what a language it ended up becoming! In a just a few years, Rust went from being a hobbyist toy to an industrial-strength tool used by large projects and corporations like Mozilla, Dropbox, Discord, Amazon, Facebook, and more. I've been using it professionally myself for the past 5 years and it's been (mostly) a great experience.
But as Rust and I continue to change, it seems that the paths of our lives are diverging. As I gather more gray hair, I don't have the energy, the time, or even the passion for learning and master exotic language features anymore. I now avoid a lot of the "cool toys" that I was so enamoured with in my 20s. My programming style is now much closer to what it was when I first learned to program in Turbo Pascal in the mid-90s: mostly functions, arrays, and structs. Rust on the other hand continues its quest to be an industrial language that has ivory tower creds: the community and the core team enthusiastically look forward to having more ways to abstract code, more ways to express constraints at the language level. I feel that before long, my values and Rust's will have grown so far apart that we'll have no choice by to break up our long relationship.
So I've been looking for what else I could use for personal projects. (I think and hope that I continue to use Rust for my professional career.) Odin is at the moment my clear favorite, with Zig slightly behind, and Nim after that. I find that Odin's design represents my own values about what software and a programming language ought to be like better than anything else out there at the moment. It's high-level, has out-of-the-box support for dynamic arrays and dictionaries (I think, the two most useful data structures), and the whole language is simple enough that I learned most of it in a weekend. It does feel like a step back from Rust in certain areas -- null pointers, using product types for returning errors, no ownership checking -- but I think that I'm at a point where I'm ready to forgive and deal with these issues in order to have a tool that is simpler and doesn't evolve at the speed of 20 year olds.
What about Hare? It's meant to be a modern boring (read simple and stable) language.
But I am not sure what I would prefer in some cases when looking at these new languages.
For example doing some alloc/pointer stuff in Odin is like:
ptr := new(int)
ptr^ = 123
x: int = ptr^
free(ptr)
And in Hare: let ptr: *int = alloc(123);
let x: int = *ptr;
free(ptr);
Which approach do you prefer, and why?From Dennis Richie:
> Declarations in C must be read in an `inside-out' style that many find difficult to grasp [Anderson 80]. Sethi [Sethi 81] observed that many of the nested declarations and expressions would become simpler if the indirection operator had been taken as a postfix operator instead of prefix, but by then it was too late to change.
Apart from that choice of syntax, both examples seen equivalent.
At the calling scope, the parameter passed is not immutable. You can pass a pointer too, if you want.
There's also this pattern:
name := name
which redeclares name in the current scope and shadows the name that exists in the outer scope call :: proc(b: int) {
fmt.println(b)
// c := &b // illegal!
// b = 10 // illegal!
b := b
b = 10;
fmt.println(b)
}I've been programming in C or C++ since 1993 and I can count on one of my hand the number of time I've used unsigned wrapping voluntary (for clock management)..
One is dealing with foreign code. A lot of old C code used their own boolean type before it was standardized. And many people defaulted to typedeffing `int`. A good example of this is Win32's `BOOL` which is `int` sized, which would be backed by `b32` in Odin.
Another reason is that file formats may use different width booleans to a single byte.
Another reason is that things that are closer to the register-width are faster than byte-wide operations. So `b32` or `b64` even though it takes more memory up can be faster to deal with than `bool`/`b8`.
As for `bit_set`s in Odin, they are backed by integers and a brilliant solution to the problem of flags. They usually become a lot of people's favourite (but small) feature because of their ease of use and clarify of what they express.
Another language which is in both the C and Go alternative language category, is Vlang (https://github.com/vlang/v/blob/master/doc/docs.md). For anybody that has used Go, these are definitely languages to check out and would be easier to learn.
Today many features have diverged since then, and Odin is ahead by many aspects (mainly that the compiler is open source, but also that it’s already being used in production via EmberGen). I still really want the full metaprogramming capabilities that Jai has though (which is lacking in Odin currently).
gingerBill (the creator/designer of Odin) has been quite clear that Odin is done (in terms of language features) and that he does not intend to ever include support for "macros" or other advanced metaprogramming features.
Also, Jai is a lot more Go-like than many people realize. When Jai diverges from various C/C++ concepts and traditions, it can do so in Go-like ways.
> ...Odin is ahead by many aspects (mainly that the compiler is open source, but also that it’s already being used in production...
Totally agree with you here. It could be argued that Jai has fumbled its advantage or at least the window of opportunity to claim its more innovative or more game program friendly than other languages. Odin has pretty much covered that gap, so that most of what people liked about Jai, is in Odin and can be used today.
You can’t clone code from YouTube videos (practically speaking).
My understanding is that Odin does not plan to ever attempt to put concurrency nor channels into the language. It looks like that some type of workaround, if it ever happens, will have to be implemented by a 3rd party library.
It should be added, Vlang (the other Go alternative mentioned) because it was designed for various memory management options (both automatic and manual), does do concurrency and channels (https://github.com/vlang/v/blob/master/doc/docs.md#concurren...). Vlang aims to be more general purpose versus more specific.
https://github.com/odin-lang/Odin/tree/master/core/sync https://github.com/odin-lang/Odin/tree/master/core/thread
We are planning on adding channels to the core library but we have not added them yet since there are quite a few different forms (SPSC, SPMC, MPSC, MPMC).
One thing that I would like to bring up is that Odin differs from Go in that it does not have `go`routines (green threads). These require automatic memory management and a huge runtime to make them possible. And you cannot easily add them after the fact to a language either without huge issues. Channels are very basic things but the thing that makes Go's concurrency powerful are the `go`routines, not the channels.
Fortunately there's a FAQ, and from that there are two Rob Pike languages (Newsqueak and Go) as well as a pair of Wirth languages (Pascal, Oberon-2).
I suppose your argument is about Odin, being compared to Jai, but its very obvious they are in near categories. Pretty much whatever a person would have used Jai for, they can use Odin. Maybe you want to also watch the YouTube link of Jai versus Odin (https://youtu.be/M763xHjsPk4).
And, it might hurt some feelings, but I'm very understanding of people that rather use Odin than to wait around for Jai.
But the warts just aren't big enough to justify switching to a different language. That's why no "better C than C" has ever become really popular.
Many people can try. It's not easy to do a good job at it.
I can't see this being generally successful unless it can interoperate with existing C code without much effort. Update: It appears it can: https://odin-lang.org/docs/overview/#foreign-system
It apparently targets LLVM.
https://github.com/floooh/sokol-odin/blob/main/sokol/gfx/gfx...
The only minor downside (compared to Zig) is that Odin still requires a separate C/C++ toolchain to actually build the C dependencies. But I guess that's a typical 1st-world-problem ;)
(however AFAIK Odin's FFI system isn't in any way related or depending on LLVM).
Sadly, Odin doesn't seem to pass the test. It's full of commits labeled with just "fix <thing>": https://github.com/odin-lang/Odin/commits/master
A few examples of projects with better commit messages:
Linux kernel: https://github.com/torvalds/linux/commits/master
gcc: https://github.com/gcc-mirror/gcc/commits/master
Perl: https://github.com/Perl/perl5/commits/blead
Wayland: https://github.com/wayland-project/wayland/commits/main
Many of the things are "Fix #NNN` which means fixing a specific GitHub issue which usually has more information to it or has comments in the code, etc. There are many "Fix typo(s)" commits because I (and others) make a lot of typos; these are usually trivial/single-line fixes. Then it comes to the rest of "Fix ..." commits which are pretty much 1-2 line fixes of minor bugs; with large bugs having multi-line commit messages and many with huge comments within the code to explain everything.
Your metric of the quality of commit messages is not a bad one depending on the codebase, but it cannot be used blindly without knowing how that codebase operates. Especially comparing a mostly centralized codebases (like Odin) to very GNU-style decentralized codebases (all of the examples you gave).
A commit message has a function which is directly applicable to the craft of programming and not something auxiliary like the aesthetic properties of a shed used by the bicycle commuters. Though in this case I would disagree with the GP since “Fix <thing>” might be a good enough commit message template.
As the project grows in contributors, the commit log gains value
According to GitHub, there are 132 contributors. Probably most of them were one-off drive-by PRs, but nonetheless, it's definitely not a one person project anymore.
Take a look at the commit histories of the other projects I've linked. Their commit messages are much more elaborate.
<thing>
But i'm interested to see how the project will continue. :)
Odin has numerous memory safety features enabled by default. Having manual memory management does not entail no "memory safety". You could easily have a language with GC or ARC and still have all of the unsafe features of C. Objective-C is a brilliant example of this.
Many memory safety can be achieved with many constructs such as:
* Bounds checking for array-like types
* Having no pointer arithmetic
* Having distinct typing (virtually no implicit type conversions)
* Having no array-to-pointer demotion like in C (combination of the above to problems)
* Having actually decent array types: fixed-length arrays, slices, dynamic arrays, #soa arrays, etc
* Have length-bound strings rather NUL-terminated strings
* `Maybe(^T)` type to allow for non-nil pointers to be explicitly checked
* Built-in discriminated `union`s (of which `Maybe` is just one of those)
* Virtual Memory Protects
* And many more!
Most of the unsafe features in C/C++ come from most of the above features, especially pointer-semantics and the lack of a decent array type.
What Odin does not offer is ownership semantics and lifetime semantics, which is an entire discuss to itself which I won't talk in this comment.
So these languages stay memory unsafe but tries to minimise the issues caused by the lack of safety.
The key would be hardware supported automatic memory management.
This tech exists since many years but nobody uses it (likely because our "good old friend": patents, which is a death sentence to any good idea).
https://researcher.watson.ibm.com/researcher/files/us-bacon/...
https://people.eecs.berkeley.edu/~kubitron/papers/holistic_r...
https://adept.eecs.berkeley.edu/wp-content/uploads/2018/06/A...
Many such cases.
> Odin is not currently self hosted nor will be until after version 1.0 when the main implementation of the Odin compiler adheres to a specification and is heavily tested. In general, self hosting before a stable language and compiler exists is masturbatory pleasure.
To quote the FAQ (https://odin-lang.org/docs/faq/#is-the-odin-compiler-self-ho...):
> Odin is not currently self hosted nor will be until after version 1.0 when the main implementation of the Odin compiler adheres to a specification and is heavily tested. In general, self hosting before a stable language and compiler exists is masturbatory pleasure.
Here's an alternative link (substack):
Probably a harder learning curve than Odin or Go but more likely to work and also run faster (than Go at least). It also has zero cost functional programming paradigms.