Skip to content

2025

Jingle bells, C3 0.7.8

With Christmas on the horizon, C3 gets another monthly update to 0.7 with 0.7.8. As usual it brings a set of small tweaks and fixes. Let's see what we got:

Struct splatting

0.7.7 added struct initializer splatting, but it was a special case. And while it has been possible to splat an array or slice into both initializers and calls, structs didn't support that. This has been amended in 0.7.8:

struct Foo
{
    int a;
    double b;
}

fn void test(int x, double y, int a)
{
    io::printfn("%s %s", x * a, y * a);
}

fn int main()
{
    Foo f = { 42, 3.14 };
    test(...f, 2);          // prints 84 6.280000
    return 0;
}

Swizzle initialization for vectors

While vectors can use names to reference the first four values, e.g. foo.x as well as supporting swizzling: foo.xy, designated initialization has been limited to using the same syntax as arrays: { [0..1] = 1.2, [2] = 3.2 }.

With this improvement, it's now possible to use the name of the components directly:

float[<3>] x = { .xy = 1.2, .z = 3.3 };
// Same as float[<3>] x = { [0..1] = 1.2, [2] = 3.2 };
A limitation is that any swizzling syntax, like .xy must indicate a consecutive gapless range, so initializing using for example .zx or .xz would not be allowed.

Function referencing in @return? for simplified fault declarations

Before we had this:

faultdef BAD_ZERO, BAD_ONE, TOO_BIG;
<*
 @return? BAD_ZERO, BAD_ONE
*> 
fn int? foo(int a)
{
    switch (a)
    {
        case 0: return BAD_ZERO?;
        case 1: return BAD_ONE?;
        default: return a * 2;  
    }
}

// We must repeat the errors of "foo"
<*
 @return? BAD_ZERO, BAD_ONE, TOO_BIG
*> 
fn int? bar(int a)
{
    if (a > 100) return TOO_BIG?;
    return foo(a) ^ 12;
}

With this improvement, we can refer to the errors of a function (or function pointer) in the @return? statements:

<*
 @return? foo!, TOO_BIG
*> 
fn int? bar(int a)
{
    if (a > 100) return TOO_BIG?;
    return foo(a) ^ 12;
}

Enums now support membersof to return associated values.

Enums used to only support the type property .associated which returned a list of the associated value types. Enums now instead use .membersof, which works like the same property on structs. .associated has been deprecated as .membersof includes its information.

enum Foo : (String x, int val)
{
    ABC = { "Hello", 3 },
    DEF = { "World", -100 },
}

fn int main()
{
    io::printn(Foo.membersof[0].get(Foo.ABC)); // Print "Hello"
    io::printn(Foo.membersof[1].get(Foo.DEF)); // Print -100
    $assert Foo.membersof[0].type == String.typeid;
    io::printn(Foo.membersof[0].nameof);       // prints x
    Foo f = ABC;
    io::printn(Foo.membersof[1].get(f));       // prints 3
    return 0;
}

@param directives for ... parameters

C vaargs on functions were previously not possible to reference using @param. This has been improved, allowing "..." to be referenced:

<* 
 @param fmt : "the format string"
 @param ... : "the arguments to print" 
*>
extern fn int printf(ZString fmt, ...);

Missing imports allowed if module @if evaluates to false

This change means that if you add imports to a module that isn't enabled, they are not checked:

module foo @if(false);
import non_existing_lib; // Missing module

fn int test()
{
    return 0;
}

Prior to 0.7.8 this would be reported as an error due to non_existing_lib not being a valid module, but from 0.7.8 such errors are only reported if the importing module is enabled. In this example, changing @if(false) to @if(true) would make the import reported as an error.

Linux musl support

A --linux-libc command line option has been added, supporting gnu and musl options. This is the beginning of official musl support for the C3C compiler, contributed by DylanDoesProgramming.

Support of int $foo... arguments

Named macro vaargs were incorrectly handled prior to 0.7.7, but the change in 0.7.7 inadvertently prevented typed const vaargs like int $foo.... This is now enabled again.

Small bag of improvements

  • String merging of "foo" "bar" is now much more efficient, handling very long strings easily.
  • Win32 got a default exception handler thanks to TechnicalFowl.
  • $schema was added as a key in project.json.
  • The @simd implementation was changed, and @simd is now possible to use directly after the type as needed.
  • --test-nocapture is deprecated in favour of --test-show-output.
  • Xtensa target no longer enabled by default on LLVM 22, use -DXTENSA_ENABLE to enable it instead.

Fixes

0.7.8 contains around 30 fixes, with the increase compared to 0.7.7 mostly depending on the vector ABI changes which yielded some regressions to clean up in 0.7.8.

Stdlib changes

The MacOS bindings in std::os::macos nicely got a bunch of additions contributed by Glenn Kirk, and printing typeids now prints the actual underlying id as well. Printing BigInts was optimized and printf now has caching which makes printing on Win32 faster.

Looking Forward

Several things are in the pipe: possibly updating the syntax for turning a fault into an optional, going from return io::EOF?; to some syntax that makes the grammar simpler. Inline asm is still waiting for its revision, and there should be a review of the casting rules. Finally, generating proper headers when building static and dynamic is rather overdue.

Community and Contributions

This release wouldn't have been possible without the C3 community. I'd like to extend a deep thank you to all who have contributed, both through filed issues, PRs and just plain discussions.

Change Log

Click for full change log

Changes / improvements

  • Improve multiline string parser inside compiler #2552.
  • Missing imports allowed if module @if evaluates to false #2251.
  • Add default exception handler to Win32 #2557.
  • Accept "$schema" as key in project.json #2554.
  • Function referencing in @return? for simplified fault declarations. Check @return? eagerly #2340.
  • Enums now work with membersof to return the associated values. #2571
  • Deprecated SomeEnum.associated in favour of SomeEnum.membersof
  • Refactored @simd implementation.
  • Improve error message for Foo{} when Foo is not a generic type #2574.
  • Support @param directives for ... parameters. #2578
  • Allow splatting of structs. #2555
  • Deprecate --test-nocapture in favour of --test-show-output #2588.
  • Xtensa target no longer enabled by default on LLVM 22, Compile with -DXTENSA_ENABLE to enable it instead
  • Add float[<3>] x = { .xy = 1.2, .z = 3.3 } swizzle initialization for vectors. #2599
  • Support int $foo... arguments. #2601
  • Add musl support with --linux-libc=musl.

Fixes

  • Foo.is_eq would return false if the type was a typedef and had an overload, but the underlying type was not comparable.
  • Remove division-by-zero checks for floating point in safe mode #2556.
  • Fix division-by-zero checks on a /= 0 and b /= 0f #2558.
  • Fix fmod a %= 0f.
  • Regression vector ABI: initializing a struct containing a NPOT vector with a constant value would crash LLVM. #2559
  • Error message with hashmap shows "mangled" name instead of original #2562.
  • Passing a compile time type implicitly converted to a typeid would crash instead of producing an error. #2568
  • Compiler assert with const enum based on vector #2566
  • Fix to Path handling c:\foo and \home parent. #2569
  • Fix appending to c:\ or \ #2569.
  • When encountering a foreach over a ZString* it would not properly emit a compilation error, but hit an assert #2573.
  • Casting a distinct type based on a pointer to an any would accidentally be permitted. #2575
  • overflow_* vector ops now correctly return a bool vector.
  • Regression vector ABI: npot vectors would load incorrectly from pointers and other things. #2576
  • Using defer catch with a (void), would cause an assertion. #2580
  • Fix decl attribute in the wrong place causing an assertion. #2581
  • Passing a single value to @wasm would ignore the renaming.
  • *(int*)1 incorrectly yielded an assert in LLVM IR lowering #2584.
  • Fix issue when tests encounter a segmentation fault or similar.
  • With project.json, when overriding with an empty list the base settings would still be used. #2583
  • Add sigsegv stacktrace in test and regular errors for Darwin Arm64. #1105
  • Incorrect error message when using generic type that isn't imported #2589
  • String.to_integer does not correctly return in some cases where it should #2590.
  • Resolving a missing property on a const enum with inline, reached an assert #2597.
  • Unexpected maybe-deref subscript error with out parameter #2600.
  • Bug on rethrow in return with defer #2603.
  • Fix bug when converting from vector to distinct type of wider vector. #2604
  • $defined(hashmap.init(mem)) causes compiler segfault #2611.
  • Reference macro parameters syntax does not error in certain cases. #2612
  • @param name parsing too lenient #2614.

Stdlib changes

  • Add CGFloat CGPoint CGSize CGRect types to core_foundation (macOS).
  • Add NSStatusItem const enum to ns module (macOS).
  • Add NSWindowCollectionBehavior NSWindowLevel NSWindowTabbingMode to ns module (macOS).
  • Add ns::eventmask_from_type function to objc (macOS).
  • Deprecate objc enums in favour of const inline enums backed by NS numerical types, and with the NS prefix, to better align with the objc api (macOS).
  • Deprecate event_type_from function in favour of using NSEvent directly, to better align with the objc api (macOS).
  • Add unit tests for objc and core_foundation (macOS).
  • Make printing typeids give some helpful typeid data.
  • Add NSApplicationTerminateReply to ns module (macOS).
  • Add registerClassPair function to objc module (macOS).
  • Somewhat faster BigInt output.
  • Cache printf output.

Want To Dive Into C3?

Check out the documentation or download it and try it out.

Have questions? Come and chat with us on Discord.

Discuss this article on Reddit and Hacker News.

C3 Language at 0.7.7: Vector ABI, RISCV improvements and more

0.7.7 is a major advance in C3 usability with vector ABI changes. It also contains several small quality-of-life additions, such as the ability to splat structs into an initializer, and implicit subscript dereferencing. Fairly few bugs were discovered during this development cycle, which is why the fixed bugs are unusually low.

Let's look at what 0.7.7 brings in more detail:

Vector ABI changes

The most significant change in this release is the ABI change for vectors, which now store and pass vectors as arrays in function calls and structs. While vectors still use SIMD, their equality to arrays on the ABI level means that C graphical libraries will directly match vector types.

Where before you needed to work with C structs defining vectors and then converting them to SIMD vectors for actual computation, it now works out of the box. Another problem with vectors prior to 0.7.7 was their space and alignment requirements over structs. From 0.7.7 alignment matches that of structs and arrays, making them extremely convenient to work with.

For cases where SIMD vectors are actually expected, it's possible to create distinct types using typedef with a new @simd attribute to exactly match standard C SIMD vectors, e.g. typedef V4si = int[<4>] @simd;. This then exactly matches the corresponding C SIMD type.

This makes it easier than ever to use SIMD with C3.

An example:

// Pre 0.7.7
union Vec3
{
    struct
    {
        float x, y, z;
    }
    float[3] arr;
}
extern fn void draw_image(Image* image, Vec3 pos);

fn void update()
{
    ...
    // Speed and position is stored as Vec3
    float[<3>] speed = ball.speed.arr; // Implicit conversion array to vector 
    float[<3>] position = ball.position.arr; // Implicit conversion array to vector
    ball.position = (position + speed);
}
// 0.7.7+
alias Vec3 = float[<3>]; // Equivalent to the struct due to ABI change
extern fn void draw_image(Image* image, Vec3 pos);

fn void update()
{
    ...
    // Speed and position is stored as Vec3
    ball.position += ball.speed; // SIMD add
}

Struct initializer splats

This feature enables using the splat operator ... to give a designated initializer default values that are overridden by the following arguments.

struct Foo
{
    int a;
    double b;
    String c;
}

fn void test()
{
    Foo f = { 1, 2.3, "Hi" };
    Foo f2 = { ...f, .a = 8, .c = "Bye" }; // Results in { 8, 2.3, "Bye" }
}

Subscript deref

When passing arrays or lists by reference, the [] operator tend to behave in an undesirable way, dereferencing the pointer instead of the underlying array/list:

fn void test(List{int}* list_ref, int[3]* array_ref, int[3] array)
{
    // WRONG, would yield a 'List{int}' not an int
    // int val = list_ref[1]; 
    int val = (*list_ref)[1]; // Correct
    int val2 = list_ref.get(1); // Also correct, uses implicit deref of '.'
    // Wrong, would yield an 'int[3]', not an int
    // int val3 = array_ref[1]; 
    int val3 = (*array_ref)[1]; // Correct
    int val4 = array[1];
}

