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: or download it and try it out: