Published the 2025-11-02 on Willow's site

Why Hare codegen matter?

Hare does not support generics, and will probably never do. For example, if you want to parse an incoming stream of Json data, to fill your own variable as a struct type, you have to write the code dedicated to your type yourself.

use io;
use encoding::json::lex;

const my_type = struct {
	foo: str,
	bar: uint,
};

fn read_my_type_json(in: io::handle, out: *my_type) void = {
	// Init a lexer, and deal with it
};

But the very last release of Hare added support for annotation. It means we can now annotate some declaration, and an external tool could make use of it. One of the first use-case that comes in mind is to develop codegen tools. In this specific situation, one could write this declaration, and expect for some tool to use this annotation, and produce the functions to read and writes it as Json.

#[json::gen]
const my_type = struct {
	foo: str,
	bar: uint,
};

We could also mention that because there are no generics in Hare, there is also no hash table (associative arrays, or dictionnaries). Runxi Yu provides with hare-ds various general-purpose data structures for use in the Hare programming language. But the data structures here are not type safe, and are not recommended to use. Drew DeVault documented the safest alternatives with hare-datastructures, but this requires some additional labor from the user. In this area, a codegen tool based on some annotations would offer a good and safe solution to generate hash maps easily.

Another use-case where codegen fit as a solution. Multiple protocols are using a description of its elements stored in a specific format. Wayland and D-Bus defines their protocols with the XML format. We could write all the glue-code ourselves, but a tool could do this automatically, and would be less error-prone.

Hare is a language that is very strict with type hinting, and this brings many benefits to the codebases. When the compiler accepts your code, you can be rest assured that your APIs fit together from start to finish. Introducing generics to solve this kind of problem would weaken the language.

The whole point of this post is to demonstrate why codegen is the solution to many needs when writing Hare projects. But unfortunately, writing codegen tools in Hare is still a pain to do.

WhyNotHugo wrote some un-convenient code when he did hare-dbus:

const meth_lower = to_snake_case(meth.name);
defer free(meth_lower);

fmt::fprintfln(out, "// Marshal message for {} method call.", meth.name)!;
fmt::fprintfln(out, "export fn marshal_{}(", meth_lower)!;
fmt::fprintln(out, "\tserial: u32,")!;
fmt::fprintln(out, "\tdestination: str,")!;
fmt::fprintln(out, "\tpath: str,")!;

if (len(in_args) > 0) {
	fmt::fprintfln(out, "\tparams: {}_input,", meth_lower)!;
};

fmt::fprintln(out, ") (message::message | nomem) = {")!;

I’ve started writing similar code for the codegen on hare-json. I tried to avoid this by sticking as much as I could with [[strings::template]]. This means I had to prepare every variable as [[fmt::formattable]] first. You may notice I also nest multiple templates, by using some string variables that have been generated with other templates (initcode, readcode, other, endcode):

let t_readstruct: template::template = [];
const t_readstruct_src = `
marshall::expect_objstart(mar)?;
${initcode}
for (true) {
	switch (marshall::unmarshall_str(mar, false)?) {
${readcode}
${other}
	};
	match (marshall::expect_objend(mar)) {
	case void => break;
	case errors::invalid => marshall::expect_comma(mar)?;
	};
};
${endcode}
`;

And I haven’t even mentionned indentation problems, if you still try to produce readable output code.

Writing codegen tools must be made easier, because the language depends too much on it. For this we need a much better templating solution. That’s why I started working on a dedicated convenient and powerfull templating system for Hare!

And it is already released! Introducing hare-template.

RSS feed

If this post inspired you, feels free to leave a comment!

Reach me