Subscript deref addresses this. Using .[1] will dereference if needed:

fn void test(List{int}* list_ref, int[3]* array_ref, int[3] array)
{
    int val = list_ref.[1]; 
    int val2 = array_ref.[1];
    int val3 = array.[1]; // Works even though it isn't a pointer.
}

This is helpful when writing macros and such that will want to accept both elements by reference and by value:

macro third_element(x)
{
    return x.[2];
}
fn void test()
{
    int[3] arr;
    int[] slice = &arr;
    third_element(arr);   // Works
    third_element(slice); // Works
    third_element(&arr);  // Also works thanks to subscript deref
}

Typedef with alignment

A new feature for typedef is to allow creating a type with a specific alignment without wrapping it in a struct. We may, for example, create an integer that is 16 bit aligned using typedef Int2 = int @align(2);. This is an alternative way to safely work with references to under-aligned members in packed structs.

// Pre 0.7.7
struct Foo @packed
{
    char a;
    int b;
}
fn void test()
{
    Foo f = { 'a', 1 };
    int* b_ref = &f.b;
    @unaligned_store(*b_ref, 2, 1); // Valid
    *b_ref = 2; // Error at runtime in safe mode, unaligned access
}
// 0.7.7+
typedef IntAlign1 = int @align(1);
struct Foo @packed
{
    char a;
    IntAlign1 b;
}
fn void test()
{
    Foo f = { 'a', 1 };
    IntAlign1* b_ref = &f.b;
    *b_ref = 2;
}

More string functions at compile time

@str_snakecase, @str_constantcase, @str_pascalcase and @str_replace macros are added to modify strings at compile time efficiently for certain macro manipulation at compile time.

fn void test()
{
    String $test = "HelloWorld";
    $echo @str_snakecase($test);               // echoes "hello_world"
    $echo @str_constantcase($test);            // echoes "HELLO_WORLD"
    String $test2 = "hello_world";
    $echo @str_pascalcase($test2);             // echoes "HelloWorld"
    $echo @str_replace($test, "Hello", "Bye"); // echoes "ByeWorld"
}

Small but important changes

Aliases that refer to @local variables must themselves have local visibility. @extern is renamed @cname as it was frequently misunderstood. Generic inference now works better in initializers. For slices with the .. syntax, it's now possible to have the end index be one less than the starting index, so that zero size slices can be expressed with the .. syntax as well.

Cross-Platform and Architecture Support Expansion

This release significantly strengthens C3C's cross-platform capabilities, particularly for RISC-V architecture support. It's now possible to set individual CPU features using --cpu-flags, e.g. --cpu-flags +avx,-sse. For RISC-V, --riscv-cpu has been added, as well as renaming the RISC-V abi flag to the more correct --riscv-abi.

Stdlib changes

The sorting macros accidentally only took non-slices by value, which would work in some cases but not in others. This has been fixed, but might mean that some code needs to update as well. TcpSocketPair was added to the tcp module to create a bidirectional local socket pair, and using sockets on Windows should now implicitly initialize the underlying socket subsystem.

Fixes

0.7.7 has only about 11 fixes, which reflects the relatively few bugs encountered in the 0.7.7 cycle. There are outstanding bugs on the inline asm, which has a significant update planned. The most important fix is patching a regression for MacOS which prevented backtrace printing.

Looking Forward

With the updated Vector ABI and the change from @extern to @cname there are a lot of vendor libraries that will need a refresh. There is also a new matrix library in development that hopefully might get included in the next release. There is more functionality to add for fine-tuning processor capabilities for both RISC-V, but also AArch64. There have also been requests for 32-bit Arm support, but the lack of CI tests for different Arm processors is blocking it at the moment.

Community and Contributions

This release wouldn't have been possible without the C3 community. I'd like to extend a deep thank you to all who have contributed, both through filed issues, PRs and just plain discussions.

Change Log

Click for full change log

Changes / improvements

  • Error when using $vaarg/$vacount/$vasplat and similar in a macro without vaargs #2510.
  • Add splat defaults for designated initialization #2441.
  • Add new builtins $$str_snakecase $$str_replace and $$str_pascalcase.
  • "build-dir" option now available for project.json, added to project. #2323
  • Allow .. ranges to use "a..a-1" in order to express zero length.
  • Disallow aliasing of @local symbols with a higher visibility in the alias.
  • Add --max-macro-iterations to set macro iteration limit.
  • Improved generic inference in initializers #2541.
  • "Maybe-deref" subscripting foo.[i] += 1 #2540.
  • ABI change for vectors: store and pass them as arrays #2542.
  • Add @simd and @align attributes to typedef #2543.
  • Rename @extern to @cname, deprecating the old name #2493.
  • Allow (Foo)0 bitstruct casts even if type sizes do not match.
  • The option --riscvfloat renamed --riscv-abi.
  • Add initial --cpu-flags allowing fine grained control over CPU features.
  • Add --riscv-cpu settings for RISC-V processors #2549.

Fixes

  • Bug in io::write_using_write_byte.
  • Bitstruct value cannot be used to index a const array in compile time. #2512
  • Compiler fails to stop error print in recursive macro, and also prints unnecessary "inline at" #2513.
  • Bitstruct truncated constant error escapes $defined #2515.
  • Compiler segfault when accessing member of number cast to bitstruct #2516.
  • Compiler assert when getting a member of a bitstruct : char @bigendian #2517.
  • Add ??? and +++= to list-precedence.
  • Fix issues with linking when using symbol aliases. #2519
  • Splatting optional compile-time macro parameter from inside lambda expression does not work #2532.
  • Compiler segfault when getting a nonexistant member from an unnamed struct #2533.
  • Correctly mention aliased type when method is not implemented #2534.
  • Regression: Not printing backtrace when tests fail for MacOS #2536.

Stdlib changes

  • Sorting functions correctly took slices by value, but also other types by value. Now, only slices are accepted by value, other containers are always by ref.
  • Added @str_snakecase, @str_replace and @str_pascalcase builtin compile time macros based on the $$ builtins.
  • Add TcpSocketPair to create a bidirectional local socket pair.
  • Add extern fn CInt socketpair(AIFamily domain, AISockType type, CInt protocol, NativeSocket[2]* sv) binding to posix.
  • Add extern fn getsockname(NativeSocket socket, SockAddrPtr address, Socklen_t* address_len) binding to win32.

Want To Dive Into C3?

Check out the documentation or download it and try it out.

Have questions? Come and chat with us on Discord.

Discuss this article on Reddit.

C3 Language at 0.7.6: Shebang, generic inference and lengthof()

The C3 programming language continues its steady evolution with version 0.7.6, focusing on quality-of-life improvements and language refinements While previous 0.7.x versions have seen some notable additions to the language itself, 0.7.6 only adds a few minor features, with nothing new planned for 0.7.7. Originally the inline asm updates and fixes were scheduled for 0.7.6, the discussion around the semantics took too much time for it to make it to 0.7.6.

In other news, C3 is now tracked by Linguist on GitHub which means code on GitHub will finally have highlighting for C3 files.

New Features and Improvements

  • Built-in functionlengthof() - The new lengthof function provides a unified way to use the result of the method or function tagged @operator(len). For builtin types with length, such as arrays, this will lower into a .len field access. This allows building macros that leverage @operator(len) without having to use foreach.
  • Shebang Support: C3 source files now support #! comments on the first line, simplifying using C3 code in scripts.
  • Generic parameter inference: - Generic parameter inference from left to right is added: List{int} x = list::NOHEAP;.
  • A new +++= operator - This complements the +++ compile time concatenation operator to make code that use repeated concatenation shorter and more concise.
  • Enhanced $defined - $defined now also supports $nameof, $offsetof and $alignof, so that the code can query if the builtins are supported for the particular argument. This is particularly useful for lazy arguments.
  • Slice and Array Comparisons: User-defined types implementing the == operator overload can now be properly compared when used in slices and arrays.
  • Project version builtin: You can now access your project version using env::PROJECT_VERSION. It reflects exactly the string in project.json.

