Handmade cities

Boston - Aug 9-10 2024.
Seattle - Nov 20-22 2024.
Tickets available now

Learning Jam

March 15-17. March 22-24.
See the results.
March 15-17.
March 22-24. See the results.
Learn more

We are working to correct the course of the software industry.

We are a community of low-level programmers with high-level goals. Originally inspired by Casey Muratori's Handmade Hero, we dig deep into our systems and learn how to do things from scratch. We're not satisfied by the latest popular language or the framework of the month. Instead we care about how computers actually work.

Software quality is declining, and modern development practices are making it worse. We need to change course. Help us get the software industry back on track.

Latest News

Hello Handmade Network! 2024 has been great so far. Most notably, we held our first-ever Learning Jam in March, in which participants learn about a topic and share that knowledge with the rest of the community. We had great turnout for an experimental jam in its first year, and I’m excited to revisit it in the future.

But looking back, not everything we’ve done over the last couple years has been quite so successful. We’ve excitedly kicked off projects like our education initiative, Time Machine, even a 501c3 behind the scenes. Sadly, none of these have panned out. Making good educational resources with a Handmade flair is hard (really hard) and requires a huge time commitment from a rare type of person. Time Machine was a fun idea, but was never destined to succeed as a large community project. And the 501c3…we’ll save that for another time.

Community members did great work on these projects, and we learned a lot, but as time passed it became clear that we were neglecting the heart of the Handmade community: projects, and the people who author them.

Handmade software is literally the point of the Handmade Network. Communities that talk about programming are a dime a dozen. But Handmade software are different. It is so fast, so capable, so lightweight, so simple, that it shocks people with what modern computers are capable of.

At the end of the day, Handmade projects are what brings people to the community. This is not just me being nice; our Google Search analytics show that RemedyBG is by far the #1 source of traffic to handmade.network. #project-showcase is also the most popular channel on the Discord, and we frequently hear that it inspires people to dig deeper into their own projects. And ultimately, if we’re not making quality software, what’s the point?

So this year, we are 100% focused on projects. Our sole goal is to promote and boost the amazing work being done by the Handmade community. To that end:

  • We’re doing more jams. In addition to the Learning Jam, we’ll bringing back both the Visibility Jam and Wheel Reinvention Jam for another year—and plan to keep doing so indefinitely. The Visibility Jam will be in July, while the Wheel Reinvention Jam will be September. See this page for all the details.
  • We’re doubling down on Unwind. Our monthly Twitch show Unwind is an opportunity to dig deeper into technical details with the authors of various projects. The first few episodes have been a great time, but there’s so much more we can do with the show, and we hope to increase the show’s reach so that even more people can be aware of the great work being done by members of the community.
  • We’re redesigning the website. The current website design is very old, and doesn’t do a good job highlighting the actual work people are doing. Additionally, although the project system has been working pretty well for jams, there are many quality-of-life issues. The wonderful Jes Chuhta has been crafting a new design for us, and Asaf and I have been implementing it this month. In fact, I’m streaming the work every Monday and Friday this month over on Twitch.

As for our previous initiatives, we’ll be sunsetting them and archiving their content as necessary. Nothing will be lost except our time and our pride, but we’ll recover. 🙂

Before I close, a few key project updates:

  • Disk Voyager is coming along beautifully and already has dozens of very happy alpha users. He recently added a bookmarks / quick access panel, which I am very excited about. It will soon enter open alpha, so go to https://diskvoyager.com/ and sign up to make sure you get access.

    A screenshot of Disk Voyager's new bookmarks panel

  • Cactus Image Viewer has been receiving lots of quality updates recently, with more on the way, including a gallery of other images in the folder. You can download the latest version from GitHub.

    A screenshot of Cactus Image Viewer's new gallery UI

  • Orca is on the cusp of another major release. Shaw and I rewrote the Python tooling in C to reduce dependencies, Reuben added a complete libc implementation (no more shim!), and Martin rewrote the vector graphics backend in WebGPU. Make sure to subscribe to the Orca newsletter to be notified when it releases.

And finally, Abner has started a Discord server for Handmade Cities. You can read more about his rationale in this blog post, but if you are interested in meetups or coworking with Handmade folks, I recommend you go join.

