"Bring your own datastructures" is likely one of the C carry-overs for Hare. If you think about many "classic" C applications, a lot of them basically only exist around/for one or a few data structures (e.g. most servers, many utilities etc.). In that context BYODS is somewhat defensible: If you don't care to implement the data structure, why does your application exist in the first place?
But that's not "modern" applications, which often deal with a ton of data structures. C applications that do so usually have a few "template headers" lying around (indeed, certain operating systems ship with them), though one of the most frequently used data structures in C is of course the intrusive linked list. Which is not a good data structure to use in most cases. Why is it used so much in C? Because it is easiest to implement. Why does that matter? Because C makes implementing generic data structures tedious. Is it a good idea to replicate that model?
Rust and Go seemed to initially want to target C programmers [1], but ended up capturing users from higher level languages – even Python and such – who wanted better performance and more control, because they had so many affordances.
Hare clearly isn't like that: it's _actually_ aimed at (FOSS) C programmers, almost stubbornly so, and isn't going to appeal to many others. But for those people I can see C-with-tagged-unions being an improvement, and there's value in finding the minimal diff that makes a language better.
[1] Remember when Go called itself a "systems programming language"? Neither community seems to use this phrase any more, though.
Rust still considers itself a “systems programming language” . They are moving Rust into the Linux kernel and android drivers. It doesn’t get any more “systems programming” then that. But you are correct about go, it doesn’t give the programmer enough control to be used at that level.
> But you are correct about go, it doesn’t give the programmer enough control to be used at that level.
What I like about go is that I can go full unsafe.Pointer if i want to, and do whatever I want.
The other thing I really like, is that they did such a good job discouraging it that i frequently hear people complain about go having pointers but not even letting you have fun with them.
The problem isn't that go doesn't give you enough control, the problem is that it's garbage collected. And you'd need to work around the GC. It's doable and people have done it though. Good idea? Maybe not.
Still waiting for someone to make "go but with ownership and borrow checker"
I think that being garbage collected is exactly the control problem in question.
I'm no a systems programmer unless you count hobby microcontroller projects, but my understanding is that, when a systems programmer is talking about control, they tend to be talking first and foremost about keeping tight limits on memory usage. Pointers and suchlike just happen to be key tools that help you do that.
> Still waiting for someone to make "go but with ownership and borrow checker"
That would have interested me more a few years ago, but I'm deeply invested in Rust these days.
Is that all you'd change about Go? One of the reasons I moved was the error handling and lack of generics. Go has fixed the latter, but I can't stand not having Result<> and Option<> these days. The '?' is awesome too.
You can't even call a non-Go library without switching to cgo, which contradictorily basically everyone says not to use.
Leaving aside the GC, until Go has a real FFI it's hard to to imagine it being a really great fit for systems programming when the systems are all not written in Go.
Go is not a traditional systems programming language and calling it one is misleading at best and maybe even borderline dishonest. Two big problems are the lack of control of memory layout and the lack of some form that compiles down to a computed goto. It’s just not possible to write performant systems code without those.
I’m not bashing Go though. I appreciate the focus on readability among other things. It’s a fine language for the use cases it was actually designed for, like pushing petabytes of ad data to serving clusters all around the world with acceptable latency and reliability.
In my prior comments above and in all the following I use "language" in the typical way, which is to say referring to not just the syntax but the semantics of the standard toolchain and runtime as well. I wanted to clarify that since perhaps there is some confusion there. So when I talk about Go I'm talking about what I get here[1] as is everyone else who isn't explicitly specifying some other implementation.
Writing compilers is not systems programming in the sense that it requires a systems programming language, no. One could easily write a C compiler in Ruby, but we don't consider Ruby to be a systems programming language.
Thus, obviously, Go, despite not being a systems programming language, could be used to write the compiler for a systems programming language. I guess that is what Tamago is? I'm not going to read through the source to find out and the web page you linked is boring marketing copy.
Easy, K&R C was rather limited, inline Assembly wasn't yet a common extension, rather you would use the external assembler, and link both object files together.
What was good for C while it was gaining adoption, surely is good enough for Go.
Wouldn’t this be just a lesser version of rust then? If someone makes a new language and wants it to catch on, it needs new ideas. Not just nice simple syntax mixed with something someone else is already doing.
Hilarious that you think that you could just add a borrowing mechanism + ownership and still be in the so-called less is more camp. That language would be looked down upon by the so-called less is more folks because of the “straightjacket” it would force on the programmer.
I have no fists in this fight, but wanted some clarification.
Are you saying the reason Rust is now being integrated into the Kernel instead of Golang is because of the success of evangelists, not because of the merit of the language itself?
I'm not involved in Kernel development myself, but if I was, I'd see your statement as a big hit in the face that we don't know what we're doing, if they (Kernel developers) are being sold to use Rust because of marketing, not because of the value of the language.
I must admit, I am still surprised to see Rust gaining acceptance when the reasons Linus gave in 2010 for rejecting C++ in the kernel appear to apply to Rust equally well. I can't think of any of the pragmatic points which he raised which doesn't apply to Rust.
For example, the arguments against namespaces and function overloading, high context dependency, easy of understanding and in favour of C's simplicity in general.
My guess (it's just a guess) is that his position has shifted over time to consider a higher-abstraction, higher-complexity language, as development methods and the complexity and professionalising of the kernel have changed since then.
The main push for and patches to include rust in linux have come from an established and longtime kernel contributor. What you’re saying is categorically false.
For someone to start using a new language in the Kernel, some of the existing "guard" has to approve it, meaning they've investigated if it's fit or not. Or can anyone willy-nilly contribute to the Kernel without anyone approving changes going into the main branch?
The existing guard doesnt have to prove anything, theyre on the way out. C is a tough language and not a lot of people have the patience for it, and even less people are being forced to learn it like the rest of us in school. As this segment of the community ages out, we arent going to see more code written in C, so we have the new group with Rust, recreating all of the same issues in effigy of C, but with better string handling. It's not like it is terrible code, but as Nikola Tesla used to say "it is what it is, and it aint what it aint".
You see how parties completely unrelated to the fight got Linus ousted? That was not based on technical merit, it was due to the changing tide regarding outcomes when people are offended/mistreated. Rust has significant overlap with the community that gave way to this, don't think it is 100% open arms and welcomes, because that is not the case.
Damn right I don't have the patience for C. Writing anything in C properly takes a long time, and even then bugs are usually found after.
Compare and contrast Rust, where the code I write will usually just work. I don't need to be extra careful with it because the compiler is strict. You can say that this is indicative of some flaw in my character or whatever, but, uh, I don't care.
My understanding is that Go has several features that make it unsuitable as a Linux kernel language, e.g. automatic garbage collection, its concurrency model, and possibly the nature of its runtime dependency (which is related to the previous two items.)
A best feature it may well be, but it requires infrastructure and the OS kernel provides that infrastructure it doesn't depend on it.
Rust for Linux relies on the fact that Rust is deliberately structured in layers so that the bottom layer doesn't need that infrastructure, this was needed to make embedded Rust practical, but it's also important for Linux. Actually, Linux needs even more than Rust had initially, but that's driven further improvements to Rust itself.
You could add a layer to do all this lifting (that's what e.g. a Java OS does) but that's not going to fly in Linux, which is one reason why Go for Linux isn't a thing whereas Rust for Linux is.
> there's value in finding the minimal diff that makes a language better.
OTOH, if the differences are minimal, then the benefits are likely to be correspondingly minimal. And either way, the compiler/interpreter for a relatively new language will by definition be much less battle-tested.
Practically speaking, I think if a new language is going to compete with C, it needs to offer more than a few incremental improvements.
Mostly because writing compilers, linkers, GC implementations, GPU debuggers, OS emulation layers, distributed orchestration systems, unikernels, isn't considered systems programming by the crowd that is allowed to judge it.
"Because C makes implementing generic data structures tedious. Is it a good idea to replicate that model?"
A lot of so called "modern" languages make a choice for you. Here is a hash map, over there is a class, have a tuple, set, etc. And they all have a specific way to be used.
In implementing all that, many languages lose on flexibility while gaining general utility. Hare seems to go in the opposite direction, one that makes programmers think hard about the solutions to their problems.
I think the main thing is that e.g. python allows you to reimplement all of its most basic data structures in python if you so desire. There's no real way to reimplement e.g. an array in C. The result being that python could still be useful if it wouldn't come with a bunch of data structures built-in.
BYOD combined with "we don't need a package manager" sounds like a particularly bad combination. I understand Rust's "small stdlib but great package ecosystem", but whats the appeal of "anemic stdlib plus no package ecosystem"?
Maybe I would come up with something like this if I only ever worked on Linux software using C, but outside those niches these choices seem weird.
Correct me if I'm wrong, but Hare doesn't say "we need no package manager" but instead "we don't need a new package manager, use the one your OS provides", at least from how I understand it.
That strikes me as another C-ism that didn't deserve to be carried forward into a 21st century language.
There's a reason we call it "dependency hell" and not "happy fun fussing with dependency version conflicts among projects that are completely unrelated except that you happen to be using the same computer to work on them time."
The state of the art in package management could be considered Nix and Guix and those are both operating system level package managers and thats why they are better than npm or pypi or cargo, as the aforementioned language specific package managers _dont_ handle dependency hell as native dependencies exist. they all have the same problem as 'they work on my computer' and also encourage this ecosystem of thousands of micropackages, which in a thread so focused on security seems a little ironic given there is no way to guarantee all of those dependencies are made by good actors without a lot of vetting that just is not happening.
I've never had a "works on my machine" bug when dependencies are managed per project. The only time I have ever had them is with global/os-level dependencies. And I've used cargo/npm a lot in the last few years.
What I keep daydreaming of is a package manager that resolves and download packages, but doesn't automatically grab transitive dependencies. I don't even want it saying, "Hey, we need to grab these 15 other ones, too, is that OK?" I don't want to hide the pain of huge dependency graphs like that; I want to feel it acutely. Give me an error message saying, "Oops I couldn't add FancyPackage because it depends on X, Y and Z transitive dependencies," and send me on a fetch quest. And I want the whole community around the language I'm working in to feel it acutely like that. That way we're all in the same boat, and collectively incentivized not to create the problem in the first place.
That is on the library developers to settle out. Keeping your library API stable requires the mind of a genious and discipline of a monk.
Now, keeping your language design stable and backward-compatible is a no brainer. Given that, it's possible to handle "dependency hells" simply by building from source.
Hare is designed to be ultra conservative, so you don't have to worry about dependency hells. Features are in code, not builtin into the language.
If I'm writing for a proprietary OS, that's probably Windows or OS X. Both of which give me a more stable target to aim at when it comes to what dependencies I can expect.
If I'm writing for an open source OS, that's probably some flavor of Linux. But which flavor? And which package manager does it use? And do they support all the dependencies I need, in the versions I need? Without pulling non-standard package repositories into the mix?
When open source software has difficulty working on proprietary OSes, I think it's usually not because of the package manager. More often it's because of something like a glibc dependency. At which point the domain of support isn't really "open source OSes", it's usually something more like "glibc-based linux distributions."
Every programmer should not be re-solving the problem of library distribution just to distribute their library. This is a solved problem; a go-style git repository model is the bare minimum that a language should have.
Having to download several dependencies from random websiees and install them is not the most fun way to spend an afternoon. Especially if half of them have cryptic build errors.
Package dependency resolution is fundamentally impossible to do efficiently and often broken (for legacy reasons) in specific implementations. Programmers are terrible at semantic versioning. Community package repositories are security nightmares. Under those conditions not including a package manager seems like a reasonable decision.
Many Hare programs will not need to have dependencies at all. We encourage a more conservative approach to dependencies than is common in many modern languages such as Cargo, PyPI, npm, Go, etc. Even dependency-heavy Hare projects will not have hundreds of dependencies, but maybe dozens at most. Think more C, not Node.
That does look like a decent collection of functionality, but a HashMap as mentioned in the submission seems like a pretty glaring omission. I can't remember the last time I wrote a program that didn't use a Map. Even dynamic languages that tend to be quite light on types almost universally include a map type.
I can definitely see the problems with the Node "explosion of modules" approach, but if we're looking at fixing issues with C then IMO "everyone implementing their own data structures and not always with the care and attention they deserve" is right up there with things like lack of proper arrays/strings and poor null handling.
It seems like it ought to be possible to find a middle ground where dependencies are easy to find, install, update and integrate with a standardised build system, but where there is a cultural norm of being conscious of dependency size and not using micro-dependencies
Most application's bottlenecks are not their linked lists, and optimizing for anything other than your bottleneck is not the wisest use of your time. Performance is a budget, and I feel comfortable spending some of that budget on simplicity.
Linked list has a O(N) search time, which is atrocious.
A really common optimization is to replace a linear scan on a list with a hash table. In most languages, that is a trivial step to do, which means that it gets done.
There isn't any overhead.
With hare, you just blew your complexity budget on this thing.
It's not atrocious unless you need the performance. A linked list with a hundred items which is scanned every 90 seconds or something does not really demand attention for optimization, but it will be simpler, easier to write, easier to understand, and easier to debug, and those are wins are not to be sniffed at.
And for the record, Hare does have built-in growable slices, so linked lists are pretty rare in Hare. They exist in a few niche situations -- I only ever wrote a Hare program with linked lists once.
For sure there's plenty of circumstances where you don't need more performance, but you can't seriously argue re-implementing another linked list is simpler, easier to write, easier to understand or easier to debug than `std::vector`/`Vec`. Multiple allocations and setting up pointers is certainly more complex than allocation/copy. It's most definitely easier to write because you're not writing it, someone else already has. It's easier to read because there's a whole lot less of it. And a single contiguous allocation is way easier to debug.
My impression, although it was difficult to be sure, is that Hare's "slices" (or maybe arrays?) are actually vectors, in at least some cases.
It seems you can grow them by appending, so they aren't like Rust's slice which is non-owning or a typical array which can't grow.
I suspect a Linked List is actually almost never the right shape for a data structure in Hare, because you should just use these vectors to do that.
The two actual rationales for Linked List in the 21st century (other than "I was writing C and I don't know what I'm doing") are: 1. Concurrency, Lock Free and sometimes even Wait Free algorithms are practical for Linked List and the other costs pale into comparison on highly contended data structures and 2. I'm doing some serious acrobatics with huge lists, I constantly perform split/ merge operations and so those being cheap is crucial. Hare doesn't have concurrency, and nobody should attempt such acrobatics in a language this dangerous.
Yep. I used it in a kernel to create a linked list of statically allocated data structures in a context where dynamic allocation was off the table. You can see the code here:
but maps are simpler to use (if appropriately provided by the language).
maps/sets are also fundamentally building blocks for a lot of algorthms and structures, not having them makes it WAY harder to implement them. actually potential impossible as performance can easily degrade to a point where it's unusable when you use O(n) (or worse) lookups instead of O(1) ones.
this is not a case of premature optimizations but of missing fundamental building blocks
and if you don't want genetics, ok, do it like go did initially make the map special language thingy.
Linked lists have generally awful cache locality. If linked lists are not the bottleneck, it's because they already aren't being used for anything where performance matters (which is generally as it should be).
But that's not "modern" applications, which often deal with a ton of data structures. C applications that do so usually have a few "template headers" lying around (indeed, certain operating systems ship with them), though one of the most frequently used data structures in C is of course the intrusive linked list. Which is not a good data structure to use in most cases. Why is it used so much in C? Because it is easiest to implement. Why does that matter? Because C makes implementing generic data structures tedious. Is it a good idea to replicate that model?
qed.