↩ go back to index

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

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.

nearly everything is runtime

They implicitly change types like C.

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

they are not a first-class language feature, we have some "iterators" in the stdlib but it's just a pattern

vaargs are lowered to a stack-allocated slice

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:

https://docs.harelang.org/

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:

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

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.

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:

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."