Looking forward to many more great things this year! We’re just getting started.


Around the Network

Christoffer Lernö

It's been three months and finally version 0.6.0 is here!

C3 was on a monthly update cycle, so what happened to version 0.5.6? Actually the main branch contained a releasable 0.5.6 for the last two months, but I didn't release it. A release takes a bit of time updating everything and making announcements and so on, and I felt that maybe I should just release version 0.6.0 instead – rather than to push the relatively small 0.5.6.

But on the other hand I kept delaying 0.6.0, because I was looking for more breaking changes to add (recall that 0.5.x cannot introduce any breaking changes for 0.5 code, so 0.6.0 is where the breaking changes happen)

However, now 0.6.0 is stable enough, and there has lately been very little to backport into the 0.5.x branch. So it's time to do the 0.6.0 release.

Let's go through what happened since 0.5.5 first:

Updated enum syntax

Using enums with associated values now look more like defining C enums than Java enums:

// Old
enum Foo : int (int value, double value2)
    A(1, 2.0),
    B(2, 4.0)
enum Bar : int (int value)

// New
enum Foo : int (int value, double value2)
    A = { 1, 2.0 },
    B = { 2, 4.0 }
enum Bar : int (int value)
    C = 7, // Also possible to write as C = { 7 }
    D = 9

The new syntax also allows the enum size to be elided when there are associated values:

enum Foo : (int value, double value2)
    A = { 1, 2.0 },
    B = { 2, 4.0 }

There were other ideas around the enums, allowing them to be used more like unordered C enums. However, the experimental attempts showed little promise. Maybe this will be revisited for version 0.7.

Changes to any / interfaces

Two things changed, the most important thing is that the 0.5 change where any was handled written as a pointer (any*) was rolled back. This was a trade-off: in some cases using any* felt like it better expressed what was going on, but in other cases it ended up being confusing.

Secondly @default implementations for interfaces were removed. The need for these were greatly reduced when the stdlib moved from "call the method on the type" to "call a function with the interface" style (see std::io for examples).

Guaranteed jump tables

For switches it's now possible to get guaranteed calculated goto jump tables by using the @jump attribute:

switch (a) @jump
    case 1: res(1);
    case 3: res(3);

The above code will lower to an array of jump destinations which is indexed into and then jumped to.

RGBA swizzling

In addition to swizzling using xyzw it's now possible to use rgba as well:

int[<4>] abc = { 1, 2, 3, 4 };
int[<3>] z = abc.xwz; // Grabs index 1, 4 and 3
int[<3>] c = abc.rab; // Same as above

More distinct types

It's now possible to make distinct types out of void, typeid, anyfault and fault types.

Catch an error in defer

It's now possible to get the error thrown when using defer catch statements:

defer catch io::printn("Exit due to some error"); 
defer (catch err) io::printfn("Exit due to: %s", err); // New

Stricter semantics for if try

It's no longer possible to do:

if (try foo() + 1) { ... }

The semantics of this code was a bit confusing, so in 0.6.0 you may no longer do "if try" binary or unary expressions. You may however still test expressions like if (try (foo() + 1)).

Changes in command-line arguments

  • Added --output-dir to set the output directory.
  • Added --print-input to print all files used for compilation.
  • Removed --system-linker and replaced it by --linker which also allows setting custom linkers.

Stricter @if evaluation

Evaluating @if on the top level is always subject to ordering issues. For this reason 0.6 does not permit conditional definition guarded by @if to depend on something that in itself depended on @if.

int foo;
// This would succeed on 0.5.x, but changing the
// ordering, placing `B` before `a` would be a compile error.
const B @if($defined(foo)) = 1;
const B @if(!$defined(foo)) = 2;
int a @if(B == 1);

In 0.6.0 the above code is an error, as "a" depends on the conditional "B".

This change helps preventing the user from accidentally building code that depends on top level ordering.

assert(false) disallowed

Aside from (compile time known) unused branches and tests, asserts that are compile time evaluated to be false are now compile time errors. This allows asserts to detect more broken code at compile time.

const A = 1;
if (A == 0)
  assert(false); // Ok, dead branch.
  assert(false); // This is a compile time error
