thoughts on the hare programming language; march 2020 to now
published April 25, 2022
So a few years ago now (wow way longer than it feels like) I got a look at the fledgeling Hare programming language by Drew DeVault, which finally got released today! Here's the post I wrote for him giving some thoughts, opinions, and questions, plus some additional more recent thoughts.
I'm not actually too happy with the writing, but in the spirit of capturing what Hare was like at the time I've left the older posts unmodified other than bringing the formatting in-line with how my newer Gemlog posts are formatted. Be sure to check out the end for some thoughts from 2021 as well as thoughts from today when it was released—although note that it's not at the completed
1.0 release yet, it was just publicly announced.
initial review
written March 29, 2020
These are my thoughts on the Hare programming language, Drew DeVault's new systems programming language. He was nice enough to let me have a sneak peek
(so to speak) at the language to get my thoughts on it after seeing one of my posts, so I'm just going to narrate my thoughts as I go along, hopefully they'll be helpful to the Hare team now and to people looking into the language when it's released. A lot of stuff on here may be outdated by the time the language is released, since I'm writing this in March 2021 but publishing it in [Q2 2022?], but I'll try to note where those discrepancies are, and also include abbreviated thoughts on the latest version of the language near the bottom.
Also, lots of C comparisons (obviously), but also lots of Zig and Go comparisons because those are what's on my mind right now, and they're both great candidates for comparing and contrasting against Hare anyways.
initial impressions
Just glancing around the homepage, I'm really impressed. I love the Plan 9 aesthetic, and I will absolutely buy a floppy disk as soon as they're available. I am a bit disappointed in the supported architectures list (that screenshot was taken when i wrote this, the up-to-date list is here), I would love to program microcontrollers in a non-C language, and I also don't see anything about bare metal on there (although Drew says that bare metal does actually work, it's just not listed), but it is a very new language so I'm not that worried about future support, the base
architectures and OSes necessary for experimentation and development are all there, I'm sure wider support is coming in the future once the main development is finalized.
building & installing
This was the most simple compilation for a language anywhere, it took maybe three minutes to go from nothing to a bootstrap compiler to a self-hosting build driver. I am a little annoyed that there's no install target (I have to re-run `hare.sh` in every tmux window), but it's reasonable considering the language's current stage of development. Considering how new it is, the tooling itself seems to be very mature really (I'm sure that's mostly because of using the pre-existing QBE as the backend).
documentation
style guide
A real style guide is always good, and Hare has a very in-depth and clear style guide. All I have to say really, but it's really helpful for a newcomer to have a clear guide on how to actually write it idiomatically. I wonder if a `hare fmt` utility would be justified or not. Certainly don't make it a compiler error like Zig, but an auto-formatting tool would be interesting if redundant when used with an editor plugin.
I also really like the style of the language. This is obviously very subjective, but the style guide very roughly mirrors my personal style guide when writing C, which is very nice because I don't have to make many adjustments to how I write most of the choices in the style guide are what I would naturally do anyways.
specification
Wow, Hare has a real specification, it already wins over rust! It's a formal specification (in contrast, see Go's specification which is detailed but relatively informal). It's in what I call spec-ese
, which puts a slight damper on readability when actually learning a language, but it is necessary for when you're actually writing a toolchain for it so I don't think badly of it. I will admit that I just skimmed most of the spec though, so I can't give a detailed opinion on it. I wouldn't say it's required reading, or even expected reading (like the Go spec is), but it makes a great reference when you want to look into something specific.
standard library docs
The standard library tutorial is currently unwritten :(. It makes sense though, it seems like most of the current work is focused on the standard library, so no point writing a tutorial for it when it changes by the day. The code itself is very readable and well documented though, so it wasn't hard for me to understand what's going on despite the missing documentation.
tutorial
Pretty well written introduction, it doesn't drag for someone with previous experience, the early sections are pretty basic, but they more go into syntax and Hare-specific stuff rather than boring you with something like how do variables work?
I'm just going to roll the rest of this into the sections after this, it'd be redundant to talk more here.
language features
tagged unions
Tagged unions are what unions in C really should be, they're amazing. Unions are the most underused thing in C really, because they're rather needlessly complex (you almost always need a wrapper struct + enum) and underexplained in most courses and whatever, but I really love the idea. I like Hare's tagged unions even more than Zig's error unions, because they're far more flexible, you can return any number of types, not just one error set and one success type.
error handling
I love the error handling. A lot of languages seem to deal with it poorly, in particular I think Go is very very lacking on errors, and error returns are one of C's weakest and most hackyest points. I like Hare's error handling though, the use of unions to contain all errors for a package is reminicent of Zig's error handling, and the de-facto errstr standard gives something reminiscent of C's errno (but per-error rather than global), which is an interesting hybrid that I can get behind.
strings
Strings seem to be the weak points of most languages, and it seems like Hare is no exception to this rule. The big problem is that strings are impossible to make easy
without going behind the programmer's back on a lot of allocations, which is very at odds with a language that gives you fine-grained memory controls. The very best you can hope for is good standard library support, which looking through the source, Hare has. At lease Hare has a clear string type rather than just having an array/slice of bytes, where even if it does act roughly like an array of bytes anyways, it at least provides clarity in code that a lot of C/Zig/etc code misses.
functions (and more) as expressions
This is a very strong point of Hare, that you can use any expression, not just an expression list
(a.k.a. a “block” in C), which is very useful. If/case statements as expressions are also very helpful. In general Hare seems very functional (or just modern
at least) in that most everything is first-class and an expression.
standard library
The standard library is what most of the work seems to be focused on at the time of me writing this, so it's obviously incomplete right now. Despite that, it seems very well-rounded already, which is really what you want in a language with no runtime, a well-rounded, well-documented (very important point) standard library, which Hare seems to have even now.
misc
- I like being able to label loops to allow more finer-grained breaking/continuing.
- The distinction between namespaces (::) and struct accesses (.)
unclear or missing stuff
Stuff that's currently unclear in my head after skimming the spec very rapidly and running through what currently exists of the tutorial, alongside poking around in a few areas of the standard library that I'm curious about. A few of these may be resolved once the tutorial and the rest of the docs are finished, but a few are internals questions that would need additional documentation that isn't already TODOed. Also, see some accompanying answers from Drew.
- Is there a way to easily tell what's compile-time and what's runtime? How far will the compiler go to precompute as much as possible? (see Zig for an example of this being taken to the extreme)
nearly everything is runtime
- Do tagged unions get “locked” into a type and require a cast à la Zig, or do they implicitly change types when assigning à la C?
They implicitly change types like C.
- More detail on void types, and their general usage beyond symbolizing an empty return. When would you use a void type in a tagged union and when would you use a “nullable” type?
null is mostly useful for representing pointers which do not have a value, it's kind of subjective
they actually have ended up being used pretty rarely in hare
common use-case would be in code which tries to address OOM conditions
- How do iterators work? Similarly to C, or are there other, more idiomatic ways to iterate over objects? How would one make a data type iterable if there is a different way?
they are not a first-class language feature, we have some "iterators" in the stdlib but it's just a pattern
- How do varargs affect the stack? Is the number of args computed at compile time where it'd be boiled down to a hard stack frame or does it require allocation or something crazy like that?
vaargs are lowered to a stack-allocated slice
- More standard idioms (obviously this one's hard when there isn't even a community around to come up with idioms yet :P)
- 128 bit float type? Usually excessive but useful to have when you need them
nack, but we're considering 128 bit ints
conclusions
Hooo boy, this is up there as one of my favorite better C's
. There's a lot of things I love about Zig (other than the documentation and hubris of the language devs), but Hare is really good. The syntax is terse enough to be short and clear, but not so condensed that it obscures the underlying logic. All of the design choices seem to be sensible—even Zig has a few design choices that make me go WTF, in particular making bad
formatting an error. If you use hard tabs your code won't even compile, WTF!—and logical, and it feels like Go where I feel like I could pick up the basics in a day or so. What documentation exists is pretty well written (enough for me to get a feel for the language really quickly), and the standard lib source is very readable.
I would (and probably will) use Hare regularly when it's released, and it seems to fit into a niche where it could supplant a lot of my C (and Zig) usage and also supplant a lot of my Go usage. High level enough to have simple code for most things, but low level enough to let the programmer do what they want how they want to do it.
september 2021 revisit
written September 10–12, 2021
I got a hankering to take another look at Hare so here we are. It seems to have matured quite a bit now, although the stdlib and self-hosted compiler are still WIP it seems to be moving to completion at a swift rate. At any rate, here's some additional thoughts.
I wanted to write a real
program in Hare just to get a really good feel for it. I was going to write a Gemini client but the stdlib doesn't have TLS and there's no TLS bindings available yet, hopefully that'll be added somehow. Additionally I realized that although there's unix::tty
that's just stuff like isatty()
and there's no (portable) TUI interface either. I could just cheat and hard-code VT100 codes but I like portability. So instead I was going to try to reimplement ed(1) but the stdlib also doesn't have a regex engine and I'm not clever enough to do that myself.
I finally decided that I'll try to rewrite a budding project of mine to manage ROMs for emulators and flash carts. Hare has a variety of hashing algorithms and an XML parser which means I can parse and verify against the No-Intro and Redump databases, and in general it seems pretty ripe for it. The only thing that I'm disappointed about is that I probably won't be able to distribute the source code until next year. Presumably I'll update this with how it's going later on.
improved documentation
There's now a fancy documentation generator for all Hare code, and there's online stdlib docs:
The only criticism I have is that there's no links to the stdlib source code, that's always very convenient to have.
I do love how you can get library and general code documentation (including stdlib documentation) locally in pretty much any format you could reasonably want:
- HTML
- Gemtext
- Hare (a custom markup language with simple inter-code hyperlinks)
- TTY (a.k.a. it's pretty-printed with colors and such in your terminal)
The language tutorial is now seemingly complete, but all of the other tutorials like the stdlib tutorial are still unwritten. As I mentioned before the stdlib is still WIP so it makes since that the tutorial wouldn't be written until it's done since it'd probably have to be rewritten anyways. While a guided tour through the interesting parts would be nice, with the new stdlib docs I don't feel much need for the tutorial anyways.
miscellanea
Somehow this escaped me in my first look at it, but I missed variable shadowing
within blocks. So you can do:
let x = 5; fmt::printfln("x = {}", x)!; // => 5 let x = 10; fmt::printfln("x = {}", x)!; // => 10
A very minor point but a very convenient one as well.
Now that I'm exploring Ada more I'm appreciating Hare's very strong typing. Even C's type system is far too weak for my taste, but Hare seems to be in a good spot (although not as strong nor flexible as Ada's type system). While there's automatic
variables, when stuff has types already there's not a lot of implicit conversions. The best part is that type declarations are separate types rather than aliases though, sooo many mistakes because C's typedefs are dumb. They're still called type aliases
but each alias is a distinct type rather than being implicitly castable with all other aliases of the same type (although the usual implicit numeric casts still seem to work).
I also missed that defer follows all scopes rather than just functions' scope. Another nice feature, you can restrain an allocation's lifetime by just using a block rather than needlessly delaying cleanup until the end of a function like in Go.
current criticisms and suggestions
- Standard Library still lacking
The standard library that is already written is really well done, but there's a lot of areas that are totally missing. Main missing areas currently are (as previously mentioned) TLS, TUI, and regex. As far as I can tell the current development on the standard library seems to mostly be focused on getting the subset required to write the self-hosting compiler so I would assume that the missing parts (IMO) will be implemented later.
- Toolchain still a bit sucky
This is again related to it being in development, but you still have to manually source the bootstrap compiler and the build driver in every single terminal window. It looks like the bootstrap compiler has an install target now so I'm using that—but there's no uninstall target :(. I will admit I did hack an install and uninstall target into the build driver because otherwise it's a PITA to get a makefile to work with it.
Errors are also extremely verbose. I forgot a comma in the middle of an array literal and I got probably 10 lines of errors without any indication like expected token ','
. It seems like the main cause of the cascade of errors is because the parser attempts to continue parsing which results in a lot of nonsense unrelated to the root cause being spewed out. While I'm sure there's some fancy heuristic that could be used to filter erroneous errors, the simplest solution is probably to just make errors fatal (within a single file's compilation) rather than trying to continue.
thoughts on public release
written April 25, 2022
Some miscellanea since last time I looked at it:
- The toolchain installs like a normal program now
- The verbose errors have been largely fixed, now they're normal and actually useful
- The library is a lot more full-featured. There's still a few major things missing but that's expected, the language still isn't completed yet.
- I like how the standard library isn't given much special treatment compared to other libraries. You can shadow stdlib modules, and add your own modules into the categories in the stdlib (i.e. you could put your Gemtext parser in format::gemtext)
I'm starting a real
project so I'll probably have some more in-depth thoughts. Expecting some interesting little experiments.
Here's a simple function of my default slightly more complex than a hello, world!
function, calculating a square root using the babylonian method:
// Calculate a square root of a given radicand using the babylonian method. // The initial_guess is the starting point of the calculation, the closer it is // to the true square root the faster the calculation will converge. Rounds is // the number of rounds to run the calculation, each round will on average // double the accuracy of the result. export fn babylonian_sqrt(radicand: f64, initial_guess: f64, rounds: uint) f64 = { let result: f64 = initial_guess; for (let i = 0u; i < rounds; i += 1) { result = (result + (radicand / result)) / 2f64; }; return result; };
thoughts on completed 1.0 release
written XXX
You are in a gemlog post. There is an appendix with strange gothic lettering here.
> examine appendix
The engravings translate to "This space intentionally left blank."