Standard Library Expansions

  • InterfaceList: - A new container for storing values that implement specific interfaces, enabling more flexible polymorphic programming.
  • Enhanced LinkedList: - Now supports array view operations including [] indexing and both forward and reverse foreach iteration (#2438).
  • Cross-Platform File System Support: - Directory location support has been added for home, documents, downloads and other folders in the path module.
  • Added I/O Operations: - io::skip for skipping data and little-endian family for read/write operations io::read_le, io::write_le.
  • CVaList support: - Added MacOS AArch64 and Linux/MacOS x64 valist support with CVaList.

Community and Contributions

This release wouldn't have been possible without the incredible C3C community. The collaborative effort in identifying, reporting, and fixing the numerous issues addressed in this release exemplifies the strength of the C3C development community.

The C3 team remains committed to creating a modern, safe, and fast programming language that doesn't compromise on the low-level control that makes C so enduring. Thank you to everyone who has contributed to making this release possible!

Change Log

Click for full change log

Changes / improvements

  • Add lengthof() compile time function #2439
  • Allow doc comments on individual struct members, faultdefs and enum values #2427.
  • $alignof, $offsetof and $nameof can now be used in $defined.
  • Infer generic parameters lhs -> rhs: List{int} x = list::NOHEAP.
  • Unify generic and regular module namespace.
  • env::PROJECT_VERSION now returns the version in project.json.
  • Comparing slices and arrays of user-defined types that implement == operator now works #2486.
  • Add 'loop-vectorize', 'slp-vectorize', 'unroll-loops' and 'merge-functions' optimization flags #2491.
  • Add exec timings to -vv output #2490.
  • Support #! as a comment on the first line only.
  • Add +++= operator.

Fixes

  • Compiler assert with var x @noinit = 0 #2452
  • Confusing error message when type has [] overloaded but not []= #2453
  • $defined(x[0] = val) causes an error instead of returning false when a type does not have []= defined #2454
  • Returning pointer to index of slice stored in a struct from method taking self incorrectly detected as returning pointer to local variable #2455.
  • Inlining location when accessing #foo symbols.
  • Improve inlined-at when checking generic code.
  • Fix codegen bug in expressions like foo(x()) ?? io::EOF? causing irregular crashes.
  • Correctly silence "unsupported architecture" warning with --quiet #2465
  • Overloading &[] should be enough for foreach. #2466
  • Any register allowed in X86_64 inline asm address. #2463
  • int val = some_int + some_distinct_inline_int errors that int cannot be cast to DistinctInt #2468
  • Compiler hang with unaligned load-store pair. #2470
  • ?? with void results on both sides cause a compiler crash #2472.
  • Stack object size limit error on a static object. #2476
  • Compiler segfault when modifying variable using an inline assembly block inside defer #2450.
  • Compile time switch over type would not correctly compare function pointer types.
  • Regression: Compiler segfault when assigning struct literal with too few members #2483
  • Fix compile time format check when the formatting string is a constant slice.
  • Compiler segfault for invalid e-mails in project.json. #2488
  • Taking .ordinal from an enum passed by pointer and then taking the address of this result would return the enum, not int.
  • Alias and distinct types didn't check the underlying type wasn't compile time or optional.
  • Incorrect nameof on nested struct names. #2492
  • Issue not correctly aborting compilation on recursive generics.
  • Crash during codegen when taking the typeid of an empty enum with associated values.
  • Assert when the binary doesn't get created and --run-once is used. #2502
  • Prevent foo.bar = {} when bar is a flexible array member. #2497
  • Fix several issues relating to multi-level inference like int[*][*] #2505
  • $for int $a = 1; $a < 2; $a++ would not parse.
  • Fix lambda-in-macro visibility, where lambdas would sometimes not correctly link if used through a macro.
  • Dead code analysis with labelled if did not work properly.
  • Compiler segfault when splatting variable that does not exist in untyped vaarg macro #2509

Stdlib changes

  • Added Advanced Encryption Standard (AES) algorithm (ECB, CTR, CBC modes) in std::crypto::aes.
  • Added generic InterfaceList to store a list of values that implement a specific interface
  • Added path::home_directory, path::documents_directory, path::videos_directory, path::pictures_directory, path::desktop_directory, path::screenshots_directory, path::public_share_directory, path::templates_directory, path::saved_games_directory, path::music_directory, path::downloads_directory.
  • Add LinkedList array_view to support [] and foreach/foreach_r. #2438
  • Make LinkedList printable and add == operator. #2438
  • CVaList support on MacOS aarch64, SysV ABI x64.
  • Add io::skip and io::read_le and io::write_le family of functions.

Want To Dive Into C3?

Check out the documentation or download it and try it out.

Have questions? Come and chat with us on Discord.

Discuss this article on Reddit or Hacker News.

C3 Language at 0.7.5: Language tweaks and conveniences

The C3 programming language has reached 0.7.5, marking another milestone in the language's evolution. This release brings improvements to language features, developer experience, and standard library functionality.

Here's what's new and improved in this update.

Major Language Features

Module Aliasing Support

One of the new features in 0.7.5 is the introduction of module aliasing with the syntax alias foo = module std::io. This enhancement improves user control over code organization and readability, allowing developers to create shorter names for modules where needed.

Enhanced Compile-Time Capabilities

The compile-time system has received several additions: - Optional macro params: Gives macros arguments that are optional without requiring a default value. - ???: Compile time ternary, guaranteed to resolve at compile time and will not execute the false branch. - @safeinfer: Enables the use of var in function contexts, where it was previously disallowed. - New mathematical builtins: @intlog2, @clz, @min, @max and functions are now available at compile time. - bitsizeof macro builtin: Provides bit-level size information for types.

Operator Overloading Evolution

C3 0.7.5 brings improvements to operator overloading: - @operator(==) now also enables switch statement support for the type. - Enhanced chained array access: foo[x][y] = b now can pass through multiple levels of overloads, and works as expected with proper overload resolution. - Type.is_eq now correctly returns true for types with equality overloads.

Type System Enhancements
  • $kindof: Shorthand for $typeof(...).kindof which simplifies contract checks.
  • Implicit type conversions: Types now convert to typeid implicitly, streamlining contracts and compile time programming.
  • Enhanced $defined: It now accepts declarations, like $defined(int x = y) which removes the need for macros like @assignable_to.
  • Struct inheritance: Struct and typedef subtypes now inherit dynamic methods.

Developer Experience Improvements

Better Error Messages and Safety

The compiler now provides more helpful diagnostics: - Improved error messages for missing $endif and missing if bodies. - Better directory creation error messages in project and library creation - Huge stack object overflow protection with configurable --max-stack-object-size

Build System Enhancements
  • Library management: c3l-libraries now package linked libraries in a directory specified by the "linklib-dir" setting.
  • Cross-platform improvements: Enhanced support for different operating systems and architectures

Standard Library Expansion

New Data Structures and Utilities

The standard library has grown: - FileMmap: Memory-mapped file management - FixedBlockPool: Memory pool for fixed-size objects - HashSet: Generic hash set implementation with values method support - AsciiCharset: Fast ASCII character matching utilities - Logging system: Introduction of std::core::log for common logging .

Enhanced String Operations

String manipulation gets a boost with: - String.contains_char: Character containment checking - String.trim_charset: Trimming based on character sets - Functional array operations: New macros including @zip, @reduce, @filter, @any, @all, @sum, @product and @indices_of.

Breaking Changes and Deprecations

Important Deprecations

Several features have been deprecated in favor of improved alternatives: - @compact comparison behavior (use --use-old-compact-eq for compatibility) - add_array in favor of push_all on lists. - @assignable_to in favor of using $define. - @typekind in favor of using $kindof. - @typeis in favor of $typeof(foo) == Type. - @select in favor of $foo ??? #expr1 : #expr2.

Performance and Bug Fixes

Critical Fixes

This release addresses numerous important issues: - ASAN triggering fixes in List.remove_at - AVX512 vector handling corrections - Codegen improvements for if-try expressions - Memory allocation optimizations for 32-bit machines - Recursive generic creation detection

Platform-Specific Improvements
  • Enhanced Android and OpenBSD support
  • Improved native CPU detection
  • Better cross-compilation support

Looking Forward

Deprecations of many type introspection macros, such as @typekind and @typeis is together with the improvements in $defined, the implicit type conversions to typeid and $kindof spearheading a shift to making constraint checking succinct while also being completely obvious. Relying on macros would often make the constraints less clear to a reader. On top of this we get compile time ternary using ??? : to succinctly express compile time selection between two expressions. With the changes, the code is as short to type but without the need to remember particulars of one macro over the other.

Overall, C3 0.7.5 represents another step in maturing the language's core features while laying the groundwork for future enhancements. The focus will continue to be aimed at improve developer experience, performance, and language consistency. Many of the standard library additions are contributions from the community around C3, which is providing essential feedback and direction to polish the language further. C3 is step-by-step establishing itself as a modern evolution of C that maintains simplicity while adding powerful abstractions.

Demo

For a deeper look at the changes, watch the demo.

Change Log

Click for full change log

Changes / improvements

  • Support alias foo = module std::io module aliasing.
  • Add compile-time @intlog2 macro to math.
  • Add compile-time @clz builtin. #2367
  • Add bitsizeof macro builtins. #2376
  • Add compile-time @min and @max builtins. #2378
  • Deprecate @compact use for comparison. Old behaviour is enabled using --use-old-compact-eq.
  • Switch available for types implementing @operator(==).
  • Type.is_eq is now true for types with == overload.
  • Methods ignore visibility settings.
  • Allow inout etc on untyped macro parameters even if they are not pointers.
  • Deprecate add_array in favour of push_all on lists.
  • Fix max module name to 31 chars and the entire module path to 63 characters.
  • Improve error message for missing $endif.
  • foo[x][y] = b now interpreted as (*&foo[x])[y] = b which allows overloads to do chained [] accesses.
  • Error if a stack allocated variable is too big (configurable with --max-stack-object-size).
  • Add @safeinfer to allow var to be used locally.
  • Types converts to typeid implicitly.
  • Allow $defined take declarations: $defined(int x = y)
  • Struct and typedef subtypes inherit dynamic functions.
  • Improved directory creation error messages in project and library creation commands.
  • @assignable_to is deprecated in favour of $define
  • Add linklib-dir to c3l-libraries to place their linked libraries in. Defaults to linked-libs
  • If the os-arch linked library doesn't exist, try with os for c3l libs.
  • A file with an inferred module may not contain additional other modules.
  • Update error message for missing body after if/for/etc #2289.
  • @is_const is deprecated in favour of directly using $defined.
  • @is_lvalue(#value) is deprecated in favour of directly using $defined.
  • Added $kindof compile time function.
  • Deprecated @typekind macro in favour of $kindof.
  • Deprecated @typeis macro in favour of $typeof(#foo) == int.
  • $defined(#hash) will not check the internal expression, just that #hash exists. Use $defined((void)#hash) for the old behaviour.
  • Added optional macro arguments using macro foo(int x = ...) which can be checked using $defined(x).
  • Add compile time ternary $val ??? <expr> : <expr>.

Fixes

  • List.remove_at would incorrectly trigger ASAN.
  • With avx512, passing a 512 bit vector in a union would be lowered incorrectly, causing an assert. #2362
  • Codegen error in if (try x = (true ? io::EOF? : 1)), i.e. using if-try with a known Empty.
  • Codegen error in if (try x = (false ? io::EOF? : 1)), i.e. using if-try with a CT known value.
  • Reduce allocated Vmem for the compiler on 32 bit machines.
  • Bug causing a compiler error when parsing a broken lambda inside of an expression.
  • Fixed: regression in comments for @deprecated and @pure.
  • Detect recursive creation of generics #2366.
  • Compiler assertion when defining a function with return type untyped_list #2368.
  • Compiler assert when using generic parameters list without any parameters. #2369
  • Parsing difference between "0x00." and "0X00." literals #2371
  • Fixed bug generating $c += 1 when $c was derived from a pointer but behind a cast.
  • Compiler segfault when using bitwise not on number literal cast to bitstruct #2373.
  • Formatter did not properly handle "null" for any, and null for empty faults. #2375
  • Bitstructs no longer overloadable with bitops. #2374
  • types::has_equals fails with assert for bitstructs #2377
  • Fix native_cpus functionality for OpenBSD systems. #2387
  • Assert triggered when trying to slice a struct.
  • Improve codegen for stack allocated large non-zero arrays.
  • Implement a5hash in the compiler for compile-time $$str_hash to match String.hash().
  • Functions being tested for overload are now always checked before test.
  • Compile time indexing at compile time in a $typeof was no considered compile time.
  • Slicing a constant array with designated initialization would not update the indexes.
  • Fix for bug when @format encountered * in some cases.
  • Compiler segfault on global slice initialization with null[:0] #2404.
  • Use correct allocator in replace.
  • Regression: 1 character module names would create an error.
  • Compiler segfault with struct containing list of structs with an inline member #2416
  • Occasionally when using macro method extensions on built-in types, the liveness checker would try to process them. #2398
  • Miscompilation of do-while when the while starts with a branch #2394.
  • Compiler assert when calling unassigned CT functions #2418.
  • Fixed crash in header generation when exporting functions with const enums (#2384).
  • Fix incorrect panic message when slicing with negative size.
  • Incorrect type checking when &[] and [] return optional values.
  • Failed to find subscript overloading on optional values.
  • Socket.get_option didn't properly call getsockopt, and getsockopt had an invalid signature.
  • Taking the address of a label would cause a crash. #2430
  • @tag was not allowed to repeat.
  • Lambdas on the top level were not exported by default. #2428
  • has_tagof on tagged lambdas returns false #2432
  • Properly add "inlined at" for generic instantiation errors #2382.
  • Inlining a const as an lvalue would take the wrong path and corrupt the expression node.
  • Grabbing (missing) methods on function pointers would cause crash #2434.
  • Fix alignment on jump table.
  • Fix correct ? after optional function name when reporting type errors.
  • Make log and exp no-strip.
  • @test/@benchmark on module would attach to interface and regular methods.
  • Deprecated @select in favor of ???.
  • Enum inference, like Foo x = $eval("A"), now works correctly for $eval.
  • Fix regression where files were added more than once. #2442
  • Disambiguate types when they have the same name and need cast between each other.
  • Compiler module-scope pointer to slice with offset, causes assert. #2446
  • Compiler hangs on == overload if other is generic #2443
  • Fix missing end of line when encountering errors in project creation.
  • Const enum methods are not being recognized. #2445
  • $defined returns an error when assigning a struct initializer with an incorrect type #2449

Stdlib changes

  • Add == to Pair, Triple and TzDateTime. Add print to Pair and Triple.
  • Add OpenBSD to env::INET_DEVICES and add required socket constants.
  • Added FileMmap to manage memory mapped files.
  • Add vm::mmap_file to memory map a file.
  • Updated hash functions in default hash methods.
  • Added FixedBlockPool which is a memory pool for fixed size blocks.
  • Added the experimental std::core::log for logging.
  • Added array @zip and @zip_into macros. #2370
  • Updated termios bindings to use bitstructs and fixed some constants with incorrect values #2372
  • Add Freestanding OS types to runtime env:: booleans.
  • Added libloaderapi to std::os::win32.
  • Added HashSet.values and String.contains_char #2386
  • Added &[] overload to HashMap.
  • Deprecated PollSubscribes and PollEvents in favour of PollSubscribe and PollEvent and made them const enums.
  • Added AsciiCharset for matching ascii characters quickly.
  • Added String.trim_charset.
  • Added array @reduce, @filter, @any, @all, @sum, @product, and @indices_of macros.
  • String.bformat has reduced overhead.
  • Supplemental roundeven has a normal implementation.

Want To Dive Into C3?

Check out the documentation or download it and try it out.

Have questions? Come and chat with us on Discord.

C3 0.7.4 Released: Enhanced Enum Support and Smarter Error Handling

C3 version 0.7.4 brings a substantial set of improvements focused on better enum support, enhanced error handling, improved debugging capabilities, and numerous quality-of-life improvements. And we're now also releasing binaries for OpenBSD! Here's a comprehensive look at what's new.

Major Language Features

Enhanced Enum Support

One of the most significant changes in 0.7.4 is the introduction of const enums. - Const enums: You can now declare enum Foo : const, which behaves like C enums but supports any underlying type - Direct enum casting: Casting to and from regular enums is now possible again without needing .ordinal and .from_ordinal methods - Deprecation warning: Inline associated enum values are now deprecated (use --use-old-enums to maintain compatibility)

New Error Handling Macros

C3 0.7.4 introduces two useful new macros for streamlined error handling: - @try: Takes an lvalue and returns a void?. It assigns the value if successful, otherwise returns the error. - @try_catch: Extended version that takes an lvalue, an expression, and the expected error value, it will return a bool?.

These macros can significantly reduce boilerplate when working with if-catch.

Compile-Time Improvements

Several compile-time features have been enhanced: - $typeof improvements: Now returns compile-time types in more contexts - @is_const introduction: This macro replaces the deprecated $is_const, and is based on $defined - Multiline contract comments: Contract documentation can now span multiple lines

Developer Experience Enhancements

Better Error Messages and Debugging

C3 0.7.4 continues the focus on improving the developer experience: - Improved error messages: Better reporting for missing enum qualifiers, struct initialization errors, and unsigned-to-signed conversion issues - Common mistake detection: The compiler now catches accidental foo == BAR; where foo = BAR; was likely intended - Enhanced debugging output: Added --echo-prefix for customizing $echo statement prefixes with {FILE} and {LINE} support

Assembly and Low-Level Development

  • --list-asm flag: View all supported assembly instructions
  • Unaligned access detection: The compiler now checks for unaligned array access
  • Thread synchronization: New thread::fence function provides thread fencing capabilities

Build System Improvements

  • Smarter output directories: Projects now output to out by default, with temp folders used for command-line builds
  • Absolute path support: $embed now accepts absolute paths
  • Math library auto-linking: libc math functions are now only linked when math functions are used.
  • Author field improvements: The project authors field can now be accessed using env::AUTHORS and env::AUTHOR_EMAILS.

Performance and Memory Management

Hash Function Additions

The standard library now includes several new high-performance hash functions: - komihash - a5hash - metrohash64 and metrohash128 - wyhash2 variants - SipHash family of keyed PRFs

Memory Management Improvements

  • Virtual memory: New virtual memory library and arena allocator
  • Smaller memory limits: Support for even more restrictive memory environments
  • Arena allocator fixes: Resolved resize bugs in ArenaAllocator, DynamicArenaAllocator, and BackedArenaAllocator
  • No temp allocator in backtrace: Removed temp allocator usage in backtrace printing.

Standard Library Enhancements

Cryptography and Security

  • Whirlpool hash: Added the Whirlpool cryptographic hash function
  • Ed25519: Elliptic curve digital signature algorithm support

String and I/O Operations

  • string::bformat: New binary formatting capabilities
  • Enhanced formatting: The format option now supports pointers %h
  • String case conversion: Functions for converting between snake_case and PascalCase

Concurrency Improvements

  • Condition variable enhancements: Added ConditionVariable.wait_until and ConditionVariable.wait_for
  • Stream I/O: New readline_to_stream function for stream-based input

Experimental Features

  • Reference counting: Ref and RefCounted experimental types
  • Memory safety types: Volatile and UnalignedRef generic types for typesafe volatile and unaligned references.

Bug Fixes and Stability

Version 0.7.4 includes over 40 bug fixes addressing: - Platform-specific issues (Android, Windows, POSIX) - Compile-time constant evaluation - Memory management edge cases - Type system consistency - Code generation improvements

Notable fixes include proper handling of null ZString comparisons, correct bounds checking for const ranges, and fixes to complex number operations.

Deprecations and Migration

Several features have been deprecated and to streamline the language: - $is_const@is_const - $assignable@assignable_to - allocator::heap()mem and allocator::temp()tmem - Inline associated enum values (use --use-old-enums for compatibility)

Diving deeper into the new const enums

C3 introduced ordinal based enums in 0.3.0. This allowed the language to support associated enum values and enum → string conversions at runtime with zero overhead. The downside was the lack of support for C enums with gaps. While possible to address with constants or associated values, it lacked the ease of C enums. However, C enums could not be made to properly support things the same things as the 0.3 C3 enums did.

The new "const enum" solves this problem. It works like a distinct type, with constants associated with it. It is defined like an enum, with const added after :. Its behaviour is the same as you would expect from C:

enum ConstEnum : const short
{
    ABC = 1,
    BDE,     // Implicitly 2
    DEF = 100
}

But the const enums can do things C enums can't do:

enum Greeting : const inline String
{
    HELLO = "Hello",
    WORLD = "World",
    YO    = "Yo"
}

fn void main()
{
    // Prints "Hello"
    io::printn(Greeting.HELLO); 
    // Inline allows implicit conversion to String
    io::printn(Greeting.YO == "Yo");
    // Inference works like for regular enums 
    Greeting g = WORLD; 
}

:::note[Note On reflection] The cost we pay for this is that we cannot get the ordinal, nor the name, so neither Greeting.YO.nameof nor Greeting.YO.ordinal will work. :::

The new @try and @try_catch

Usually in C3, implicit unwrapping is used to convert an Optional to a normal value:

fn void test1()
{
    int? f = foo();
    if (catch err = f)
    {
        /* do something */
        return;
    }
    /* f is unwrapped here */
}

However, sometimes the assignment may involve an already existing value, in which case we need a temporary:

fn void test2_old()
{
    int f = abc();
    while (some_condition())
    {
        /* use f */
        // f = foo(); <- we can't do this
        int? temp = foo();
        if (catch err = temp)
        {
            /* do something */
            return;
        }
        f = temp;
        /* continue */
    }
}

Having to introduce the temp variable isn't always ideal, and this is where @try comes in. It conditionally sets a variable if the value isn't Empty and otherwise returns the error. This allows us to rewrite the code without a temporary like this:

fn void test2_new()
{
    int f = abc();
    while (some_condition())
    {
        /* use f */
        if (catch err = @try(f, foo()))
        {
            /* do something */
            return;
        }
        /* continue */
    }
}

Another situation is when you only want to change a value if it's a non-Empty, it can also be improved:

fn void update_old()
{
    if (try temp = foo()) my_global = temp;
}
fn void update_new()
{
    (void)@try(my_global, foo());
}

The @try_catch works similar to @try but is useful when you have one expected fault and the other faults should be rethrown. It will also conditionally set a variable, but will return a bool? which is false when the variable is set, true if the expected fault is caught, or an Empty otherwise.

fn void? test3_old()
{
    while (true)
    {
        String? s = next_string();
        if (catch err = s)
        {
            if (err == io::EOF) break;
            return err?;
        }
        use_string(s);
    }
}

fn void? test3_new()
{
    while (true)
    {
        String s;
        if (@try_catch(s, next_string(), io::EOF)!) break;
        use_string(s);
    }
}

TLDR;

C3 0.7.4 introduces improved enum support, new error handling macros, and several enhancements to the developer experience. Key additions include const enums with flexible underlying types, direct enum casting, and streamlined error-handling via @try and @try_catch. The release also brings better error messages, improved debugging tools, and expanded standard library features such as new hash functions, Ed25519 support, and virtual memory management. Numerous bug fixes and deprecations aim to improve language consistency and performance. This version marks continued progress toward language maturity and usability.

Change Log

Click for full change log

Changes / improvements

  • Added const enums: enum Foo : const. Behaves like C enums but may be any type.
  • Casting to / from an enum is now possible again. No need to use .ordinal and .from_ordinal.
  • Inline associated enum values are deprecated, use --use-old-enums to re-enable them.
  • $typeof may return a compile time type.
  • Improved error messages on missing qualifier on enum value. #2260
  • Add --echo-prefix to edit the prefix with $echo statements. Supports {FILE} and {LINE}
  • Catch accidental foo == BAR; where foo = BAR; was most likely intended. #2274
  • Improve error message when doing a rethrow in a function that doesn't return an optional.
  • Add --list-asm to view all supported asm instructions.
  • Formatting option "%h" now supports pointers.
  • Improve error on unsigned implicit conversion to signed.
  • Update error message for struct initialization #2286
  • Add SipHash family of keyed PRFs. #2287
  • $is_const is deprecated in favour of @is_const based on $defined.
  • Multiline contract comments #2113
  • Removed the use of temp allocator in backtrace printing.
  • env::AUTHORS and env::AUTHOR_EMAILS added.
  • Suppress codegen of panic printing with when panic messages are set to "off".
  • Implicit linking of libc math when libc math functions are used.
  • Allow even smaller memory limits.
  • Check unaligned array access.
  • Add "@structlike" for typedefs.
  • "poison" the current function early when a declaration can't be correctly resolved.
  • Add komihash, a5hash, metrohash64, metrohash128, and wyhash2 variants with tests/benchmark. #2293
  • '$assignable' is deprecated.
  • Deprecate allocator::heap() and allocator::temp()
  • Add thread::fence providing a thread fence.
  • Place output in out by default for projects. Use temp folder for building at the command line.
  • Allow absolute paths for $embed.
  • Add @try and @try_catch.
  • Assignment evaluation order now right->left, following C++17 and possibly C23.

Fixes

  • mkdir/rmdir would not work properly with substring paths on non-windows platforms.
  • Hex string formatter check incorrectly rejected slices.
  • Correctly reject interface methods type and ptr.
  • Comparing a null ZString with a non-null ZString would crash.
  • Switch case with const non-int / enum would be treated as ints and crash. #2263
  • Missing bounds check on upper bound with const ranges foo[1:3].
  • Check up the hierarchy when considering if an interface cast is valid #2267.
  • Fix issue with labelled break inside of a $switch.
  • Non-const macros may not return untyped lists.
  • $for ct-state not properly popped.
  • Inline r / complex for complex numbers fixed.
  • Const slice lengths were not always detected as constant.
  • Const slice indexing was not bounds checked.
  • Initialize pool correctly in print_backtrace.
  • --max-mem now works correctly again.
  • Casting a fault to a pointer would trigger an assert.
  • Make to_float more tolerant to spaces.
  • Fixes to thread local pointer handling.
  • Fixes to JSON parsing and Object.
  • Array indices are now using int64 internally.
  • Bit shift operation fails with inline uint enum despite matching underlying type #2279.
  • Fix to codegen when using a bitstruct constant defined using a cast with an operator #2248.
  • Function pointers are now compile time constants.
  • Splat 8 arguments can sometimes cause incorrect behaviour in the compiler. #2283
  • Correctly poison the analysis after a failed $assert or $error. #2284
  • $foo variables could be assigned non-compile time values.
  • $foo[0] = ... was incorrectly requiring that the assigned values were compile time constants.
  • "Inlined at" would sometimes show the current location.
  • Fixed bug splatting constants into constants.
  • Resize bug when resizing memory down in ArenaAllocator, DynamicArenaAllocator, BackedArenaAllocator.
  • Error message for missing arg incorrect for methods with zero args #2296.
  • Fix stringify of $vaexpr #2301.
  • Segfault when failing to cast subexpression to 'isz' in pointer subtraction #2305.
  • Fix unexpected display of macro definition when passing a poisoned expression #2305.
  • @links on macros would not be added to calling functions.
  • Fix Formatter.print returning incorrect size.
  • A distinct type based on an array would yield .len == 0
  • Overloading addition with a pointer would not work.
  • Copying const enums and regular enums incorrect #2313.
  • Regression: Chaining an optional together with contracts could in some cases lose the optional.
  • char[*] b = *(char[*]*)&a; would crash the compiler if a was a slice. #2320
  • Implicitly cast const int expressions would sometimes not be detected as compile time const.
  • Using @noreturn in a short body macro would not work properly #2326.
  • Bug when reporting error in a macro return would crash the compiler #2326.
  • Short body return expression would not have the correct span.
  • Fix issue where recursively creating a dir would be incorrectly marked as a failure the first time.
  • @format did not work correctly with macros #2341.
  • Crash when parsing recursive type declaration #2345.
  • Remove unnecessary "ret" in naked functions #2344.
  • Lambdas now properly follow its attributes #2346.
  • Not setting android-ndk resulted in a "set ndk-path" error.
  • Lambda deduplication would be incorrect when generated at the global scope.
  • Disallow accessing parameters in a naked function, as well as return, this fixes #1955.
  • Assigning string literal to char[<*>] stores pointer rather than characters. #2357

Stdlib changes

  • Improve contract for readline. #2280
  • Added Whirlpool hash.
  • Added Ed25519.
  • Added string::bformat.
  • Virtual memory library.
  • New virtual emory arena allocator.
  • Added WString.len.
  • Added @addr macro.
  • Add ConditionVariable.wait_until and ConditionVariable.wait_for
  • Added readline_to_stream that takes a stream.
  • Added Ref and RefCounted experimental functionality.
  • Added Volatile generic type.
  • Added UnalignedRef generic type.
  • Add String conversion functions snake_case -> PascalCase and vice versa.

Want To Dive Into C3?

Check out the documentation or download it and try it out.

Have questions? Come and chat with us on Discord.

How C3 Manages Memory Without Leaks, GC, or Borrow Checkers

Modern languages offer a variety of techniques to help with dynamic memory management, each one a different tradeoff in terms of performance, control and complexity. In this post we'll look at an old idea, memory allocation regions or arenas, implemented via the C3 Temp allocator, which is the new default for C3.

The Temp allocator combines the ease of use of garbage collection with C3's unique features to give a simple and (semi)-automated solution within a manual memory management language. The Temp allocator helps you avoid memory leaks, improve performance, and simplify code compared to traditional approaches.

Memory allocations come in two broad types stack allocations which are compact, efficient and automatic and heap allocations which are much larger and have customisable organisation. Custom organisation allows both innovation and footguns in equal measure, let's explore those.

Memory Leaks

When we dynamically allocate memory, with say malloc() typically we need to free() it afterwards, otherwise we can't use that memory until the OS process exits. If that memory isn't being used and it has not been freed this is called a memory leak and can lead to restricting or running out of available system memory.

Avoiding Memory Leaks

Common solutions are RAII, reference counting or garbage collection which automatically free those variables.

Each method has different tradeoffs. - RAII needs classes or structs to manage their own memory cleanup with a lot of extra code. - Reference counting counts how many users each memory allocation has, when this hits zero the memory is freed. Reference counting is expensive with multiple CPU cores as we need to synchronise these counts and share information between cores. - Garbage collection competes with the program for both memory and CPU time, reducing program performance and increasing memory usage.

Memory Allocation Regions

Memory allocation regions, go by various names: arenas, pools, contexts; The idea dates back to the 1960's with the IBM OS/360 mainframes having a similar system. Memory regions are efficient for managing many memory allocations and can be freed in a single operation, and are particularly effective when we know the memory will not be needed later. That is, we know the memory's lifetime. This idea is powerful and used in many applications like web server request handlers or database transactions, as used by the Apache webserver and the Postgres database.

Memory allocation regions use a single buffer so have good locality because all the allocations are closely associated together, making it more efficient for CPU access, compared to traditional malloc where allocations are spread throughout the heap.

Memory allocation regions may make it easier to manage memory, however you still need to remember to free them, and if you forget to do call free, then that memory will still leak.

Enter The Temp Allocator

The Temp allocator in C3 is a region based allocator which is automatically reset once execution leaves its scope, so you cannot forget to free the memory and can't leak memory. The Temp allocator in C3 is a builtin in the standard library, called @pool() and using it you can define the scope where the allocated variables are available, for example:

fn int example(int input) 
{
    @pool()
    {
        // Allocate temp_variable on the heap with Temp allocator
        int* temp_variable = mem::tnew(int);
        *temp_variable = 56;
        input = input + temp_variable;
        return input;
    }; // Automatically cleanup temp_variable
} 

fn void main()
{
    int result = example(1);
    assert(result == 57, "The result should be 57");
}

Check With Valgrind

Valgrind is a tool which detects memory leaks and we can use it to show the temp allocator managed the memory for us automatically.

valgrind ./pool_example |& grep "All heap blocks were freed"
==129129== All heap blocks were freed -- no leaks are possible

Success!

We have relatively performant memory allocations managed automatically without needing RAII, garbage collection or reference counting.

Controlling Variable Cleanup

Normally temp allocated variables are cleaned up at the end of the closest @pool scope, but what if you have nested @pool and want explicit control over when it's cleaned up? Simple: assign the temp allocator with the scope you need to a variable, and use it explicitly. The temp allocator in a scope is a global variable called tmem.

fn String* example(int input)
{
    // previous global temp allocator from main() scope
    Allocator temp_allocator = tmem;

    @pool()
    {
        // Allocate on the global temp allocator
        String* returned = allocator::new(temp_allocator, String);
        *returned = string::format(temp_allocator, "modified %s", input);
        return returned;
    };
}

fn void main()
{
    // top-most temp allocator, tmem created here
    @pool()
    {
        String* returned = example(42);
        // "modified 42" string returned
        io::printn(*returned);
    };
}

A Handy Shorthand

We can reduce the code's nesting using short function declaration syntax => making it even simpler as:

fn int example(int input) => @pool(reserve: 2048)
{
    // Allocate temp_variable on the heap 
    int* temp_variable = mem::tnew(int);
    *temp_variable = 56;
    input = input + *temp_variable;
    return input;
}

In Simple Cases Omit @pool()

Happy with the defaults? We can actually omit the @pool() in main() all together!

The compiler automatically adds a @pool() scope to the main() function for us, once it finds a temp allocation function like mem::tnew(), without an enclosing @pool() scope. That simplifies our code to:

fn int example(int input)
{
    // Allocate temp_variable on the heap
    // @pool() temp allocator created for us by the compiler
    int* temp_variable = mem::tnew(int);
    *temp_variable = 56;
    input = input + *temp_variable;
    return input;
}

Conclusion

The Temp allocator has landed in C3; combining scoped compile time known memory lifetimes, ease of use and performance. Tying memory lifetimes to scope and automating cleanup, you get the benefits of manual control without the risks. No more memory leaks, use after free, or slow compile times with complex ownership tracking.

Whether you're building low-latency systems or just want more explicit code without garbage collection overhead, the C3 Temp allocator is a powerful tool to have in your arsenal, making memory management nearly effortless.

Want To Dive Into C3?

Check out the documentation or download it and try it out.

Have questions? Come and chat with us on Discord.

C3 0.7.3 - Small Improvements

This month's release is 0.7.3, and is about gradual improvements to the language. Some parts of a language will always have less use than others, and that is true for C3 as well. For C3 it's mostly the compile time parts that reveal their rough edges fairly late, but they need polish as well.

Type / typeid

Types can be introspected an manipulated in two ways in C3. One is to just use the type directly, like in Type.sizeof. The other is to use the typeid value, to get the same thing: Type.typeid.sizeof. However, they have differences: a typeid value can never be in the position of a type, and only compile time constant typeid has the full range of values:

typeid t = int.typeid;
const typeid T = int.typeid;
...
// Getting the size, this works for all
int.sizeof;     // Compile time value
T.sizeof;       // Compile time value
t.sizeof;       // Runtime value
// But others doesn't
int.max;        // Direct type access ok!
T.max;          // Const typeid access ok!
t.max;          // ERROR! Runtime access is not ok!
// Using it as a type in an assignment:
int a;          // Ok of course
T a;            // ERROR!
$typefrom(T) a; // Ok! Converts a typeid to a type.
$typefrom(t) a; // ERROR! Must be a compile time typeid

For macros that had type arguments they usually looked like this:

macro new_thing($Type) { ... }
...

Foo* f = new_thing(Foo);

The disadvantage was that if you had a constant typeid you'd first have to convert it to a type:

const typeid T = Foo.typeid;

void* thing = new_thing($typefrom(T));

Starting from 0.7.3 though, we can use the constant typeid directly and get an implicit conversion to a type:

const typeid T = Foo.typeid;

void* thing = new_thing(T);  // New in 0.7.3

The reason this is useful, is because constant macros can't return a type directly BUT can return a typeid. So maybe you have macro code like this:

typeid $type = @select_the_type($foo);

void* thing = new_thing($typefrom($type));

It can now be this instead:

typeid $type = @select_the_type($foo);

void* thing = new_thing($type);

And contracting the code it's even more of an improvement:

void* thing = new_thing($typefrom(@select_the_type($foo))); // 0.7.2
void* thing = new_thing(@select_the_type($foo));            // 0.7.3

It's just overall clearer to drop the superfluous $typefrom.

This also extends to the $assignable builtin, which also now takes either a type or a typeid.

Removing $evaltype

Also related to typeid and types is the deprecation of $evaltype. It takes a constant string and turns it into a type, so you could write:

String $name = "foo";
$evaltype($name) x = 1;

In order to simplify the language $evaltype is getting removed but the functionality is still there, because $typefrom can now also take a string.

String $name = "foo";
$typefrom($name) x = 1; // New in 0.7.3

This hopefully reduces the amount of different constructs it's necessary to know in order to understand advanced compile time code generation.

Improved type inference

C3 has type inference for initializers:

fn void test(int[3] x)
{ ... }

test({1, 2, 3});

However, this did not extend to &&:

// 0.7.2
fn void test(int[3]* x)
{ ... }

test(&&{1, 2, 3});      // ERROR!

This has now been improved:

// 0.7.3
fn void test(int[3]* x)
{ ... }

test(&&{1, 2, 3});      // Works in 0.7.3

Compile time @sprintf

Creating strings at compile time is sometimes desirable, but the help for it has been complicated an inefficient. Usually the use of +++ has been needed:

String $s = $foo +++ ":" +++ $bar;

Now, however, there is a @sprintf. It's not a full-fledged printf-like function. It only supports "%s" arguments, but will do a best effort to convert any compile time value:

int $v = 123;
Enum $x = FOO;
String $s = @sprintf("%s:%s", $v, $x); // $s = "123:FOO"

Often this is good enough, and the operation is much cheaper than the other compile time concatenation in terms of compiler resources.

$assert and $error "printf"

Hand-in-hand with the @sprintf, $assert and $error now accepts the same format as @sprintf for better compile time errors:

var $a = -123;
$assert $a > 0 : "$a was %s", $a;
// Above is a compile time error printing "$a was -123"

Better $eval

Where $evaltype is going away, $eval, which can turn a string into an identifier has been improved to properly work with @foo, #foo and $foo identifiers.

More liberal "main"

The main function used to only support String[] or int argc, char **argv as parameters. Now it accepts distinct types that lowers to the same arguments, so int argc, ZString* argv works properly.

--sources to add additional files to compile

Sometimes it's useful to add extra sources to compile with a regular build. --sources allows you to do exactly that:

c3c build --sources inject.c3

Improved overloading with wildcard types

One omission of the arithmetic overloads was supporting macros with wildcard types, so rather than writing a macro Foo Foo.add(self, int x) @operator(+) for every type you wanted to support, what if you could write macro Foo Foo.add(self, x) @operator(+) and handle the rest with contracts and compile time checks?

This actually crashed in 0.7.2, but in 0.7.3 this works correctly. It's even allowed with @operator_r and @operator_s variants.

Distinct improvements

Bitstructs can now be based on a distinct type, created by typedef:

typedef Test = int;
bitstruct Foo : Test // Works in 0.7.3
{
    int z : 1..3;
}

And it's now possible for generics to be generic over distinct const values:

module foo { VAL };
const GLOBAL = VAL;

module test;
import foo;
typedef Bar = int;

fn void main()
{
    const Bar B = 123;
    Bar b = foo::GLOBAL{B}; // Works in 0.7.3
}

Constant vector comparison

Vectors has been comparable at runtime, but not at compile time. However, from 0.7.3 and onwards this works:

int[<2>] $v = { 1, 2 };
int[<2>] $w = { 1, 2 };
$assert $v == $w;

Silence deprecation

@deprecated has been mostly used as a migration mechanism for the c3 standard library, but using it with user code requires more fine-grained control, which is why there is now an @allow_deprecated attribute to silence deprecation warnings inside a particular function:

fn void old() @deprecated {}

fn void test1()
{
    old(); // Emits warning
}

fn void test2() @allow_deprecated
{
    old(); // No warning
}

Removal of '\f' as whitespace

\f (the form-feed character) is not really used in normal text, so it has been removed from the characters recognized as whitespace by the lexer.

$memberof get/set improvements

You can get the member of a struct or union by indexing Type.membersof and then use the get method on the member to retrieve the value. However, there was no corresponding set method, instead you had to do *&$member.get(2) = 123. People have been asking for a .set for a long time, and that was also the only way to properly support setting bitstruct values:

bitstruct Foo : int
{
    int x : 1..5;
    int y : 10..14;
}
fn void test()
{
    var $member = Foo.membersof[1];
    Foo f = { 1, 3 };
    int val = $member.get(f);        // .get on bitstructs now works
    $member.set(f, val + 1);         // .set method updates
    io::printn(f.y);                 // Prints 4
}

Custom file extensions in build targets

It's now possible to override the extension used for libraries and executables of a target with the "extension" project property.

Bug fixes

0.7.3 shipped with around 40 bug fixes, which is a bit below average for a 0.0.1 release.

Stdlib additions

ZString types can now be compared using == by having equality overloaded. We have a SHA512 library and String.escape, String.unescape for escaping and unescaping strings. io::struct_to_format also now supports bitstructs, which means io::printn and related functions properly prints bitstructs.

Stdlib deprecations

String.is_zstr and String.quick_zstr are unsafe and are deprecated is_array_or_slice_of_char and is_arrayptr_or_slice_of_char are replaced by constant @ variants.

Change Log

Click for full change log

Changes / improvements

  • $typefrom now also accepts a constant string, and so works like $evaltype.
  • $evaltype is deprecated in favour of $typefrom.
  • Literal rules have changed, this makes -0xFF now a signed integer.
  • Implicitly convert from constant typeid to Type in $Type assignment, and $assignable.
  • Make $Type parameters accept constant typeid values.
  • Deprecate foo.#bar.
  • Allow inference across && #2172.
  • Added support for custom file extensions in project.json targets.
  • $eval now also works with @foo, #foo, $Foo and $foo parameters #2114.
  • @sprintf macro (based on the $$sprintf builtin) allows compile time format strings #1874.
  • Improve error reports when encountering a broken "if-catch".
  • Add printf format to $assert and $error #2183.
  • Make accepting arguments for main a bit more liberal, accepting main(int argc, ZString* argv)
  • Make $echo and @sprintf correctly stringify compile time initializers and slices.
  • Add --sources build option to add additional files to compile. #2097
  • Support untyped second argument for operator overloading.
  • The form-feed character '\f' is no longer valid white space.
  • Show code that caused unreachable code #2207
  • Allow generics over distinct types #2216.
  • Support distrinct types as the base type of bitstructs. #2218
  • Add hash::sha512 module to stdlib. #2227
  • Compile time type assignment (eg $Foo = int) is no longer an expression.
  • Add @allow_deprecated attribute to functions to selectively allow deprecated declarations #2223.
  • Improve error message on pointer diff #2239.
  • Compile-time comparison of constant vectors. #1575.
  • $member.get supports bitstructs.
  • $member.set for setting members without the *& trick.
  • Initial support for #1925, does not affect C compilation yet, and doesn't try to link etc. Using "--emit-only"

Fixes

  • -2147483648, MIN literals work correctly.
  • Splatting const slices would not be const. #2185
  • Fixes to $define handling of binary ops.
  • Fixes methodsof to pick up all sorts of extension methods. #2192
  • --lsp sometimes does not emit end tag #2194.
  • Improve Android termux detection.
  • Update Android ABI.
  • Fixes to @format checking #2199.
  • Distinct versions of builtin types ignore @operator overloads #2204.
  • @operator macro using untyped parameter causes compiler segfault #2200.
  • Make unreachable() only panic in safe mode.
  • cflags additions for targets was not handed properly. #2209
  • $echo would suppress warning about unreachable code. #2205
  • Correctly format '%c' when given a width. #2199
  • Fix to is_array_or_slice_of_char #2214.
  • Method on array slice caused segfault #2211.
  • In some cases, the compiler would dereference a compile time null. #2215
  • Incorrect codegen if a macro ends with unreachable and is assigned to something. #2210
  • Fix error for named arguments-order with compile-time arguments #2212
  • Bug in AST copying would make operator overloading like += compile incorrectly #2217.
  • $defined(#expr) broken with binary. #2219
  • Method ambiguity when importing parent module publicly in private submodule. #2208
  • Linker errors when shadowing @local with public function #2198
  • Bug when offsetting pointers of large structs using ++ and --.
  • x++ and x-- works on pointer vectors #2222.
  • x += 1 and x -= 1 works properly on pointer vectors #2222.
  • Fixes to x += { 1, 1 } for enum and pointer vectors #2222.
  • Linking fails on operator method imported as @public #2224.
  • Lambda C-style vaargs were not properly rejected, leading to crash #2229.
  • Incorrect handling of constant null fault causing compiler crash #2232.
  • Overload resolution fixes to inline typedef #2226.
  • math::overflow_* wrappers incorrectly don't allow distinct integers #2221.
  • Compiler segfault when using distinct type in attribute imported from other module #2234.
  • Assert casting bitstruct to short/char #2237.
  • @tag didn't work with members #2236.
  • Assert comparing untyped lists #2240.
  • Fix bugs relating to optional interface addr-of #2244.
  • Compiler null pointer when building a static-lib with -o somedir/... #2246
  • Segfault in the compiler when using a bitstruct constant defined using a cast with an operator #2248.
  • Default assert() message drops parens #2249.

Stdlib changes

  • Deprecate String.is_zstr and String.quick_zstr #2188.
  • Add comparison with == for ZString types.
  • is_array_or_slice_of_char and is_arrayptr_or_slice_of_char are replaced by constant @ variants.
  • @pool now has an optional reserve parameter, some minor changes to the temp_allocator API
  • io::struct_to_format now supports bitstructs.
  • Add String.escape, String.unescape for escaping and unescaping a string.

Want To Dive Into C3?

Check out the documentation or download it and try it out.

Have questions? Come and chat with us on Discord.

Discuss this article on Reddit or Hacker News.

C3 0.7.2 - Quality Of Life

Unlike 0.7.1, 0.7.2 is not about big new features, instead it is laser focused on adding quality of life improvements, which are backwards compatible with other 0.7 releases.

Compile time additions

:::note[Note on compile time] C3 has visual differentiation between compile time and runtime code, to make it explicit when the code will be run.

Compile time code uses $if condition: ... $endif for conditional logic. Learn more about macros and compile time evaluation. :::

Simplifying compile time evaluated code

Setting a variable at compile time can now be simplified using compile time logical or ||| and compile time logical and &&& with a variable right hand expression.

The original code to set a variable at compile time:

$if FOO:
    bool x = true;
$else
    bool x = foo();
$endif

Now this compile time variable can be set with compile time logical or ||| compactly as:

bool x = FOO ||| foo();

You can also use compile time logical and &&& in a similar way.

Compile time random: @rnd

Sometimes it can be useful to create unique ids at compile time. This is now possible with the @rnd macro.

Compile time ceil: math::@ceil

It is somewhat hard to write a good compile time ceil function, so 0.7.2 adds a builtin which is accessible using the math::@ceil macro.

General additions

Set the run directory

To make c3c run and c3c compile-run more convenient, it's now possible to use --run-dir (or the project setting run-dir) to set the director from which the compiler runs the resulting binary.

Limits to SIMD sizes

Limitations of vectors are sometimes misunderstood, they will cause code bloat if used for large vectors. For this reason a max vector size has been introduced. By default this is 4096 bits, so basically the size of double[<64>]. It is possible to increase this using --max-vector-size as needed. (As a comparison, the biggest SIMD vectors on x64 is 512 bits, so such a 4096 bit vector would be represented by 8 registers).

has_tagof works on builtin types

While has_tagof will always return false on builtin types, this change nonetheless simplifies writing compile time code involving tags.

Allow recursive generic modules

To simplify generic module resolution it was not possible to recursively generate generic module. This restriction has been lifted in 0.7.1.

Deprecations

Bitsize suffix deprecations

The bitsize suffixes are deprecated, so rather than writing 23u64 use the C style 23UL instead. u128 and i128 suffixes are replaced by ULL and LL suffixes.

Finally, the d suffix for doubles have been added as a complement to f.

Compile time reflection deprecations

MyEnum.elements have been deprecated. Use MyEnum.len instead. SomeFn.params have been deprecated. Use SomeFn.paramsof instead.

Deprecation for old @param docs

By 0.7.1 the declaration style @param foo "abc" would be allowed rather than @param foo : "abc". This was by accident. It's now properly deprecated.

Generic faults are not allowed

Creating faults that are parameterized is usually a mistake and should not have been allowed. It's been completely removed in 0.7.2 as it was classified as a bug.

This is no longer allowed:

module Foo {Type};
typedef BAZ, HELLO;

Faults should be defined in a non-generic module, such as a parent module or sub-module instead.

Fixes

This release contains about 30 different fixes, most of them newly discovered and not regressions.

They range from fixes to advanced generic types to stdlib bugs.

Stdlib updates

Currently a new version of the matrix library is incubating, and while it didn't make it for the 0.7.2 release, I hope it can be included by 0.7.4 and contributions are welcome!

Optimized String -> ZString conversions

:::note[Note On Strings] Default C3 string: typedef String = inline char[];
C compatible, null terminated: typedef ZString = inline char*;

Learn more about strings. :::

Sometimes a String is already pointing to a valid ZString, so no copy is needed. To check this, String gets two new methods .is_zstr to check if the String is also zero terminated, and .quick_zstr which will create a temp ZString if needed, but otherwise use the String.

std::ascii moves into std::core

std::ascii moved into std::core::ascii. Old _m variants are deprecated, as is uint methods.

Better String tokenization

It's now possible to further customize tokenization, to ignore empty elements in the middle (or not), ignore any last empty elements at the end (or not). This introduces .tokenize_all which replaces the now deprecated .splitter method.

Count and replace

String further gets some conveniences: .count to count the number of instances of a string within the string and replace / treplace to return a new String with a substring replaced. This functionality was already in DString, but was now added to String as well.

Operator overloads for std::time and Maybe

Duration * Int, Clock - Clock and DateTime + Duration overloads were added for manipulating time.

For Maybe, the == operator is available when the inner type is equatable.

Subprocess improvements

Subprocess added an inherit_stdio option to inherit the parent's stdin, stdout, and stderr instead of creating pipes.

Change Log

Click for full change log
Changes / improvements
  • Better default assert messages when no message is specified #2122
  • Add --run-dir, to specify directory for running executable using compile-run and run #2121.
  • Add run-dir to project.json.
  • Add quiet to project.json.
  • Deprecate uXX and iXX bit suffixes.
  • Add experimental LL / ULL suffixes for int128 and uint128 literals.
  • Allow the right hand side of ||| and &&& be runtime values.
  • Added @rnd() compile time random function (using the $$rnd() builtin). #2078
  • Add math::@ceil() compile time ceil function. #2134
  • Improve error message when using keywords as functions/macros/variables #2133.
  • Deprecate MyEnum.elements.
  • Deprecate SomeFn.params.
  • Improve error message when encountering recursively defined structs. #2146
  • Limit vector max size, default is 4096 bits, but may be increased using --max-vector-size.
  • Allow the use of has_tagof on builtin types.
  • @jump now included in --list-attributes #2155.
  • Add $$matrix_mul and $$matrix_transpose builtins.
  • Add d as floating point suffix for double types.
  • Deprecate f32, f64 and f128 suffixes.
  • Allow recursive generic modules.
  • Add deprecation for @param foo "abc".
  • Add --header-output and header-output options for controlling header output folder.
  • Generic faults is disallowed.
Fixes
  • Assert triggered when casting from int[2] to uint[2] #2115
  • Assert when a macro with compile time value is discarded, e.g. foo(); where foo() returns an untyped list. #2117
  • Fix stringify for compound initializers #2120.
  • Fix No index OOB check for [:^n] #2123.
  • Fix regression in Time diff due to operator overloading #2124.
  • attrdef with any invalid name causes compiler assert #2128.
  • Correctly error on @attrdef Foo = ;.
  • Contract on trying to use Object without initializing it.
  • Variable aliases of aliases would not resolve correctly. #2131
  • Variable aliases could not be assigned to.
  • Some folding was missing in binary op compile time resolution #2135.
  • Defining an enum like ABC = { 1 2 } was accidentally allowed.
  • Using a non-const as the end range for a bitstruct would trigger an assert.
  • Incorrect parsing of ad hoc generic types, like Foo{int}**** #2140.
  • $define did not correctly handle generic types #2140.
  • Incorrect parsing of call attributes #2144.
  • Error when using named argument on trailing macro body expansion #2139.
  • Designated const initializers with {} would overwrite the parent field.
  • Empty default case in @jump switch does not fallthrough #2147.
  • &&& was accidentally available as a valid prefix operator.
  • Missing error on default values for body with default arguments #2148.
  • --path does not interact correctly with relative path arguments #2149.
  • Add missing @noreturn to os::exit.
  • Implicit casting from struct to interface failure for inheriting interfaces #2151.
  • Distinct types could not be used with tagof #2152.
  • $$sat_mul was missing.
  • for with incorrect var declaration caused crash #2154.
  • Check pointer/slice/etc on [out] and & params. #2156.
  • Compiler didn't check foreach over flexible array member, and folding a flexible array member was allowed #2164.
  • Too strict project view #2163.
  • Bug using #foo arguments with $defined #2173
  • Incorrect ensure on String.split.
  • Removed the naive check for compile time modification, which fixes #1997 but regresses in detection.
Stdlib changes
  • Added String.quick_ztr and String.is_zstr
  • std::ascii moved into std::core::ascii. Old _m variants are deprecated, as is uint methods.
  • Add String.tokenize_all to replace the now deprecated String.splitter
  • Add String.count to count the number of instances of a string.
  • Add String.replace and String.treplace to replace substrings within a string.
  • Add Duration * Int and Clock - Clock overload.
  • Add DateTime + Duration overloads.
  • Add Maybe.equals and respective == operator when the inner type is equatable.
  • Add inherit_stdio option to SubProcessOptions to inherit parent's stdin, stdout, and stderr instead of creating pipes. #2012
  • Remove superfluous cleanup parameter in os::exit and os::fastexit.
  • Add extern fn ioctl(CInt fd, ulong request, ...) binding to libc;

Want To Dive Into C3?

Check out the documentation or download it and try it out.

Have questions? Come and chat with us on Discord.

Gradual improvements: C3 0.7.2

Originally from: https://c3.handmade.network/blog/p/9028-gradual_improvements__c3_0.7.2

It's another months and consequently it's time for a new incremental 0.7 release.

Additions

Unlike 0.7.1, 0.7.2 doesn't really add anything major, but just add a few quality of life enhancements:

||| and &&& with variable right hand side

Rather than writing:

$if FOO:
    bool x = true;
$else
    bool x = foo();
$endif

It's now possible to use ||| to write this compactly as:

bool x = FOO ||| foo();

&&& also supports this.

Compile time random: @rnd

Sometimes it can be useful to create unique ids at compile time. This is now possible with the @rnd macro.

Compile time ceil: math::@ceil

It is somewhat hard to write a good compile time ceil function, so 0.7.2 adds a builtin which is accessible using the math::@ceil macro.

Set the run directory

To make c3c run and c3c compile-run more convenient, it's now possible to use --run-dir (or the project setting run-dir) to set the director from which the compiler runs the resulting binary.

Suffix deprecations

The bitsize suffixes are deprecated, so rather than writing 23u64 use the C style 23UL instead. u128 and i128 suffixes are replaced by ULL and LL suffixes.

Finally, the d suffix for doubles have been added as a complement to f.

Compile time reflection deprecations

MyEnum.elements have been deprecated. Use MyEnum.len instead.

SomeFn.params have been deprecated. Use SomeFn.paramsof instead.

Limits to SIMD sizes

Limitations of vectors are sometimes misunderstood, they will cause code bloat if used for large vectors. For this reason a max vector size has been introduced. By default this is 4096 bits, so basically the size of double[<64>]. It is possible to increase this using --max-vector-size as needed. (As a comparison, the biggest SIMD vectors on x64 is 512 bits, so such a 4096 bit vector would be represented by 8 registers).

has_tagof works on builtin types

While has_tagof will always return false on builtin types, this change nonetheless simplifies writing compile time code involving tags.

Allow recursive generic modules

To simplify generic module resolution it was not possible to recursively generate generic module. This restriction has been lifted in 0.7.1.

Deprecation for old @param docs

By 0.7.1 the declaration style @param foo "abc" would be allowed rather than @param foo : "abc". This was by accident. It's now properly deprecated.

Generic faults are not allowed

Creating faults that are parameterized is usually a mistake and should not have been allowed. It's been completely removed in 0.7.2 as it was classified as a bug.

So no more

module Foo {Type};
typedef BAZ, HELLO;

Faults should be places in a parent or sub-module instead.

Fixes

This release contains about 30 different fixes, most of them newly discovered and not regressions.

They range from fixes to advanced generic types to stdlib bugs.

Stdlib updates

Currently a new version of the matrix library is incubating, and while it didn't make it for the 0.7.2 release, I hope it can be included by 0.7.4.

Optimized String -> ZString conversions

Sometimes a String is already pointing to a valid ZString, so no copy is needed. To check this, String gets two new methods .is_zstr to check if the String is also zero terminated, and .quick_zstr which will create a temp ZString if needed, but otherwise use the String.

std::ascii moves into core

std::ascii moved into std::core::ascii. Old _m variants are deprecated, as is uint methods.

Better String tokenization

It's now possible to further customize tokenization, to ignore empty elements in the middle (or not), ignore any last empty elements at the end (or not). This introduces .tokenize_all which replaces the now deprecated .splitter method.

Count and replace

String further gets some conveniences: .count to count the number of instances of a string within the string and replace / treplace to return a new String with a substring replaced. This functionality was already in DString, but was now added to String as well.

Operator overloads for std::time and Maybe

Duration * Int, Clock - Clock and DateTime + Duration overloads were added for manipulating time.

For Maybe, the == operator is available when the inner type is equatable.

Subprocess improvements

Subprocess added an inherit_stdio option to inherit the parent's stdin, stdout, and stderr instead of creating pipes.

Changelist

The full changelist is here:

### Changes / improvements
- Better default assert messages when no message is specified #2122
- Add `--run-dir`, to specify directory for running executable using `compile-run` and `run` #2121.
- Add `run-dir` to project.json.
- Add `quiet` to project.json.
- Deprecate uXX and iXX bit suffixes.
- Add experimental LL / ULL suffixes for int128 and uint128 literals.
- Allow the right hand side of `|||` and `&&&` be runtime values.
- Added `@rnd()` compile time random function (using the `$$rnd()` builtin). #2078
- Add `math::@ceil()` compile time ceil function. #2134
- Improve error message when using keywords as functions/macros/variables #2133.
- Deprecate `MyEnum.elements`.
- Deprecate `SomeFn.params`.
- Improve error message when encountering recursively defined structs. #2146
- Limit vector max size, default is 4096 bits, but may be increased using --max-vector-size.
- Allow the use of `has_tagof` on builtin types.
- `@jump` now included in `--list-attributes` #2155.
- Add `$$matrix_mul` and `$$matrix_transpose` builtins.
- Add `d` as floating point suffix for `double` types.
- Deprecate `f32`, `f64` and `f128` suffixes.
- Allow recursive generic modules.
- Add deprecation for `@param foo "abc"`.
- Add `--header-output` and `header-output` options for controlling header output folder.
- Generic faults is disallowed.

### Fixes
- Assert triggered when casting from `int[2]` to `uint[2]` #2115
- Assert when a macro with compile time value is discarded, e.g. `foo();` where `foo()` returns an untyped list. #2117
- Fix stringify for compound initializers #2120.
- Fix No index OOB check for `[:^n]` #2123.
- Fix regression in Time diff due to operator overloading #2124.
- attrdef with any invalid name causes compiler assert #2128.
- Correctly error on `@attrdef Foo = ;`.
- Contract on trying to use Object without initializing it.
- Variable aliases of aliases would not resolve correctly. #2131
- Variable aliases could not be assigned to.
- Some folding was missing in binary op compile time resolution #2135.
- Defining an enum like `ABC = { 1 2 }` was accidentally allowed.
- Using a non-const as the end range for a bitstruct would trigger an assert.
- Incorrect parsing of ad hoc generic types, like `Foo{int}****` #2140.
- $define did not correctly handle generic types #2140.
- Incorrect parsing of call attributes #2144.
- Error when using named argument on trailing macro body expansion #2139.
- Designated const initializers with `{}` would overwrite the parent field.
- Empty default case in @jump switch does not fallthrough #2147.
- `&&&` was accidentally available as a valid prefix operator.
- Missing error on default values for body with default arguments #2148.
- `--path` does not interact correctly with relative path arguments #2149.
- Add missing `@noreturn` to `os::exit`.
- Implicit casting from struct to interface failure for inheriting interfaces #2151.
- Distinct types could not be used with tagof #2152.
- `$$sat_mul` was missing.
- `for` with incorrect `var` declaration caused crash #2154.
- Check pointer/slice/etc on `[out]` and `&` params. #2156.
- Compiler didn't check foreach over flexible array member, and folding a flexible array member was allowed #2164.
- Too strict project view #2163.
- Bug using `#foo` arguments with `$defined` #2173
- Incorrect ensure on String.split.
- Removed the naive check for compile time modification, which fixes #1997 but regresses in detection.

### Stdlib changes
- Added `String.quick_ztr` and `String.is_zstr`
- std::ascii moved into std::core::ascii. Old _m variants are deprecated, as is uint methods.
- Add `String.tokenize_all` to replace the now deprecated `String.splitter`
- Add `String.count` to count the number of instances of a string.
- Add `String.replace` and `String.treplace` to replace substrings within a string.
- Add `Duration * Int` and `Clock - Clock` overload.
- Add `DateTime + Duration` overloads.
- Add `Maybe.equals` and respective `==` operator when the inner type is equatable.
- Add `inherit_stdio` option to `SubProcessOptions` to inherit parent's stdin, stdout, and stderr instead of creating pipes. #2012
- Remove superfluous `cleanup` parameter in `os::exit` and `os::fastexit`.
- Add `extern fn ioctl(CInt fd, ulong request, ...)` binding to libc;

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

Comments


Comment by Christoffer Lernö

It's another months and consequently it's time for a new incremental 0.7 release.

Additions

Unlike 0.7.1, 0.7.2 doesn't really add anything major, but just add a few quality of life enhancements:

||| and &&& with variable right hand side

Rather than writing:

$if FOO:
    bool x = true;
$else
    bool x = foo();
$endif

It's now possible to use ||| to write this compactly as:

bool x = FOO ||| foo();

&&& also supports this.

Compile time random: @rnd

Sometimes it can be useful to create unique ids at compile time. This is now possible with the @rnd macro.

Compile time ceil: math::@ceil

It is somewhat hard to write a good compile time ceil function, so 0.7.2 adds a builtin which is accessible using the math::@ceil macro.

Set the run directory

To make c3c run and c3c compile-run more convenient, it's now possible to use --run-dir (or the project setting run-dir) to set the director from which the compiler runs the resulting binary.

Suffix deprecations

The bitsize suffixes are deprecated, so rather than writing 23u64 use the C style 23UL instead. u128 and i128 suffixes are replaced by ULL and LL suffixes.

Finally, the d suffix for doubles have been added as a complement to f.

Compile time reflection deprecations

MyEnum.elements have been deprecated. Use MyEnum.len instead.

SomeFn.params have been deprecated. Use SomeFn.paramsof instead.

Limits to SIMD sizes

Limitations of vectors are sometimes misunderstood, they will cause code bloat if used for large vectors. For this reason a max vector size has been introduced. By default this is 4096 bits, so basically the size of double[<64>]. It is possible to increase this using --max-vector-size as needed. (As a comparison, the biggest SIMD vectors on x64 is 512 bits, so such a 4096 bit vector would be represented by 8 registers).

has_tagof works on builtin types

While has_tagof will always return false on builtin types, this change nonetheless simplifies writing compile time code involving tags.

Allow recursive generic modules

To simplify generic module resolution it was not possible to recursively generate generic module. This restriction has been lifted in 0.7.1.

Deprecation for old @param docs

By 0.7.1 the declaration style @param foo "abc" would be allowed rather than @param foo : "abc". This was by accident. It's now properly deprecated.

Generic faults are not allowed

Creating faults that are parameterized is usually a mistake and should not have been allowed. It's been completely removed in 0.7.2 as it was classified as a bug.

So no more

module Foo {Type};
typedef BAZ, HELLO;

Faults should be places in a parent or sub-module instead.

Fixes

This release contains about 30 different fixes, most of them newly discovered and not regressions.

They range from fixes to advanced generic types to stdlib bugs.

Stdlib updates

Currently a new version of the matrix library is incubating, and while it didn't make it for the 0.7.2 release, I hope it can be included by 0.7.4.

Optimized String -> ZString conversions

Sometimes a String is already pointing to a valid ZString, so no copy is needed. To check this, String gets two new methods .is_zstr to check if the String is also zero terminated, and .quick_zstr which will create a temp ZString if needed, but otherwise use the String.

std::ascii moves into core

std::ascii moved into std::core::ascii. Old _m variants are deprecated, as is uint methods.

Better String tokenization

It's now possible to further customize tokenization, to ignore empty elements in the middle (or not), ignore any last empty elements at the end (or not). This introduces .tokenize_all which replaces the now deprecated .splitter method.

Count and replace

String further gets some conveniences: .count to count the number of instances of a string within the string and replace / treplace to return a new String with a substring replaced. This functionality was already in DString, but was now added to String as well.

Operator overloads for std::time and Maybe

Duration * Int, Clock - Clock and DateTime + Duration overloads were added for manipulating time.

For Maybe, the == operator is available when the inner type is equatable.

Subprocess improvements

Subprocess added an inherit_stdio option to inherit the parent's stdin, stdout, and stderr instead of creating pipes.

Changelist

The full changelist is here:

### Changes / improvements
- Better default assert messages when no message is specified #2122
- Add `--run-dir`, to specify directory for running executable using `compile-run` and `run` #2121.
- Add `run-dir` to project.json.
- Add `quiet` to project.json.
- Deprecate uXX and iXX bit suffixes.
- Add experimental LL / ULL suffixes for int128 and uint128 literals.
- Allow the right hand side of `|||` and `&&&` be runtime values.
- Added `@rnd()` compile time random function (using the `$$rnd()` builtin). #2078
- Add `math::@ceil()` compile time ceil function. #2134
- Improve error message when using keywords as functions/macros/variables #2133.
- Deprecate `MyEnum.elements`.
- Deprecate `SomeFn.params`.
- Improve error message when encountering recursively defined structs. #2146
- Limit vector max size, default is 4096 bits, but may be increased using --max-vector-size.
- Allow the use of `has_tagof` on builtin types.
- `@jump` now included in `--list-attributes` #2155.
- Add `$$matrix_mul` and `$$matrix_transpose` builtins.
- Add `d` as floating point suffix for `double` types.
- Deprecate `f32`, `f64` and `f128` suffixes.
- Allow recursive generic modules.
- Add deprecation for `@param foo "abc"`.
- Add `--header-output` and `header-output` options for controlling header output folder.
- Generic faults is disallowed.

### Fixes
- Assert triggered when casting from `int[2]` to `uint[2]` #2115
- Assert when a macro with compile time value is discarded, e.g. `foo();` where `foo()` returns an untyped list. #2117
- Fix stringify for compound initializers #2120.
- Fix No index OOB check for `[:^n]` #2123.
- Fix regression in Time diff due to operator overloading #2124.
- attrdef with any invalid name causes compiler assert #2128.
- Correctly error on `@attrdef Foo = ;`.
- Contract on trying to use Object without initializing it.
- Variable aliases of aliases would not resolve correctly. #2131
- Variable aliases could not be assigned to.
- Some folding was missing in binary op compile time resolution #2135.
- Defining an enum like `ABC = { 1 2 }` was accidentally allowed.
- Using a non-const as the end range for a bitstruct would trigger an assert.
- Incorrect parsing of ad hoc generic types, like `Foo{int}****` #2140.
- $define did not correctly handle generic types #2140.
- Incorrect parsing of call attributes #2144.
- Error when using named argument on trailing macro body expansion #2139.
- Designated const initializers with `{}` would overwrite the parent field.
- Empty default case in @jump switch does not fallthrough #2147.
- `&&&` was accidentally available as a valid prefix operator.
- Missing error on default values for body with default arguments #2148.
- `--path` does not interact correctly with relative path arguments #2149.
- Add missing `@noreturn` to `os::exit`.
- Implicit casting from struct to interface failure for inheriting interfaces #2151.
- Distinct types could not be used with tagof #2152.
- `$$sat_mul` was missing.
- `for` with incorrect `var` declaration caused crash #2154.
- Check pointer/slice/etc on `[out]` and `&` params. #2156.
- Compiler didn't check foreach over flexible array member, and folding a flexible array member was allowed #2164.
- Too strict project view #2163.
- Bug using `#foo` arguments with `$defined` #2173
- Incorrect ensure on String.split.
- Removed the naive check for compile time modification, which fixes #1997 but regresses in detection.

### Stdlib changes
- Added `String.quick_ztr` and `String.is_zstr`
- std::ascii moved into std::core::ascii. Old _m variants are deprecated, as is uint methods.
- Add `String.tokenize_all` to replace the now deprecated `String.splitter`
- Add `String.count` to count the number of instances of a string.
- Add `String.replace` and `String.treplace` to replace substrings within a string.
- Add `Duration * Int` and `Clock - Clock` overload.
- Add `DateTime + Duration` overloads.
- Add `Maybe.equals` and respective `==` operator when the inner type is equatable.
- Add `inherit_stdio` option to `SubProcessOptions` to inherit parent's stdin, stdout, and stderr instead of creating pipes. #2012
- Remove superfluous `cleanup` parameter in `os::exit` and `os::fastexit`.
- Add `extern fn ioctl(CInt fd, ulong request, ...)` binding to libc;

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

C3 0.7.1 - Operator Overloading

0.7.0 was supposed to be THE big change this year, but 0.7.1 is actually quitely introducing some very important features.

Operator overloading

Operator overloading has been added to help games and maths programming, a previous blog goes into more depth, below is a short overview.

Operator overloading overview

The @operator attribute defines the overload for Vec + int

To define something like int + Vec there are two additional attributes added:

  • @operator_s ("symmetric operator") allows either type to go first.
  • @operator_r ("reverse operator") requires the second term to be the first one in the arithmetic expression.
Examples

Overloading with the @operator overload attribute:

// Defining a simple add: "Complex + Complex"
macro Complex Complex.add(self, Complex b) @operator(+)
{
    return { .v = self.v + b.v };
} 
// Define the self-modifying +=: "Complex += Complex"
macro Complex Complex.add_this(&self, Complex b) @operator(+=) 
{
    return { .v = self.v += b.v };
}

Overloading with the @operator_s symmetric overload attribute:

// This defines "Quaternion * Real" /and/ "Real * Quaternion"
macro Quaternion Quaternion.scale(self, Real s) @operator_s(*)
{
    return { .v = self.v * s };
}

Overloading with the @operator_r reverse overload attribute:

// This defines "Real / Complex"
macro Complex Complex.div_real_inverse(Complex c, Real r) @operator_r(/) 
{
    return ((Complex) { .r = self }).div(c);
}

More static checking

An important step towards correctness is that @require contracts for functions are now inlined at the caller site in safe mode. This has always been a thing for macros, but now this works for functions as well.

This means simple errors can be caught by the compiler, such as using a null pointer where it wasn't allowed or passing a zero where the argument:

<*
 @require a > 0 : "The parameter cannot be zero"
*>
fn void test(int a)
{ ... }

fn void main()
{
    // Compile time error: contract violation 
    // "The parameter cannot be zero"
    test(0); 
}

This is just the start. C3 will increasingly use more static analysis to check correctness at compile time.

Improvements to attributes

@if attributes now work on locals and aliases defined using alias can now be @deprecated.

Enum lookup

0.6.9 and 0.7.0 introduced ways to let an enum convert to its associated value:

enum Foo : (inline int val)
{
    ABC = 3,
    LIFF = 42
}

fn void main()
{
    int val = Foo.LIFF; // Same as Foo.LIFF.val
}

Still, converting from such a associated value to an enum used the rather unknown @enum_from_value macro.

To make this more convenient, enums gain two new type functions .lookup and .lookup_field: - lookup(value) lookup an enum by inlined value. - lookup_field(field_name, value) lookup an enum by associated value.

fn void main()
{
    Foo? foo = Foo.lookup(42);            // Returns Foo.LIFF
    Foo? foo2 = Foo.lookup_field(val, 3); // Returns Foo.ABC
    Foo? foo3 = Foo.lookup_field(val, 1); // Returns NO_FOUND?
}

Other changes

  • String str = "" was not guaranteed to return a pointer to a null terminated empty string, unlike ZString str = "". This was a little inconsistent, so both return the same thing now.
  • Slice assignment foo[..] = bar would sometimes mean copying the right hand side to each element of foo and sometimes it meant a slice copy. Now semantics have been tightened: foo[..] = bar assigns to each element and foo[..] = bar[..] is a copy.
  • c3c build now picks the first target rather than the first executable.
  • Finally the Win32 thread implementation has been improved.

What's next?

Following this release, the focus will be on improving the standard library organization. New guidelines for submitting modules have been adopted to ensure that the implementation can be worked on without having to be tied to a pull request. Right now an updated matrix library is actively worked on.

There is also a new repo for uncurated C3 resources, where anyone can be showcased: So if you have a project you want to share – do file a pull request.

Change Log

Click for full change log

Changes / improvements

  • Better errors on some common casting mistakes (pointer->slice, String->ZString, deref pointer->array) #2064.
  • Better errors trying to convert an enum to an int and vice versa.
  • Function @require checks are added to the caller in safe mode. #186
  • Improved error message when narrowing isn't allowed.
  • Operator overloading for + - * / % & | ^ << >> ~ == != += -= *= /= %= &= |= ^= <<= >>=
  • Add @operator_r and @operator_s attributes.
  • More stdlib tests: sincos, ArenaAllocator, Slice2d.
  • Make aliases able to use @deprecated.
  • Refactored stdlib file organization.
  • Allow @if on locals.
  • String str = "" is now guaranteed to be null terminated. #2083
  • Improved error messages on Foo { 3, abc } #2099.
  • Foo[1..2] = { .baz = 123 } inference now works. #2095
  • Deprecated old inference with slice copy. Copying must now ensure a slicing operator at the end of the right hand side: foo[1..2] = bar[..] rather than the old foo[1..2] = bar. The old behaviour can be mostly retained with --use-old-slice-copy).
  • Added Enum.lookup and Enum.lookup_field.
  • c3c build picks first target rather than the first executable #2105.
  • New Win32 Mutex, ConditionVariable and OnceFlag implementation

Fixes

  • Trying to cast an enum to int and back caused the compiler to crash.
  • Incorrect rounding at compile time going from double to int.
  • Regression with invalid setup of the WASM temp allocator.
  • Correctly detect multiple overloads of the same type.
  • ABI bug on x64 Linux / MacOS when passing a union containing a struct of 3 floats. #2087
  • Bug with slice acces as inline struct member #2088.
  • @if now does implicit conversion to bool like $if. #2086
  • Fix broken enum inline -> bool conversions #2094.
  • @if was ignored on attrdef, regression 0.7 #2093.
  • @ensure was not included when the function doesn't return a value #2098.
  • Added missing @clone_aligned and add checks to @tclone
  • Comparing a distinct type with an enum with an inline distinct type failed unexpectedly.
  • The %s would not properly print function pointers.
  • Compiler crash when passing an untyped list as an argument to assert #2108.
  • @ensure should be allowed to read "out" variables. #2107
  • Error message for casting generic to incompatible type does not work properly with nested generics #1953
  • Fixed enum regression after 0.7.0 enum change.
  • ConditionVariable now properly works on Win32

Stdlib changes

  • Hash functions for integer vectors and arrays.
  • Prefer math::I and math::I_F for math::IMAGINARY and math::IMAGINARYF the latter is deprecated.
  • Add array::contains to check for a value in an array or slice.

Want To Dive Into C3?

Check out the documentation or download it and try it out.

Have questions? Come and chat with us on Discord.