assert(A == 0); // Also a compile time error.

More permissive function definitions

Functions definitions may now be recursive (e.g. a function type taking as argument a pointer to itself).

Better errors for inlined macros

The code now provides a backtrace to where the macro was first inlined when detecting an error.

Improved debug information

The debug information has gotten an overhaul, in particular debug information for macros are much improved.

Changes to the stdlib

Various fixes, but perhaps most importantly list types now consistently use push rather than add, and pop now always return an Optional result.

The full change list 0.5.5 -> 0.6.0

Changes / improvements

  • @default implementations for interfaces removed.
  • any* => any, same for interfaces.
  • Private / local globals now have internal visibility in LLVM.
  • Updated enum syntax.
  • 'rgba' also available for swizzling.
  • The name "subarray" has been replaced by the more well known name "slice' across the codebase.
  • Improved alignment handling.
  • Add --output-dir to command line. #1155
  • Allow making distinct types out of "void", "typeid", "anyfault" and faults.
  • Removed --system-linker setting.
  • "Try" expressions may not be any binary or unary expressions. So for example try foo() + 1 is disallowed.
  • Added $$REGISTER_SIZE for int register size.
  • assert(false) only allowed in unused branches or in tests. Compile time failed asserts is a compile time error.
  • Require expression blocks returning values to have the value used.
  • Detect "unsigned >= 0" as errors.
  • Improve callstack debug information #1184.
  • Request jump table using @jump for switches.
  • Improved inline debug information.
  • Improved error messages on inlined macros.
  • Introduce MSVC compatible SIMD ABI.
  • $foreach doesn't create an implicit syntactic scope.
  • Error of @if depends on @if
  • Support defer (catch err)
  • Added print-input command argument to print all files used for compilation
  • Allow recursive function definitions as long as they are pointers. #1182
  • Default CPU to native if less than AVX, otherwise use AVX.
  • Bounds checking on length for foo[1:2] slicing #1191.
  • Foreach uses non-wrapping add/dec.


  • Fixed issue in safe mode when converting enums.
  • Better checking of operator methods.
  • Bug when assigning an optional from an optional.
  • Lambdas were not type checked thoroughly #1185.
  • Fix problems using reflection on interface types #1203.
  • @param with unnamed macro varargs could crash the compiler.
  • Compiler crash using enum nameof from different module #1205.
  • Incorrect length passed to scratch buffer printf.
  • Casting to a bitstruct would be allowed even if the type was the wrong size.
  • Generic modules parameterized with constants would sometimes get the wrong parameterized module name causing conversion errors #1192.
  • Duplicate emit of expressions on negation would incorrectly compile negated macros.
  • Casting a slice address to its pointer type should not compile #1193.
  • Union is not properly zero-initialized with designated initializer #1194.
  • Compile time fmod evaluates to 0 #1195.
  • Assertion failed when casting (unsigned) argument to enum #1196
  • Correct debug info on parameters without storage.
  • Fix location on foreach debug output.
  • Compiler crash on designated initializer for structs with bitstruct.

Stdlib changes

  • "init_new/init_temp" removed.
  • LinkedList API rewritten.
  • List "pop" and "remove" function now return Optionals.
  • RingBuffer API rewritten. Allocator interface changed.
  • Deprecated Allocator, DString and mem functions removed.
  • "identity" functions are now constants for Matrix and Complex numbers.
  • Removed 'append' from Object and List, replaced by 'push'.
  • GenericList renamed AnyList.
  • Proper handling of '.' and Win32 '//server' paths.
  • Path normalization - fix possible null terminator out of bounds.
  • Add 'zstr' variants for string::new_format / string::tformat.
  • Fix mutex and wait signatures for Win32.

0.6 has feature stability guarantees, code written for 0.6.0 will work with all of 0.6.x.

If you want to read more about C3, check out the documentation: https://c3-lang.org or download it and try it out: https://github.com/c3lang/c3c

Blog comment: Plotting
Forum reply: RemedyBG
Forum reply: RemedyBG
Forum reply: RemedyBG
Simon Anciaux
New forum thread: RemedyBG
Forum reply: Coroutines

Community Showcase

This is a selection of recent work done by community members. Want to participate? Join us on Discord.