Skip to content

2025

C3 0.7.1 - Operator overloading, here we come!

Originally from: https://c3.handmade.network/blog/p/9021-c3_0.7.1_-_operator_overloading%252C_here_we_come%2521

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 is the biggest change of course, which is already covered in great detail here. The @operator, @operator_s and @operator_r attributes are used to enable overloading for user defined types.

More static checking

An important step towards correctness is that @require contracts for functions are now inlined at the caller site for safe mode. This has always been a thing for macros, but now this works for functions as well. This means simple errors, 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()
{
    test(0); // Compile time error: contract violation "The parameter cannot be zero"
}

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 would use the rather unknown @enum_from_value macro.

To make this more convenient, enums gain two new type functions .lookup and .lookup_field:

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: https://github.com/c3lang/c3-showcase, so if you have a project you want to share – do file a pull request.

The full change list for 0.7.1 can be found below:

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.

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ö

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 is the biggest change of course, which is already covered in great detail here. The @operator, @operator_s and @operator_r attributes are used to enable overloading for user defined types.

More static checking

An important step towards correctness is that @require contracts for functions are now inlined at the caller site for safe mode. This has always been a thing for macros, but now this works for functions as well. This means simple errors, 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()
{
    test(0); // Compile time error: contract violation "The parameter cannot be zero"
}

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 would use the rather unknown @enum_from_value macro.

To make this more convenient, enums gain two new type functions .lookup and .lookup_field:

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: https://github.com/c3lang/c3-showcase, so if you have a project you want to share – do file a pull request.

The full change list for 0.7.1 can be found below:

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.

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 goes game and maths friendly with operator overloading

Originally from: https://c3.handmade.network/blog/p/9019-c3_goes_game_and_maths_friendly_with_operator_overloading

Operator overloading is a divisive feature. It's been used for atrocious hacks, and to make code totally unreadable, but it's also the reason people stick to a C-like subset of C++ rather than using plain C for projects.

C3 has had operator overloading for a long time, but only for array indexing. In fact there are four different overloads: len [] []= and &[]. Together they do not only enable indexing, but more importantly they enable foreach.

If a type implements len and at least one of [] and &[], then you get foreach on that type. &[] is required for foreach with ref arguments though. And while you can use [] without implementing len, you need len for reverse indexing like a[^3].

However, I've resisted adding operator overloading for arithmetics and equals. Such a change brings us further away from C semantics than I've felt comfortable with. It can certainly be extremely misused and cause confusion.

But, as of 0.7.1 it's in the language. So what made me change my mind?

Most important to me is the fact that in practice people need this. C already has features that can be misused, such as untyped pointers, and we still use it. The same can be said of operator overloading.

Overview

C3 allows the arithmetic operators + - * / % and unary - to be overloaded. In addition to this, bit operators ^ | & and << >> may be overloaded. == and != and "operator + assign" operators such as += and <<= are also possible to overload.

Some technical details:

  1. You can create multiple overloads for a single type, for example Vec * Vec and Vec * int.
  2. Only user defined types may be overloaded. However, bitstructs already have ^ | & defined, so those may not be overloaded.
  3. Overloading == will implicitly make != work and vice versa.
  4. "operator + assign" operators are not required, they will be generated from the operator if not present.

Working with primitive types

While it's not possible to add @operator (the attribute usually used to define the overload) to define something like int + Vec, there are two additional attributes added: @operator_s ("symmetric operator") and @operator_r ("reverse operator") to help it. The former allows either type to go first, and @operator_r requires the second term to be the first one in the arithmetic expression.

Examples

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

The use of the symmetric operator overloading:

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

And finally, reverse operator:

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

The goals and non-goals of C3 operator overloading

The goal of overloading in C3 is allow arithmetics to be used on more types than the built in ones. In C, types such as Complex needs to be implemented in the language itself to get access to + - * /. Expanding the number of builtin types is possible, and the solution the programming language Odin uses. In Odin there is a rich number of builtins, including quaternion and matrix types.

There is a limit to this, as each builtin would increase the complexity of the language itself. I recently considered fixed point types which in the absence of operator overloading would have to use method syntax or be builtins. The former makes such types second class and builtins would increase the language complexity.

That this is such a popular feature with maths and game programming also speaks strongly for its inclusion.

But we also have non goals for C3: I don't want operator overloading that is used to create "magic" solutions in C++. So overloading for things like casting operations, dereference, method dot and logical comparisons are out of scope.

The goal is to enable numerical types to be used in a convenient manner, not to enable advanced abstractions with smart pointers and the like.

That said, there are some omissions:

  1. Comparison operators: useful for some numerical types, such as fixed point numbers, but less so for others – like a complex number or a matrix. Does it need to be in?
  2. Bool conversion: again more useful for fixed point numbers than other numerical types even though zero may be well defined. That's said, it's easily abused.

So at least for 0.7.1, these are omitted.

C3: An evolved C + operator overloading

C3 brings a lot of conveniences and improvements to C: slices, modules, generics, error handling, SIMD vectors, methods, contracts and more.

Mostly these are incremental improvements. If we look at something like slices, C can have slice types too, using macros, but it's inconvenient. Similar with generics (macro-based generics are possible in C), SIMD vectors, contracts etc.

On the other hand .method() syntax is certainly not in C, and C3 has much more compile time reflection available, but this is rarely a major pain point for C programmers. Operator overloading on the other hand: this is something you can't properly emulate in C. So for C programmers that really need operator overloading, there isn't really any way to get this. C++ or some similarly more complex language - such as Rust – is the only way out.

Of the recent indie C alternatives: Odin, Zig, C3 and Hare, only C3 offers operator overloading(*). Odin, as previously mentioned, addresses the issue with a rich set of builtin numerical types instead.

So moving forward it's possible that this feature should be pushed a bit more as it differentiates C3 from the alternatives. People looking for a simpler alternative to C++ for "C + operator overloading", now has C3 as another option to consider.

(* One should note that Jai, Jonathan Blow's C++ alternative geared towards game programming, offers operator overloading)

Wrapping it up

It's taken a long time for C3 to get operator overloading, but now that it's finally here, the language will lean into it, taking advantage of what it offers. Some features, such as @compact implicitly allowing comparisons will likely be removed. The standard library will add fixed point numbers and potentially leverage it for other things as well that otherwise was so marginal as to never be considered (saturating integers comes to mind).

I have been extremely reluctant to add operator overloading for arithmetics and resisted it for years, so the fact that we're adding it to C3 anyway is showing how much value it brings.

0.7.1 will officially be released the 1st of May (possibly earlier)

Comments


Comment by Christoffer Lernö

Operator overloading is a divisive feature. It's been used for atrocious hacks, and to make code totally unreadable, but it's also the reason people stick to a C-like subset of C++ rather than using plain C for projects.

C3 has had operator overloading for a long time, but only for array indexing. In fact there are four different overloads: len [] []= and &[]. Together they do not only enable indexing, but more importantly they enable foreach.

If a type implements len and at least one of [] and &[], then you get foreach on that type. &[] is required for foreach with ref arguments though. And while you can use [] without implementing len, you need len for reverse indexing like a[^3].

However, I've resisted adding operator overloading for arithmetics and equals. Such a change brings us further away from C semantics than I've felt comfortable with. It can certainly be extremely misused and cause confusion.

But, as of 0.7.1 it's in the language. So what made me change my mind?

Most important to me is the fact that in practice people need this. C already has features that can be misused, such as untyped pointers, and we still use it. The same can be said of operator overloading.

Overview

C3 allows the arithmetic operators + - * / % and unary - to be overloaded. In addition to this, bit operators ^ | & and << >> may be overloaded. == and != and "operator + assign" operators such as += and <<= are also possible to overload.

Some technical details:

  1. You can create multiple overloads for a single type, for example Vec * Vec and Vec * int.
  2. Only user defined types may be overloaded. However, bitstructs already have ^ | & defined, so those may not be overloaded.
  3. Overloading == will implicitly make != work and vice versa.
  4. "operator + assign" operators are not required, they will be generated from the operator if not present.

Working with primitive types

While it's not possible to add @operator (the attribute usually used to define the overload) to define something like int + Vec, there are two additional attributes added: @operator_s ("symmetric operator") and @operator_r ("reverse operator") to help it. The former allows either type to go first, and @operator_r requires the second term to be the first one in the arithmetic expression.

Examples

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

The use of the symmetric operator overloading:

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

And finally, reverse operator:

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

The goals and non-goals of C3 operator overloading

The goal of overloading in C3 is allow arithmetics to be used on more types than the built in ones. In C, types such as Complex needs to be implemented in the language itself to get access to + - * /. Expanding the number of builtin types is possible, and the solution the programming language Odin uses. In Odin there is a rich number of builtins, including quaternion and matrix types.

There is a limit to this, as each builtin would increase the complexity of the language itself. I recently considered fixed point types which in the absence of operator overloading would have to use method syntax or be builtins. The former makes such types second class and builtins would increase the language complexity.

That this is such a popular feature with maths and game programming also speaks strongly for its inclusion.

But we also have non goals for C3: I don't want operator overloading that is used to create "magic" solutions in C++. So overloading for things like casting operations, dereference, method dot and logical comparisons are out of scope.

The goal is to enable numerical types to be used in a convenient manner, not to enable advanced abstractions with smart pointers and the like.

That said, there are some omissions:

  1. Comparison operators: useful for some numerical types, such as fixed point numbers, but less so for others – like a complex number or a matrix. Does it need to be in?
  2. Bool conversion: again more useful for fixed point numbers than other numerical types even though zero may be well defined. That's said, it's easily abused.

So at least for 0.7.1, these are omitted.

C3: An evolved C + operator overloading

C3 brings a lot of conveniences and improvements to C: slices, modules, generics, error handling, SIMD vectors, methods, contracts and more.

Mostly these are incremental improvements. If we look at something like slices, C can have slice types too, using macros, but it's inconvenient. Similar with generics (macro-based generics are possible in C), SIMD vectors, contracts etc.

On the other hand .method() syntax is certainly not in C, and C3 has much more compile time reflection available, but this is rarely a major pain point for C programmers. Operator overloading on the other hand: this is something you can't properly emulate in C. So for C programmers that really need operator overloading, there isn't really any way to get this. C++ or some similarly more complex language - such as Rust – is the only way out.

Of the recent indie C alternatives: Odin, Zig, C3 and Hare, only C3 offers operator overloading(*). Odin, as previously mentioned, addresses the issue with a rich set of builtin numerical types instead.

So moving forward it's possible that this feature should be pushed a bit more as it differentiates C3 from the alternatives. People looking for a simpler alternative to C++ for "C + operator overloading", now has C3 as another option to consider.

(* One should note that Jai, Jonathan Blow's C++ alternative geared towards game programming, offers operator overloading)

Wrapping it up

It's taken a long time for C3 to get operator overloading, but now that it's finally here, the language will lean into it, taking advantage of what it offers. Some features, such as @compact implicitly allowing comparisons will likely be removed. The standard library will add fixed point numbers and potentially leverage it for other things as well that otherwise was so marginal as to never be considered (saturating integers comes to mind).

I have been extremely reluctant to add operator overloading for arithmetics and resisted it for years, so the fact that we're adding it to C3 anyway is showing how much value it brings.

0.7.1 will officially be released the 1st of May (possibly earlier)

C3: Operator overloading is coming in 0.7.1

Originally from: https://c3.handmade.network/blog/p/9016-c3__operator_overloading_is_coming_in_0.7.1

Initially I had planned 0.7.1 to be mostly patching 0.7.0, but plans change.

The big news is that operator overloading is coming. Not just the existing overload of [] &[] and []=, but arithmetics, bit operations and equals is all coming.

Comparisons is conspicuously missing, but might arrive.

Overloading is here to simplify creating numerical types that can be manipulated with the normal operators.

Consequently, there won't be anything like C++, where you can overload deref, dot invocation, casts etc. Also, overloads may not manipulate global state.

All in all, using these overloads for anything but numerical types is not intended (yes, so don't string concat with + nor should you use << to append to lists etc)

Operator overloading will only allowed on user defined types.

Comments


Comment by Christoffer Lernö

Initially I had planned 0.7.1 to be mostly patching 0.7.0, but plans change.

The big news is that operator overloading is coming. Not just the existing overload of [] &[] and []=, but arithmetics, bit operations and equals is all coming.

Comparisons is conspicuously missing, but might arrive.

Overloading is here to simplify creating numerical types that can be manipulated with the normal operators.

Consequently, there won't be anything like C++, where you can overload deref, dot invocation, casts etc. Also, overloads may not manipulate global state.

All in all, using these overloads for anything but numerical types is not intended (yes, so don't string concat with + nor should you use << to append to lists etc)

Operator overloading will only allowed on user defined types.

C3 0.7.0 - One Step Closer To 1.0

The 0.7.0 milestone reached

Originally I was going to write this as normal release notes, but it's a bit too much to go through the whys and wherefores of the changes. So the format of this blog post is going to be a bit different.

0.6.0 was tagged in June 2024, and since then there's been monthly releases up until the last one, 0.6.8. While the language has evolved during this year, each 0.6.x version has been backwards compatible with code written for previous 0.6 versions. Despite this constraint, there have been a lot of improvements, especially in enhancing compile time to have much fewer corners, and of course the standard library has been improved as well.

Well worth mentioning is the impact Tsoding had on the visibility of the language. His "recreational programming" streams on C3 brought a lot of new users to the community, and with it a lot of extremely valuable feedback on both the langage and the standard library.

The release format of C3 allows for breaking changes in every new 0.x release. This means that 0.7.0 and 0.6.8 code will not be compatible. Some of this is already deprecated code in 0.6.8, but for some things there were no clear migration path.

For that reason, I'd like to start by giving an primer on how to convert 0.6.x code to 0.7.0

Migrating to 0.7.0

While these changes may seem like a lot, most of the changes were actually gradually introduced in 0.6.x with the now removed variants are being deprecated.

Syntax changes

  1. Generics now use {} rather than (<>). E.g. List(<int>) x; -> List {int} x;
  2. The keyword def has been replaced by alias, syntax is otherwise unchanged. E.g. def Foo = int; -> alias Foo = int;
  3. The keyword distinct has been replaced by typedef, syntax is otherwise unchanged. E.g. distinct Bar = int; -> typedef Bar = int;
  4. Optional types are now declared with ? instad of !. E.g. int! a; -> int? a;
  5. Declare faults with faultdef: fault MyFault { ABC, FOO_FAULT } -> faultdef ABC, FOO_FAULT;
  6. The anyfault type is now called fault. E.g. anyfault the_err = @catch(foo); -> fault the_err = @catch(foo);
  7. Compound literal syntax Foo { 1, 2 } is no longer valid. Use C-style (Foo) { 1, 2 } instead.
  8. {| |} expression blocks have been removed. Use do { ... }; with break instead.
  9. &ref and $varef have been removed. Use #expr and $vaexpr instead.
  10. $vaarg(1) syntax is now invalid. Use $vaarg[1] instead.
  11. Casting an enum to its ordinal is no longer valid. .ordinal instead.
  12. $or, $and are removed. Use ||| and &&&.
  13. $concat is removed. Use +++ instead.
  14. .allocator = allocator syntax for named arguments in calls is removed. Use the new syntax allocator: allocator instead.
  15. The "any-switch" has been removed. Switch on foo.type then explicitly do the cast instead.
  16. if (catch foo) { case ... } syntax is removed. Use if (catch err = foo) { switch (err) { ... } } instead.
  17. $foreach, $for and $switch no longer uses (). Remove the parentheses and end the statement with :, e.g. $foreach ($x, $y : $foo) -> $foreach $x, $y : $foo:
  18. Remove the int[?] syntax which was tested as an alternative to int[*].
  19. @return! in contracts are changed to @return?
  20. Contracts that have a trailing description, now MUST have a : before the description. E.g. @require a > b "a must be greater than b" -> @require a > b : "a must be greater than b".
  21. Order of attribute declaration is changed for alias: def Foo = int @public; -> alias Foo @public = int;
  22. Definition of new attributes use attrdef as the keyword rather than def.

Stdlib changes

  1. Use tmem rather than allocator::temp().
  2. Use mem rather than allocator::heap().
  3. The new_init and other new_* functions have been removed. Use init(mem), s.concat(mem, s2) etc.
  4. The temp allocator is not implicitly set up on other than the main thread. Use @pool_init() to set up a temporary memory pool on a thread.
  5. Lists and maps no longer allocate on the heap when not initialized. Instead, they use the temp allocator if not initialized.
  6. @pool() no longer requires (or permits) an allocator. The temp allocator supports any number on in-flight temp allocators.

Overall, the new functions, which implicitly used the heap allocator, has gone away and been replaced by a function that explicitly takes the allocator. There are often also temp allocator variants, which are then prefixed with t, e.g. tconcat.

Try to use the temp allocator when possible.

Additions and improvements

There are not only removals and changes, there are also some language additions and improvements in 0.7.0 compared to 0.6.8.

  1. Swizzling assignment is now possible, e.g. foo.xy += { 1, 2 }
  2. A @format attribute has been added to compile time check formatting strings.
  3. Enum associated values can reference the calling enum.
  4. !!foo used to not work, now it properly is interpreted as !(!foo).
  5. Enum inline values were already in 0.6.8, but from 0.7.0 the .ordinal has the same behaviour as associated values. This makes enums more flexible and closer to C enum behaviour.
  6. @wstring, @wstring32, @char32 and @char16 macros were added for interfacing with wide character libraries.

Thoughts on 0.7.0

The breaking changes of 0.7.0 are of two categories: (1) removals (2) syntax changes.

Why remove things?

One might ask why one would remove things from a language? Surely more is better?

The reason is that learning a language this "one more feature", is additional effort to learn the language. It's also one more thing for people knowing the language to remember. In this way, each feature has a cost outside of compiler complexity.

When people encounter a rare feature such as the any-switch, they will need to look up how it works. This both takes time and undermines people's confidence that they know the language.

So during the development of a language it's important to weed out things that aren't carrying its weight, and this is why 0.7 removes features.

Why change syntax?

As an example, int! a; has been used in C3 since the introduction of optionals. Why change it now?

In this case it's because the semantics of int! a; was quite different from an optional when it was introduced. The use of ! was exactly not to confuse it with an optional. Now it's so much closer to the "common" optional that a change to int? a; makes perfect sense.

def -> alias follows a similar trajectory. Initially it would be used both for aliases and to define new attributes and distinct types. Then distinct type definition got its own separate definition, so it was more like an alias aside from the attributes. With attributes getting its own attrdef, what remained were only aliases, and changing the name to alias suddenly made perfect sense.

Both these changes make the language follow more common syntax conventions as well, which is a win.

The goal is for the syntax to feel straightforward and have as little "oh this is odd" as possible. This makes it both easier to learn and remember.

More like C

Even though neither alias nor int? exist in C, they're familiar from adjacent languages. I believe this will make C3 feel "more like C", even though the changes are not in C.

Similarly, removing novel constructs like the expression block reduces the divergence from C syntax.

C3 0.7.1 and onwards

Where C3 0.7.0 has been a big cleanup release, it doesn't have that many improvements to general quality of life aside from the @format attribute.

I am really looking forward to inlining and resolving more function contracts on the caller side. In the first pass it will catch some low hanging fruit such as passing invalid constants, but this can be gradually improved to catch even more bugs.

Another thing is improving the standard library. There are additions I'd like to do both to the collections and to threads.

There are also a number of minor improvements to the compiler that would add to the quality of life for some uses. On top of this the inline asm is in need of bug fixes and cleanup.

Outside of the language and the compiler, a good docgen is needed.

But as for language changes, there is nothing planned, just incremental improvements to what's already there.

Change Log

Here is the full changelist for 0.6.8 to 0.7.0. Obviously if we'd bundle all changes from 0.6.1 to 0.7.0 the list would be MUCH bigger :D

Click for full change log
  • Removed Foo { 1, 2 } initializer.
  • Changed Foo(<int>) to Foo {int}.
  • Removed {| |} expression blocks.
  • Removed macro &ref and $varef parameters.
  • Removed $vaexpr(0) syntax in favour of $vaexpr[0]
  • Enum does not cast to/from an integer (its ordinal).
  • Removed use of void! for main, test and benchmark functions.
  • Removed $or, $and, $concat compile time functions.
  • Removed @adhoc attribute.
  • Disallow inline use of nested generics (e.g. List{List{int}}.
  • Remove .allocator = allocator syntax for functions.
  • Remove @operator(construct).
  • Removal of "any-switch".
  • Allow swizzling assign, eg. abc.xz += { 5, 10 };
  • Added $$wstr16 and $$wstr32 builtins.
  • $foreach "()" replaced by trailing ":" $foreach ($x, $y : $foo) -> $foreach $x, $y : $foo:
  • $for "()" replaced by trailing ":" $for (var $x = 0; $x < FOO; $x++) -> $for var $x = 0; $x < FOO; $x++:
  • $switch "()" replaced by trailing ":" $switch ($Type) -> $switch $Type:
  • Empty $switch requires trailing ":" $switch -> $switch:
  • Rename @return! to @return? and change syntax to require ":" after faults.
  • Remove if (catch foo) { case ... } syntax.
  • Remove [?] syntax.
  • Change int! to int? syntax.
  • New fault declaration using faultdef.
  • Enum associated values can reference the calling enum.
  • Improve error message on foo ?? io::EOF with missing '?' #2036
  • Make @public import recursive. #2018
  • Fault nameof prefixes the first last module path, for instance std::io::EOF is rendered as io::EOF.
  • Rename def to alias.
  • Change distinct -> typedef.
  • Order of attribute declaration is changed for alias.
  • Added LANGUAGE_DEV_VERSION env constant.
  • Rename anyfault -> fault.
  • !!foo now works same as as ! ! foo.
  • Temp allocator now supports more than 2 in-flight stacks.
  • Printing stacktrace uses its own temp allocator.
  • Allow inferred type on body parameters. E.g. @stack_mem(1024; alloc) { ... };
  • Use @pool_init() to set up a temp pool on a thread. Only the main thread has implicit temp pool setup.
  • tmem is now a variable.
  • Compile test and benchmark functions when invoking --lsp #2058.
  • Added @format attribute for compile time printf validation #2057.
  • Formatter no longer implicitly converts enums to ordinals.

Fixes

  • Fix address sanitizer to work on MachO targets (e.g. MacOS).
  • Post and pre-decrement operators switched places for vector elements #2010.
  • Aliases were incorrectly considered compile time constants.
  • FreeBSD libc stat definitions were incorrect.
  • Atomic max was incorrect.
  • "+".to_float() would panic.
  • import can now both be @public and @norecurse.
  • Crash when trying to convert a struct slice to a vector #2039.
  • Crash resolving a method on Foo[2] when Foo is distinct #2042.
  • Bug due to missing cast when doing $i[$x] = $z.
  • Incorrectly allowed getting pointer to a macro #2049.
  • &self not runtime null-checked in macro #1827.
  • Bug when printing a boolean value as an integer using printf.
  • Show error when a generic module contains a self-generic type.
  • "Single module" was not enforced when creating a static library using as a project target.

Stdlib changes

  • new_* functions in general moved to version without new_ prefix.
  • string::new_from_* changed to string::from_*.
  • String.to_utf16_copy and related changed to String.to_utf16.
  • String.to_utf16_tcopy and related changed to String.to_temp_utf16
  • mem::temp_new changed to mem::tnew.
  • mem::temp_alloc and related changed to mem::talloc.
  • mem::temp_new_array changed to mem::temp_array.
  • Add ONHEAP variants for List/HashMap for initializing global maps on the heap.
  • Remove Vec2 and other aliases from std::math. Replace .length_sq() with sq_magnitude()
  • Change all hash functions to have a common hash function.
  • @wstring, @wstring32, @char32 and @char16 compile time macros added.
  • Updates to Atomic to handle distinct types and booleans.
  • Added math::iota.
  • @pool no longer takes an argument.
  • Allocator interface removes mark and reset.
  • DynamicArenaAllocator has changed init function.
  • Added BackedArenaAllocator which is allocated to a fixed size, then allocates on the backing allocator and supports mark/reset.
  • AnyList now also defaults to the temp allocator.
  • os::getcwd and os::get_home_dir requires an explicit allocator.
  • file::load_new and file::load_path_new removed.
  • os::exit and os::fastexit added.

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 released - one step closer to 1.0

Originally from: https://c3.handmade.network/blog/p/9010-c3_0.7_released_-_one_step_closer_to_1.0

The 0.7.0 milestone reached

Originally I was going to write this as normal release notes, but it's a bit too much to go through the whys and wherefores of the changes. So the format of this blog post is going to be a bit different.

0.6.0 was tagged in June 2024, and since then there's been monthly releases up until the last one, 0.6.8. While the language has evolved during this year, each 0.6.x version has been backwards compatible with code written for previous 0.6 versions. Despite this constraint, there have been a lot of improvements, especially in enhancing compile time to have much fewer corners, and of course the standard library has been improved as well.

Well worth mentioning is the impact Tsoding (https://www.twitch.tv/tsoding) had on the visibility of the language. His "recreational programming" streams on C3 brought a lot of new users to the community, and with it a lot of extremely valuable feedback on both the langage and the standard library.

The release format of C3 allows for breaking changes in every 0.x release. This means that 0.7.0 and 0.6.8 code will not be compatible. Some of this is already deprecated code in 0.6.8, but for some things there were no clear migration path.

For that reason, I'd like to start by giving an primer on how to convert 0.6.x code to 0.7.0

Migrating to 0.7.0

(While these changes may seem like a lot, most of the changes were actually gradually introduced in 0.6.x with the now removed variants being deprecated)

Syntax changes

  1. Generics now use {} rather than (<>). E.g. List(<int>) x; -> List {int} x;
  2. The keyword def has been replaced by alias, syntax is otherwise unchanged. E.g. def Foo = int; -> alias Foo = int;
  3. The keyword distinct has been replaced by typedef, syntax is otherwise unchanged. E.g. distinct Bar = int; -> typedef Bar = int;
  4. Optional types are now declared with ? instad of !. E.g. int! a; -> int? a;
  5. Declare faults with faultdef: fault MyFault { ABC, FOO_FAULT } -> faultdef ABC, FOO_FAULT;
  6. The anyfault type is now called fault. E.g. anyfault the_err = @catch(foo); -> fault the_err = @catch(foo);
  7. Compound literal syntax Foo { 1, 2 } is no longer valid. Use C-style (Foo) { 1, 2 } instead.
  8. {| |} expression blocks have been removed. Use do { ... }; with break instead.
  9. &ref and $varef have been removed. Use #expr and $vaexpr instead.
  10. $vaarg(1) syntax is now invalid. Use $vaarg[1] instead.
  11. Casting an enum to its ordinal is no longer valid. .ordinal instead.
  12. $or, $and are removed. Use ||| and &&&.
  13. $concat is removed. Use +++ instead.
  14. .allocator = allocator syntax for named arguments in calls is removed. Use the new syntax allocator: allocator instead.
  15. The "any-switch" has been removed. Switch on foo.type then explicitly do the cast instead.
  16. if (catch foo) { case ... } syntax is removed. Use if (catch err = foo) { switch (err) { ... } } instead.
  17. $foreach, $for and $switch no longer uses (). Remove the parentheses and end the statement with :, e.g. $foreach ($x, $y : $foo) -> $foreach $x, $y : $foo:
  18. Remove the int[?] syntax which was tested as an alternative to int[*].
  19. @return! in contracts are changed to @return?
  20. Contracts that have a trailing description, now MUST have a : before the description. E.g. @require a > b "a must be greater than b" -> @require a > b : "a must be greater than b".
  21. Order of attribute declaration is changed for alias: def Foo = int @public; -> alias Foo @public = int;
  22. Definition of new attributes use attrdef as the keyword rather than def.

Stdlib changes

  1. Use tmem rather than allocator::temp().
  2. Use mem rather than allocator::heap().
  3. The new_init and other new_* functions have been removed. Use init(mem), s.concat(mem, s2) etc.
  4. The temp allocator is not implicitly set up on other than the main thread. Use @pool_init() to set up a temporary memory pool on a thread.
  5. Lists and maps no longer allocate on the heap when not initialized. Instead, they use the temp allocator if not initialized.
  6. @pool() no longer requires (or permits) an allocator. The temp allocator supports any number on in-flight temp allocators.

Overall, the new functions, which implicitly used the heap allocator, has gone away and been replaced by a function that explicitly takes the allocator. There are often also temp allocator variants, which are then prefixed with t, e.g. tconcat.

Try to use the temp allocator when possible.

Additions and improvements

There are not only removals and changes, there are also some language additions and improvements in 0.7.0 compared to 0.6.8.

  1. Swizzling assignment is now possible, e.g. foo.xy += { 1, 2 }
  2. A @format attribute has been added to compile time check formatting strings.
  3. Enum associated values can reference the calling enum.
  4. !!foo used to not work, now it properly is interpreted as !(!foo).
  5. Enum inline values were already in 0.6.8, but from 0.7.0 the .ordinal has the same behaviour as associated values. This makes enums more flexible and closer to C enum behaviour.
  6. @wstring, @wstring32, @char32 and @char16 macros were added for interfacing with wide character libraries.

Thoughts on 0.7.0

The breaking changes of 0.7.0 are of two categories: (1) removals (2) syntax changes.

Why remove things?

One might ask why one would remove things from a language? Surely more is better?

The reason is that learning a language this "one more feature", is additional effort to learn the language. It's also one more thing for people knowing the language to remember. In this way, each feature has a cost outside of compiler complexity.

When people encounter a rare feature such as the any-switch, they will need to look up how it works. This both takes time and undermines people's confidence that they know the language.

So during the development of a language it's important to weed out things that aren't carrying its weight, and this is why 0.7 removes features.

Why change syntax?

As an example, int! a; has been used in C3 since the introduction of optionals. Why change it now?

In this case it's because the semantics of int! a; was quite different from an optional when it was introduced. The use of ! was exactly not to confuse it with an optional. Now it's so much closer to the "common" optional that a change to int? a; makes perfect sense.

def -> alias follows a similar trajectory. Initially it would be used both for aliases and to define new attributes and distinct types. Then distinct type definition got its own separate definition, so it was more like an alias aside from the attributes. With attributes getting its own attrdef, what remained were only aliases, and changing the name to alias suddenly made perfect sense.

Both these changes make the language follow more common syntax conventions as well, which is a win.

The goal is for the syntax to feel straightforward and have as little "oh this is odd" as possible. This makes it both easier to learn and remember.

More like C

Even though neither alias nor int? exist in C, they're familiar from adjacent languages. I believe this will make C3 feel "more like C", even though the changes are not in C.

Similarly, removing novel constructs like the expression block reduces the divergence from C syntax.

C3 0.7.1 and onwards

Where C3 0.7.0 has been a big cleanup release, it doesn't have that many improvements to general quality of life aside from the @format attribute.

I am really looking forward to inlining and resolving more function contracts on the caller side. In the first pass it will catch some low hanging fruit such as passing invalid constants, but this can be gradually improved to catch even more bugs.

Another thing is improving the standard library. There are additions I'd like to do both to the collections and to threads.

There are also a number of minor improvements to the compiler that would add to the quality of life for some uses. On top of this the inline asm is in need of bug fixes and cleanup.

Outside of the language and the compiler, a good docgen is needed.

But as for language changes, there is nothing planned, just incremental improvements to what's already there.

Full changelist for 0.7.0

Here is the full changelist for 0.6.8 to 0.7.0. Obviously if we'd bundle all changes from 0.6.1 to 0.7.0 the list would be MUCH bigger :D

Changes / improvements

  • Removed Foo { 1, 2 } initializer.
  • Changed Foo(<int>) to Foo {int}.
  • Removed {| |} expression blocks.
  • Removed macro &ref and $varef parameters.
  • Removed $vaexpr(0) syntax in favour of $vaexpr[0]
  • Enum does not cast to/from an integer (its ordinal).
  • Removed use of void! for main, test and benchmark functions.
  • Removed $or, $and, $concat compile time functions.
  • Removed @adhoc attribute.
  • Disallow inline use of nested generics (e.g. List{List{int}}.
  • Remove .allocator = allocator syntax for functions.
  • Remove @operator(construct).
  • Removal of "any-switch".
  • Allow swizzling assign, eg. abc.xz += { 5, 10 };
  • Added $$wstr16 and $$wstr32 builtins.
  • $foreach "()" replaced by trailing ":" $foreach ($x, $y : $foo) -> $foreach $x, $y : $foo:
  • $for "()" replaced by trailing ":" $for (var $x = 0; $x < FOO; $x++) -> $for var $x = 0; $x < FOO; $x++:
  • $switch "()" replaced by trailing ":" $switch ($Type) -> $switch $Type:
  • Empty $switch requires trailing ":" $switch -> $switch:
  • Rename @return! to @return? and change syntax to require ":" after faults.
  • Remove if (catch foo) { case ... } syntax.
  • Remove [?] syntax.
  • Change int! to int? syntax.
  • New fault declaration using faultdef.
  • Enum associated values can reference the calling enum.
  • Improve error message on foo ?? io::EOF with missing '?' #2036
  • Make @public import recursive. #2018
  • Fault nameof prefixes the first last module path, for instance std::io::EOF is rendered as io::EOF.
  • Rename def to alias.
  • Change distinct -> typedef.
  • Order of attribute declaration is changed for alias.
  • Added LANGUAGE_DEV_VERSION env constant.
  • Rename anyfault -> fault.
  • !!foo now works same as as ! ! foo.
  • Temp allocator now supports more than 2 in-flight stacks.
  • Printing stacktrace uses its own temp allocator.
  • Allow inferred type on body parameters. E.g. @stack_mem(1024; alloc) { ... };
  • Use @pool_init() to set up a temp pool on a thread. Only the main thread has implicit temp pool setup.
  • tmem is now a variable.
  • Compile test and benchmark functions when invoking --lsp #2058.
  • Added @format attribute for compile time printf validation #2057.
  • Formatter no longer implicitly converts enums to ordinals.

Fixes

  • Fix address sanitizer to work on MachO targets (e.g. MacOS).
  • Post and pre-decrement operators switched places for vector elements #2010.
  • Aliases were incorrectly considered compile time constants.
  • FreeBSD libc stat definitions were incorrect.
  • Atomic max was incorrect.
  • "+".to_float() would panic.
  • import can now both be @public and @norecurse.
  • Crash when trying to convert a struct slice to a vector #2039.
  • Crash resolving a method on Foo[2] when Foo is distinct #2042.
  • Bug due to missing cast when doing $i[$x] = $z.
  • Incorrectly allowed getting pointer to a macro #2049.
  • &self not runtime null-checked in macro #1827.
  • Bug when printing a boolean value as an integer using printf.
  • Show error when a generic module contains a self-generic type.
  • "Single module" was not enforced when creating a static library using as a project target.

Stdlib changes

  • new_* functions in general moved to version without new_ prefix.
  • string::new_from_* changed to string::from_*.
  • String.to_utf16_copy and related changed to String.to_utf16.
  • String.to_utf16_tcopy and related changed to String.to_temp_utf16
  • mem::temp_new changed to mem::tnew.
  • mem::temp_alloc and related changed to mem::talloc.
  • mem::temp_new_array changed to mem::temp_array.
  • Add ONHEAP variants for List/HashMap for initializing global maps on the heap.
  • Remove Vec2 and other aliases from std::math. Replace .length_sq() with sq_magnitude()
  • Change all hash functions to have a common hash function.
  • @wstring, @wstring32, @char32 and @char16 compile time macros added.
  • Updates to Atomic to handle distinct types and booleans.
  • Added math::iota.
  • @pool no longer takes an argument.
  • Allocator interface removes mark and reset.
  • DynamicArenaAllocator has changed init function.
  • Added BackedArenaAllocator which is allocated to a fixed size, then allocates on the backing allocator and supports mark/reset.
  • AnyList now also defaults to the temp allocator.
  • os::getcwd and os::get_home_dir requires an explicit allocator.
  • file::load_new and file::load_path_new removed.
  • os::exit and os::fastexit added.

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ö

The 0.7.0 milestone reached

Originally I was going to write this as normal release notes, but it's a bit too much to go through the whys and wherefores of the changes. So the format of this blog post is going to be a bit different.

0.6.0 was tagged in June 2024, and since then there's been monthly releases up until the last one, 0.6.8. While the language has evolved during this year, each 0.6.x version has been backwards compatible with code written for previous 0.6 versions. Despite this constraint, there have been a lot of improvements, especially in enhancing compile time to have much fewer corners, and of course the standard library has been improved as well.

Well worth mentioning is the impact Tsoding (https://www.twitch.tv/tsoding) had on the visibility of the language. His "recreational programming" streams on C3 brought a lot of new users to the community, and with it a lot of extremely valuable feedback on both the langage and the standard library.

The release format of C3 allows for breaking changes in every 0.x release. This means that 0.7.0 and 0.6.8 code will not be compatible. Some of this is already deprecated code in 0.6.8, but for some things there were no clear migration path.

For that reason, I'd like to start by giving an primer on how to convert 0.6.x code to 0.7.0

Migrating to 0.7.0

(While these changes may seem like a lot, most of the changes were actually gradually introduced in 0.6.x with the now removed variants being deprecated)

Syntax changes

  1. Generics now use {} rather than (<>). E.g. List(<int>) x; -> List {int} x;
  2. The keyword def has been replaced by alias, syntax is otherwise unchanged. E.g. def Foo = int; -> alias Foo = int;
  3. The keyword distinct has been replaced by typedef, syntax is otherwise unchanged. E.g. distinct Bar = int; -> typedef Bar = int;
  4. Optional types are now declared with ? instad of !. E.g. int! a; -> int? a;
  5. Declare faults with faultdef: fault MyFault { ABC, FOO_FAULT } -> faultdef ABC, FOO_FAULT;
  6. The anyfault type is now called fault. E.g. anyfault the_err = @catch(foo); -> fault the_err = @catch(foo);
  7. Compound literal syntax Foo { 1, 2 } is no longer valid. Use C-style (Foo) { 1, 2 } instead.
  8. {| |} expression blocks have been removed. Use do { ... }; with break instead.
  9. &ref and $varef have been removed. Use #expr and $vaexpr instead.
  10. $vaarg(1) syntax is now invalid. Use $vaarg[1] instead.
  11. Casting an enum to its ordinal is no longer valid. .ordinal instead.
  12. $or, $and are removed. Use ||| and &&&.
  13. $concat is removed. Use +++ instead.
  14. .allocator = allocator syntax for named arguments in calls is removed. Use the new syntax allocator: allocator instead.
  15. The "any-switch" has been removed. Switch on foo.type then explicitly do the cast instead.
  16. if (catch foo) { case ... } syntax is removed. Use if (catch err = foo) { switch (err) { ... } } instead.
  17. $foreach, $for and $switch no longer uses (). Remove the parentheses and end the statement with :, e.g. $foreach ($x, $y : $foo) -> $foreach $x, $y : $foo:
  18. Remove the int[?] syntax which was tested as an alternative to int[*].
  19. @return! in contracts are changed to @return?
  20. Contracts that have a trailing description, now MUST have a : before the description. E.g. @require a > b "a must be greater than b" -> @require a > b : "a must be greater than b".
  21. Order of attribute declaration is changed for alias: def Foo = int @public; -> alias Foo @public = int;
  22. Definition of new attributes use attrdef as the keyword rather than def.

Stdlib changes

  1. Use tmem rather than allocator::temp().
  2. Use mem rather than allocator::heap().
  3. The new_init and other new_* functions have been removed. Use init(mem), s.concat(mem, s2) etc.
  4. The temp allocator is not implicitly set up on other than the main thread. Use @pool_init() to set up a temporary memory pool on a thread.
  5. Lists and maps no longer allocate on the heap when not initialized. Instead, they use the temp allocator if not initialized.
  6. @pool() no longer requires (or permits) an allocator. The temp allocator supports any number on in-flight temp allocators.

Overall, the new functions, which implicitly used the heap allocator, has gone away and been replaced by a function that explicitly takes the allocator. There are often also temp allocator variants, which are then prefixed with t, e.g. tconcat.

Try to use the temp allocator when possible.

Additions and improvements

There are not only removals and changes, there are also some language additions and improvements in 0.7.0 compared to 0.6.8.

  1. Swizzling assignment is now possible, e.g. foo.xy += { 1, 2 }
  2. A @format attribute has been added to compile time check formatting strings.
  3. Enum associated values can reference the calling enum.
  4. !!foo used to not work, now it properly is interpreted as !(!foo).
  5. Enum inline values were already in 0.6.8, but from 0.7.0 the .ordinal has the same behaviour as associated values. This makes enums more flexible and closer to C enum behaviour.
  6. @wstring, @wstring32, @char32 and @char16 macros were added for interfacing with wide character libraries.

Thoughts on 0.7.0

The breaking changes of 0.7.0 are of two categories: (1) removals (2) syntax changes.

Why remove things?

One might ask why one would remove things from a language? Surely more is better?

The reason is that learning a language this "one more feature", is additional effort to learn the language. It's also one more thing for people knowing the language to remember. In this way, each feature has a cost outside of compiler complexity.

When people encounter a rare feature such as the any-switch, they will need to look up how it works. This both takes time and undermines people's confidence that they know the language.

So during the development of a language it's important to weed out things that aren't carrying its weight, and this is why 0.7 removes features.

Why change syntax?

As an example, int! a; has been used in C3 since the introduction of optionals. Why change it now?

In this case it's because the semantics of int! a; was quite different from an optional when it was introduced. The use of ! was exactly not to confuse it with an optional. Now it's so much closer to the "common" optional that a change to int? a; makes perfect sense.

def -> alias follows a similar trajectory. Initially it would be used both for aliases and to define new attributes and distinct types. Then distinct type definition got its own separate definition, so it was more like an alias aside from the attributes. With attributes getting its own attrdef, what remained were only aliases, and changing the name to alias suddenly made perfect sense.

Both these changes make the language follow more common syntax conventions as well, which is a win.

The goal is for the syntax to feel straightforward and have as little "oh this is odd" as possible. This makes it both easier to learn and remember.

More like C

Even though neither alias nor int? exist in C, they're familiar from adjacent languages. I believe this will make C3 feel "more like C", even though the changes are not in C.

Similarly, removing novel constructs like the expression block reduces the divergence from C syntax.

C3 0.7.1 and onwards

Where C3 0.7.0 has been a big cleanup release, it doesn't have that many improvements to general quality of life aside from the @format attribute.

I am really looking forward to inlining and resolving more function contracts on the caller side. In the first pass it will catch some low hanging fruit such as passing invalid constants, but this can be gradually improved to catch even more bugs.

Another thing is improving the standard library. There are additions I'd like to do both to the collections and to threads.

There are also a number of minor improvements to the compiler that would add to the quality of life for some uses. On top of this the inline asm is in need of bug fixes and cleanup.

Outside of the language and the compiler, a good docgen is needed.

But as for language changes, there is nothing planned, just incremental improvements to what's already there.

Full changelist for 0.7.0

Here is the full changelist for 0.6.8 to 0.7.0. Obviously if we'd bundle all changes from 0.6.1 to 0.7.0 the list would be MUCH bigger :D

Changes / improvements

  • Removed Foo { 1, 2 } initializer.
  • Changed Foo(<int>) to Foo {int}.
  • Removed {| |} expression blocks.
  • Removed macro &ref and $varef parameters.
  • Removed $vaexpr(0) syntax in favour of $vaexpr[0]
  • Enum does not cast to/from an integer (its ordinal).
  • Removed use of void! for main, test and benchmark functions.
  • Removed $or, $and, $concat compile time functions.
  • Removed @adhoc attribute.
  • Disallow inline use of nested generics (e.g. List{List{int}}.
  • Remove .allocator = allocator syntax for functions.
  • Remove @operator(construct).
  • Removal of "any-switch".
  • Allow swizzling assign, eg. abc.xz += { 5, 10 };
  • Added $$wstr16 and $$wstr32 builtins.
  • $foreach "()" replaced by trailing ":" $foreach ($x, $y : $foo) -> $foreach $x, $y : $foo:
  • $for "()" replaced by trailing ":" $for (var $x = 0; $x < FOO; $x++) -> $for var $x = 0; $x < FOO; $x++:
  • $switch "()" replaced by trailing ":" $switch ($Type) -> $switch $Type:
  • Empty $switch requires trailing ":" $switch -> $switch:
  • Rename @return! to @return? and change syntax to require ":" after faults.
  • Remove if (catch foo) { case ... } syntax.
  • Remove [?] syntax.
  • Change int! to int? syntax.
  • New fault declaration using faultdef.
  • Enum associated values can reference the calling enum.
  • Improve error message on foo ?? io::EOF with missing '?' #2036
  • Make @public import recursive. #2018
  • Fault nameof prefixes the first last module path, for instance std::io::EOF is rendered as io::EOF.
  • Rename def to alias.
  • Change distinct -> typedef.
  • Order of attribute declaration is changed for alias.
  • Added LANGUAGE_DEV_VERSION env constant.
  • Rename anyfault -> fault.
  • !!foo now works same as as ! ! foo.
  • Temp allocator now supports more than 2 in-flight stacks.
  • Printing stacktrace uses its own temp allocator.
  • Allow inferred type on body parameters. E.g. @stack_mem(1024; alloc) { ... };
  • Use @pool_init() to set up a temp pool on a thread. Only the main thread has implicit temp pool setup.
  • tmem is now a variable.
  • Compile test and benchmark functions when invoking --lsp #2058.
  • Added @format attribute for compile time printf validation #2057.
  • Formatter no longer implicitly converts enums to ordinals.

Fixes

  • Fix address sanitizer to work on MachO targets (e.g. MacOS).
  • Post and pre-decrement operators switched places for vector elements #2010.
  • Aliases were incorrectly considered compile time constants.
  • FreeBSD libc stat definitions were incorrect.
  • Atomic max was incorrect.
  • "+".to_float() would panic.
  • import can now both be @public and @norecurse.
  • Crash when trying to convert a struct slice to a vector #2039.
  • Crash resolving a method on Foo[2] when Foo is distinct #2042.
  • Bug due to missing cast when doing $i[$x] = $z.
  • Incorrectly allowed getting pointer to a macro #2049.
  • &self not runtime null-checked in macro #1827.
  • Bug when printing a boolean value as an integer using printf.
  • Show error when a generic module contains a self-generic type.
  • "Single module" was not enforced when creating a static library using as a project target.

Stdlib changes

  • new_* functions in general moved to version without new_ prefix.
  • string::new_from_* changed to string::from_*.
  • String.to_utf16_copy and related changed to String.to_utf16.
  • String.to_utf16_tcopy and related changed to String.to_temp_utf16
  • mem::temp_new changed to mem::tnew.
  • mem::temp_alloc and related changed to mem::talloc.
  • mem::temp_new_array changed to mem::temp_array.
  • Add ONHEAP variants for List/HashMap for initializing global maps on the heap.
  • Remove Vec2 and other aliases from std::math. Replace .length_sq() with sq_magnitude()
  • Change all hash functions to have a common hash function.
  • @wstring, @wstring32, @char32 and @char16 compile time macros added.
  • Updates to Atomic to handle distinct types and booleans.
  • Added math::iota.
  • @pool no longer takes an argument.
  • Allocator interface removes mark and reset.
  • DynamicArenaAllocator has changed init function.
  • Added BackedArenaAllocator which is allocated to a fixed size, then allocates on the backing allocator and supports mark/reset.
  • AnyList now also defaults to the temp allocator.
  • os::getcwd and os::get_home_dir requires an explicit allocator.
  • file::load_new and file::load_path_new removed.
  • os::exit and os::fastexit added.

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

Some tips organizing code in C3

Originally from: https://c3.handmade.network/blog/p/9008-some_tips_organizing_code_in_c3

I occasionally get requests for aliasing modules in C3, so something like import foo::mylongname = ml;. Often the use case is to get a shorter prefix for their types, as they're doing foo::mylongname::MyType everywhere.

This is a fundamental misunderstanding of how you are supposed to organize code in C3.

The reason for mandatory module prefixing of functions and globals

In C, we typically have some name spacing scheme in place. For example one might have settings_parse_options(...) settings_print_help(...), settings_apply_options(...) and so on.

What C3 tries to do is to formalize this informal style using the last component of the module path:

import app::settings;

fn void something(String[] args)
{
    SettingOptions? options = settings::parse_options(args);
    if (catch options)
    {
        settings::print_help();
        os::exit(1);
    }
    ...
}

How to pick a good module name

This scheme puts a lot of responsibility on the author of the module to pick a submodule name that actually is succinct and nice enough to use repeatedly. The submodule is not just for organizing code, but to be part of the identifier.

In a language where imports are typically aliased, the submodule name is fairly insignificant, but in C3 it should give you all the necessary context without being overly verbose. This is also why the C3 standard library generally goes for shorter names and the occasional abbreviation.

So to repeat: it is bad practice in C3 to use long submodule names. However, the top module should ensure that the name is unique.

module com.foo.mylib.io;
       ------------- --
       ^ unique part  ^ prefix to keep succinct

Good type names

Type names does not require a prefix, because they are intended to keep the initial prefix as part of the name, e.g. SettingOptions, HttpContext, IntList etc. It is bad practice to add the submodule. So settings::Options and http::Context are both bad in the context of C3.

So a good C3 type name should be clearly distinguishable all others. So no http::Context vs parsing::Context and so on, it's just bad practice. And again this violates how you'd typically write it in C: http::Context is http_Context, which few will use.

Organizing your own code

In C3, prefer making modules as big as possible inside of your app. There is no intrinsic gain in creating module hierarchies. You can use submodules to delineate subsystems in your app, but that should be done as need arises, not up front.

This is also why types should be unique: if you rely on submodule names to disambiguate them, then you're forced to create submodules you do not need.

Summary

Coming from other languages with other conventions, it's natural to not find best practices in a new language, but try to stick with these rules and you'll probably feel a lot less need for module name aliasing.

Let's summarize the above in a few do/don'ts

Don't

Create module names that people will want to alias:

module com::mysite::thecanvasrenderinglib;

fn Canvas? create_a_new_rendering_canvas()
{ ... }

module foo;
import com::mysite::thecanvasrenderinglib;

fn void test()
{
    Canvas? c = thecanvasrenderinglib::create_a_new_rendering_canvas();
    ...
}
Do

Create submodule names that are succinct and work with the function name:

module com::mysite::thecanvasrenderinglib::canvas;

fn Canvas? create_new_for_rendering()
{ ... }

module foo;
import com::mysite::thecanvasrenderinglib;

fn void test()
{
    Canvas? c = canvas::create_new_for_rendering();
    ...
}
Don't

Use type names that are ambiguous:

module abc::http;

struct Context { ... }

module abc::file;

struct Context { ... }

module foo;
import abc;

fn void main()
{
  http::Context hc = http::get_context();
  file::Context fc = hc.get_file_context();  
  ...
}
Do

Use type names that are distinct and clear what they're used for:

module abc::http;

struct HttpContext { ... }

module abc::file;

struct FileContext { ... }

module foo;
import abc;

fn void main()
{
  HttpContext hc = http::get_context();
  FileContext fc = hc.get_file_context();  
  ...
}
Don't

Add unnecessary module paths.

import std::io;

fn void main()
{
    std::io::File f = std::io::file::open("foo.txt", "rb");
    char[]? data = std::io::read_fully(mem, &f);
    ...
}
Do

Use as short module paths as permitted:

import std::io;

fn void main()
{
    File f = file::open("foo.txt", "rb");
    char[]? data = io::read_fully(mem, &f);
    ...
}

And that's it. Happy coding!

Comments


Comment by Christoffer Lernö

I occasionally get requests for aliasing modules in C3, so something like import foo::mylongname = ml;. Often the use case is to get a shorter prefix for their types, as they're doing foo::mylongname::MyType everywhere.

This is a fundamental misunderstanding of how you are supposed to organize code in C3.

The reason for mandatory module prefixing of functions and globals

In C, we typically have some name spacing scheme in place. For example one might have settings_parse_options(...) settings_print_help(...), settings_apply_options(...) and so on.

What C3 tries to do is to formalize this informal style using the last component of the module path:

import app::settings;

fn void something(String[] args)
{
    SettingOptions? options = settings::parse_options(args);
    if (catch options)
    {
        settings::print_help();
        os::exit(1);
    }
    ...
}

How to pick a good module name

This scheme puts a lot of responsibility on the author of the module to pick a submodule name that actually is succinct and nice enough to use repeatedly. The submodule is not just for organizing code, but to be part of the identifier.

In a language where imports are typically aliased, the submodule name is fairly insignificant, but in C3 it should give you all the necessary context without being overly verbose. This is also why the C3 standard library generally goes for shorter names and the occasional abbreviation.

So to repeat: it is bad practice in C3 to use long submodule names. However, the top module should ensure that the name is unique.

module com.foo.mylib.io;
       ------------- --
       ^ unique part  ^ prefix to keep succinct

Good type names

Type names does not require a prefix, because they are intended to keep the initial prefix as part of the name, e.g. SettingOptions, HttpContext, IntList etc. It is bad practice to add the submodule. So settings::Options and http::Context are both bad in the context of C3.

So a good C3 type name should be clearly distinguishable all others. So no http::Context vs parsing::Context and so on, it's just bad practice. And again this violates how you'd typically write it in C: http::Context is http_Context, which few will use.

Organizing your own code

In C3, prefer making modules as big as possible inside of your app. There is no intrinsic gain in creating module hierarchies. You can use submodules to delineate subsystems in your app, but that should be done as need arises, not up front.

This is also why types should be unique: if you rely on submodule names to disambiguate them, then you're forced to create submodules you do not need.

Summary

Coming from other languages with other conventions, it's natural to not find best practices in a new language, but try to stick with these rules and you'll probably feel a lot less need for module name aliasing.

Let's summarize the above in a few do/don'ts

Don't

Create module names that people will want to alias:

module com::mysite::thecanvasrenderinglib;

fn Canvas? create_a_new_rendering_canvas()
{ ... }

module foo;
import com::mysite::thecanvasrenderinglib;

fn void test()
{
    Canvas? c = thecanvasrenderinglib::create_a_new_rendering_canvas();
    ...
}
Do

Create submodule names that are succinct and work with the function name:

module com::mysite::thecanvasrenderinglib::canvas;

fn Canvas? create_new_for_rendering()
{ ... }

module foo;
import com::mysite::thecanvasrenderinglib;

fn void test()
{
    Canvas? c = canvas::create_new_for_rendering();
    ...
}
Don't

Use type names that are ambiguous:

module abc::http;

struct Context { ... }

module abc::file;

struct Context { ... }

module foo;
import abc;

fn void main()
{
  http::Context hc = http::get_context();
  file::Context fc = hc.get_file_context();  
  ...
}
Do

Use type names that are distinct and clear what they're used for:

module abc::http;

struct HttpContext { ... }

module abc::file;

struct FileContext { ... }

module foo;
import abc;

fn void main()
{
  HttpContext hc = http::get_context();
  FileContext fc = hc.get_file_context();  
  ...
}
Don't

Add unnecessary module paths.

import std::io;

fn void main()
{
    std::io::File f = std::io::file::open("foo.txt", "rb");
    char[]? data = std::io::read_fully(mem, &f);
    ...
}
Do

Use as short module paths as permitted:

import std::io;

fn void main()
{
    File f = file::open("foo.txt", "rb");
    char[]? data = io::read_fully(mem, &f);
    ...
}

And that's it. Happy coding!

An early 0.6.8 release

Originally from: https://c3.handmade.network/blog/p/8996-an_early_0.6.8_release

Release 0.6.8

0.6.8 is to be the last release before 0.7.0 and had a short cycle of development - only two weeks. As usual it has fixes, but more importantly, it prepares for 0.7.0.

So let's dive into what's new in this release!

"New generics" - List{int}

0.6.8 has the flag --enable-new-generics, which implements the new syntax for generics, from, for example, List(<int>) a; to List{int} a;. With this flag the compound literal syntax of Foo { 1, 2 } is disallowed, instead it uses C-style (Foo) { 1, 2 }.

Expression blocks and @operator(construct) deprecated

The expression blocks {| |} helps you turn any sequence of statements into an expression. This would be immensely useful in C-like macros, but with semantic macros in C3 it's much less so, to the point that it's being removed in 0.7.0. The feature isn't bad, it just hasn't been useful enough to keep it.

The @operator(construct) which allowed static method-like invocations on types is deprecated. It was introduced in 0.6.6 as an experiment, but will ultimately be removed in the next version.

Changes to the stdlib

After a lot of testing during the last year, we're retiring the new_* and temp_ functions. The old pattern of foo.new_init becomes foo.init(mem) with "mem" being the default heap allocator, and initializers with temp allocators usin foo.tinit().

Tooling changes

The compiler now places temporaries in .build by default when using compile. There is a --suppress-run for benchmark and test targets to not run them immediately. There is also a --build-env option to get some information about the build environment.

What's next:

The release of 0.7.0 is planned for early April, with the main feature being the new generics and and standard library "standardization" aroud "init(mem)". The other big thing is of course the removal of deprecated functionality.

There are some long standing issues, like completing the asm functionality and resolving some of the semantics around #hash arguments that hopefully will be fixed in 0.7.0 as well.

Even though focus is on 0.7.0, a 0.6.9 version might appear if there is an urgent need for bug fixes in 0.6.x.

Here is the full change list:

0.6.8 Change list

Changes / improvements

  • Increase precedence of (Foo) { 1, 2 }
  • Add --enable-new-generics to enable Foo{int} generic syntax.
  • {| |} expression blocks deprecated.
  • c3c --test-leak-report flag for displaying full memory lead report if any
  • Output into /.build/obj/ by default.
  • Output llvm/asm into llvm/ and asm/ by default.
  • Add flag --suppress-run. For commands which may run executable after building, skip the run step. #1931
  • Add --build-env for build environment information.
  • Deprecation of @operator(construct).

Fixes

  • Bug appearing when ?? was combined with boolean in some cases.
  • Test runner --test-disable-sort didn't work, c3c was expecting --test-nosort
  • Test runner with tracking allocator assertion at failed test #1963
  • Test runner with tracking allocator didn't properly handle teardown_fn
  • Correctly give an error if a character literal contains a line break.
  • Implicitly unwrapped optional value in defer incorrectly copied #1982.
  • Crash when trying to define a method macro that isn't @construct but has no arguments.
  • Regression, .gitkeep files were generated incorrectly.
  • Aliases are now correctly handled as if they were variables/functions in regards to namespacing and accept @builtin.
  • Correctly handle in/out when interacting with inout.
  • Don't delete .o files not produced by the compiler.
  • Fix optional jumps in expression lists, #1942.
  • Several fixes for .o files and -o output, improving handling and naming.
  • Fix bug casting bool to int to other int #1995.
  • @if declarations were missing from -P output #1973.
  • Check exe and lib output so -o works with directories.
  • Swizzling an inline vector in a struct would cause a crash.
  • Fixed error and poor error message when using an invalid target name.

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ö

Release 0.6.8

0.6.8 is to be the last release before 0.7.0 and had a short cycle of development - only two weeks. As usual it has fixes, but more importantly, it prepares for 0.7.0.

So let's dive into what's new in this release!

"New generics" - List{int}

0.6.8 has the flag --enable-new-generics, which implements the new syntax for generics, from, for example, List(<int>) a; to List{int} a;. With this flag the compound literal syntax of Foo { 1, 2 } is disallowed, instead it uses C-style (Foo) { 1, 2 }.

Expression blocks and @operator(construct) deprecated

The expression blocks {| |} helps you turn any sequence of statements into an expression. This would be immensely useful in C-like macros, but with semantic macros in C3 it's much less so, to the point that it's being removed in 0.7.0. The feature isn't bad, it just hasn't been useful enough to keep it.

The @operator(construct) which allowed static method-like invocations on types is deprecated. It was introduced in 0.6.6 as an experiment, but will ultimately be removed in the next version.

Changes to the stdlib

After a lot of testing during the last year, we're retiring the new_* and temp_ functions. The old pattern of foo.new_init becomes foo.init(mem) with "mem" being the default heap allocator, and initializers with temp allocators usin foo.tinit().

Tooling changes

The compiler now places temporaries in .build by default when using compile. There is a --suppress-run for benchmark and test targets to not run them immediately. There is also a --build-env option to get some information about the build environment.

What's next:

The release of 0.7.0 is planned for early April, with the main feature being the new generics and and standard library "standardization" aroud "init(mem)". The other big thing is of course the removal of deprecated functionality.

There are some long standing issues, like completing the asm functionality and resolving some of the semantics around #hash arguments that hopefully will be fixed in 0.7.0 as well.

Even though focus is on 0.7.0, a 0.6.9 version might appear if there is an urgent need for bug fixes in 0.6.x.

Here is the full change list:

0.6.8 Change list

Changes / improvements

  • Increase precedence of (Foo) { 1, 2 }
  • Add --enable-new-generics to enable Foo{int} generic syntax.
  • {| |} expression blocks deprecated.
  • c3c --test-leak-report flag for displaying full memory lead report if any
  • Output into /.build/obj/ by default.
  • Output llvm/asm into llvm/ and asm/ by default.
  • Add flag --suppress-run. For commands which may run executable after building, skip the run step. #1931
  • Add --build-env for build environment information.
  • Deprecation of @operator(construct).

Fixes

  • Bug appearing when ?? was combined with boolean in some cases.
  • Test runner --test-disable-sort didn't work, c3c was expecting --test-nosort
  • Test runner with tracking allocator assertion at failed test #1963
  • Test runner with tracking allocator didn't properly handle teardown_fn
  • Correctly give an error if a character literal contains a line break.
  • Implicitly unwrapped optional value in defer incorrectly copied #1982.
  • Crash when trying to define a method macro that isn't @construct but has no arguments.
  • Regression, .gitkeep files were generated incorrectly.
  • Aliases are now correctly handled as if they were variables/functions in regards to namespacing and accept @builtin.
  • Correctly handle in/out when interacting with inout.
  • Don't delete .o files not produced by the compiler.
  • Fix optional jumps in expression lists, #1942.
  • Several fixes for .o files and -o output, improving handling and naming.
  • Fix bug casting bool to int to other int #1995.
  • @if declarations were missing from -P output #1973.
  • Check exe and lib output so -o works with directories.
  • Swizzling an inline vector in a struct would cause a crash.
  • Fixed error and poor error message when using an invalid target name.

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

It's February and time for a new C3 release: 0.6.7

Originally from: https://c3.handmade.network/blog/p/8994-it%2527s_february_and_time_for_a_new_c3_release__0.6.7

0.6.7 has been interesting. There has been some significant additions to the language that opens for new solutions. Also because it's now finally possible to manipulate compile time arrays in a simple way, there are some simple but useful compile time tricks that now is available. For example, string manipulation at compile time.

So let's have look at the biggest changes.

Compile time improvements

As previously mentioned, compile time arrays can now be mutated. This allows things like $arr[$i] = 123. And at this point, the only thing still not possible to mutate at compile time are struct fields.

In addition to this the concatenation operator, +++, now works on all types of arrays, even ones defined with gaps.

"Inline" enums

It's now possible to set enums to have its ordinal or an associated value marked "inline".

The feature allows an enum value to implicitly convert to that value:

enum Foo : int (inline String name, int y, int z)
{
  ABC = { "Hello", 1, 2 },
  DEF = { "World", 2, 3 },
}

fn void main()
{
  String hello = Foo.ABC;
  io::printn(hello); // Prints "Hello"
}

Short function syntax combined with macros

The short function syntax handles macros with trailing bodies in a special way, allowing the macro's trailing body to work as the body of the function, which simplifies the code when a function starts with a macro with a body:

// 0.6.6
fn Path! new_cwd(Allocator allocator = allocator::heap())
{
  @pool(allocator)
  {
    return new(os::getcwd(allocator::temp()), allocator);
  };
}

// 0.6.7
fn Path! new_cwd(Allocator allocator = allocator::heap()) => @pool()
{
  return new(os::getcwd(allocator::temp()), allocator);
}

C style compound literals

C style compound literals is making a comeback, it's now possible to do (int[2]) { 1, 2 } as an alternative to int[2] { 1, 2 }.

This is in preparation for possibly changing the syntax for generic modules. Regardless, this style is probably here to stay.

Improvements to runtime and unit test error checking

Unaligned loads will now be detected in safe mode, and the test runner has been improved to check for memory leaks, and the test runner has been greatly updated.

A new module test in the standard library adds various test utilities for unit testing.

Type inference on ??

Previously the expression MyEnum f = foo() ?? MyEnum.ABC; would require the full enum name as in this example but this has been improved so that in 0.6.7 MyEnum f = foo() ? ABC; is sufficient. This also affects all other types of inference on the right hand side of ??

Better bytes printing in the stdlib

0.6.7 adds %h and %H for printing binary data in hexadecimal.

Other stdlib improvements

  1. Channels for use with threaded code were added.
  2. @select to perform constant a ? x : y at compile time yielding a constant result.
  3. HashMap is now implementing Printable
  4. allocator::wrap allows quickly creating an arena allocator from a collection of bytes.
  5. Nolibc math code was expanded with some additional functions.

Bug fixes

0.6.7, like 0.6.6, contains about 50 bug fixes of various severity.

What's next?

There is an ongoing discussion in regards to generic syntax. (< >) works, but it not particularly lightweight. Some other alternatives, such as < > ( ) and [ ] suffer from ambiguities, so other options are investigated, such as $() and {}

Also in a quest to simplify the language, it's an open question whether {| |} should be removed or not. The expression blocks have their uses, but significantly less in C3 with semantic macros than it would have in C.

Other than that, expansion of the standard library is a priority.

With 0.7.0 scheduled for April, there is pretty much only room for one more 0.6.x release: 0.6.8.

I'm looking forward to cleaning out deprecated code when 0.7.0 finally comes around.

Here is the full change list:

Changes / improvements

  • Contracts @require/@ensure are no longer treated as conditionals, but must be explicitly bool.
  • Add win-debug setting to be able to pick dwarf for output #1855.
  • Error on switch case fallthough if there is more than one newline #1849.
  • Added flags to c3c project view to filter displayed properties
  • Compile time array assignment #1806.
  • Allow +++ to work on all types of arrays.
  • Allow (int[*]) { 1, 2 } cast style initialization.
  • Experimental change from [*] to [?]
  • Warn on if-catch with just a default case.
  • Compile time array inc/dec.
  • Improve error message when using ',' in struct declarations. #1920
  • Compile time array assign ops, e.g. $c[1] += 3 #1890.
  • Add inline to enums #1819.
  • Cleaner error message when missing comma in struct initializer #1941.
  • Distinct inline void causes unexpected error if used in slice #1946.
  • Allow fn int test() => @pool() { return 1; } short function syntax usage #1906.
  • Test runner will also check for leaks.
  • Improve inference on ?? #1943.
  • Detect unaligned loads #1951.

Fixes

  • Fix issue requiring prefix on a generic interface declaration.
  • Fix bug in SHA1 for longer blocks #1854.
  • Fix lack of location for reporting lambdas with missing return statement #1857.
  • Compiler allows a generic module to be declared with different parameters #1856.
  • Fix issue with @const where the statement $foo = 1; was not considered constant.
  • Const strings and bytes were not properly converted to compile time bools.
  • Concatenating a const empty slice with another array caused a null pointer access.
  • Fix linux-crt and linux-crtbegin not getting recognized as a project paramater
  • Fix dues to crash when converting a const vector to another vector #1864.
  • Filter $exec output from \r, which otherwise would cause a compiler assert #1867.
  • Fixes to `"exec" use, including issue when compiling with MinGW.
  • Correctly check jump table size and be generous when compiling it #1877.
  • Fix bug where .min/.max would fail on a distinct int #1888.
  • Fix issue where compile time declarations in expression list would not be handled properly.
  • Issue where trailing body argument was allowed without type even though the definition specified it #1879.
  • Fix issues with @jump on empty default or only default #1893 #1894
  • Fixes miscompilation of nested @jump #1896.
  • Fixed STB_WEAK errors when using consts in macros in the stdlib #1871.
  • Missing error when placing a single statement for-body on a new row #1892.
  • Fix bug where in dead code, only the first statement would be turned into a nop.
  • Remove unused $inline argument to mem::copy.
  • Defer is broken when placed before a $foreach #1912.
  • Usage of @noreturn macro is type-checked as if it returns #1913.
  • Bug when indexing into a constant array at compile time.
  • Fixing various issues around shifts, like z <<= { 1, 2 }.
  • return (any)&foo would not be reported as an escaping variable if foo was a pointer or slice.
  • Incorrect error message when providing too many associated values for enum #1934.
  • Allow function types to have a calling convention. #1938
  • Issue with defer copying when triggered by break or continue #1936.
  • Assert when using optional as init or inc part in a for loop #1942.
  • Fix bigint hex parsing #1945.
  • bigint::from_int(0) throws assertion #1944.
  • write of qoi would leak memory.
  • Issue when having an empty Path or just "."
  • set_env would leak memory.
  • Fix issue where aligned bitstructs did not store/load with the given alignment.
  • Fix issue in GrowableBitSet with sanitizers.
  • Fix issue in List with sanitizers.
  • Circumvent Aarch64 miscompilations of atomics.
  • Fixes to ByteBuffer allocation/free.
  • Fix issue where compiling both for asm and object file would corrupt the obj file output.
  • Fix poll and POLL_FOREVER.
  • Missing end padding when including a packed struct #1966.
  • Issue when scalar expanding a boolean from a conditional to a bool vector #1954.
  • Fix issue when parsing bitstructs, preventing them from implementing interfaces.
  • Regression String! a; char* b = a.ptr; would incorrectly be allowed.
  • Fix issue where target was ignored for projects.
  • Fix issue when dereferencing a constant string.
  • Fix problem where a line break in a literal was allowed.

Stdlib changes

  • Added '%h' and '%H' for printing out binary data in hexadecimal using the formatter.
  • Added weakly linked __powidf2
  • Added channels for threads.
  • New std::core::test module for unit testing machinery.
  • New unit test default runner.
  • Added weakly linked fmodf.
  • Add @select to perform the equivalent of a ? x : y at compile time.
  • HashMap is now Printable.
  • Add allocator::wrap to create an arena allocator on the stack from bytes.

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ö

0.6.7 has been interesting. There has been some significant additions to the language that opens for new solutions. Also because it's now finally possible to manipulate compile time arrays in a simple way, there are some simple but useful compile time tricks that now is available. For example, string manipulation at compile time.

So let's have look at the biggest changes.

Compile time improvements

As previously mentioned, compile time arrays can now be mutated. This allows things like $arr[$i] = 123. And at this point, the only thing still not possible to mutate at compile time are struct fields.

In addition to this the concatenation operator, +++, now works on all types of arrays, even ones defined with gaps.

"Inline" enums

It's now possible to set enums to have its ordinal or an associated value marked "inline".

The feature allows an enum value to implicitly convert to that value:

enum Foo : int (inline String name, int y, int z)
{
  ABC = { "Hello", 1, 2 },
  DEF = { "World", 2, 3 },
}

fn void main()
{
  String hello = Foo.ABC;
  io::printn(hello); // Prints "Hello"
}

Short function syntax combined with macros

The short function syntax handles macros with trailing bodies in a special way, allowing the macro's trailing body to work as the body of the function, which simplifies the code when a function starts with a macro with a body:

// 0.6.6
fn Path! new_cwd(Allocator allocator = allocator::heap())
{
  @pool(allocator)
  {
    return new(os::getcwd(allocator::temp()), allocator);
  };
}

// 0.6.7
fn Path! new_cwd(Allocator allocator = allocator::heap()) => @pool()
{
  return new(os::getcwd(allocator::temp()), allocator);
}

C style compound literals

C style compound literals is making a comeback, it's now possible to do (int[2]) { 1, 2 } as an alternative to int[2] { 1, 2 }.

This is in preparation for possibly changing the syntax for generic modules. Regardless, this style is probably here to stay.

Improvements to runtime and unit test error checking

Unaligned loads will now be detected in safe mode, and the test runner has been improved to check for memory leaks, and the test runner has been greatly updated.

A new module test in the standard library adds various test utilities for unit testing.

Type inference on ??

Previously the expression MyEnum f = foo() ?? MyEnum.ABC; would require the full enum name as in this example but this has been improved so that in 0.6.7 MyEnum f = foo() ? ABC; is sufficient. This also affects all other types of inference on the right hand side of ??

Better bytes printing in the stdlib

0.6.7 adds %h and %H for printing binary data in hexadecimal.

Other stdlib improvements

  1. Channels for use with threaded code were added.
  2. @select to perform constant a ? x : y at compile time yielding a constant result.
  3. HashMap is now implementing Printable
  4. allocator::wrap allows quickly creating an arena allocator from a collection of bytes.
  5. Nolibc math code was expanded with some additional functions.

Bug fixes

0.6.7, like 0.6.6, contains about 50 bug fixes of various severity.

What's next?

There is an ongoing discussion in regards to generic syntax. (< >) works, but it not particularly lightweight. Some other alternatives, such as < > ( ) and [ ] suffer from ambiguities, so other options are investigated, such as $() and {}

Also in a quest to simplify the language, it's an open question whether {| |} should be removed or not. The expression blocks have their uses, but significantly less in C3 with semantic macros than it would have in C.

Other than that, expansion of the standard library is a priority.

With 0.7.0 scheduled for April, there is pretty much only room for one more 0.6.x release: 0.6.8.

I'm looking forward to cleaning out deprecated code when 0.7.0 finally comes around.

Here is the full change list:

Changes / improvements

  • Contracts @require/@ensure are no longer treated as conditionals, but must be explicitly bool.
  • Add win-debug setting to be able to pick dwarf for output #1855.
  • Error on switch case fallthough if there is more than one newline #1849.
  • Added flags to c3c project view to filter displayed properties
  • Compile time array assignment #1806.
  • Allow +++ to work on all types of arrays.
  • Allow (int[*]) { 1, 2 } cast style initialization.
  • Experimental change from [*] to [?]
  • Warn on if-catch with just a default case.
  • Compile time array inc/dec.
  • Improve error message when using ',' in struct declarations. #1920
  • Compile time array assign ops, e.g. $c[1] += 3 #1890.
  • Add inline to enums #1819.
  • Cleaner error message when missing comma in struct initializer #1941.
  • Distinct inline void causes unexpected error if used in slice #1946.
  • Allow fn int test() => @pool() { return 1; } short function syntax usage #1906.
  • Test runner will also check for leaks.
  • Improve inference on ?? #1943.
  • Detect unaligned loads #1951.

Fixes

  • Fix issue requiring prefix on a generic interface declaration.
  • Fix bug in SHA1 for longer blocks #1854.
  • Fix lack of location for reporting lambdas with missing return statement #1857.
  • Compiler allows a generic module to be declared with different parameters #1856.
  • Fix issue with @const where the statement $foo = 1; was not considered constant.
  • Const strings and bytes were not properly converted to compile time bools.
  • Concatenating a const empty slice with another array caused a null pointer access.
  • Fix linux-crt and linux-crtbegin not getting recognized as a project paramater
  • Fix dues to crash when converting a const vector to another vector #1864.
  • Filter $exec output from \r, which otherwise would cause a compiler assert #1867.
  • Fixes to `"exec" use, including issue when compiling with MinGW.
  • Correctly check jump table size and be generous when compiling it #1877.
  • Fix bug where .min/.max would fail on a distinct int #1888.
  • Fix issue where compile time declarations in expression list would not be handled properly.
  • Issue where trailing body argument was allowed without type even though the definition specified it #1879.
  • Fix issues with @jump on empty default or only default #1893 #1894
  • Fixes miscompilation of nested @jump #1896.
  • Fixed STB_WEAK errors when using consts in macros in the stdlib #1871.
  • Missing error when placing a single statement for-body on a new row #1892.
  • Fix bug where in dead code, only the first statement would be turned into a nop.
  • Remove unused $inline argument to mem::copy.
  • Defer is broken when placed before a $foreach #1912.
  • Usage of @noreturn macro is type-checked as if it returns #1913.
  • Bug when indexing into a constant array at compile time.
  • Fixing various issues around shifts, like z <<= { 1, 2 }.
  • return (any)&foo would not be reported as an escaping variable if foo was a pointer or slice.
  • Incorrect error message when providing too many associated values for enum #1934.
  • Allow function types to have a calling convention. #1938
  • Issue with defer copying when triggered by break or continue #1936.
  • Assert when using optional as init or inc part in a for loop #1942.
  • Fix bigint hex parsing #1945.
  • bigint::from_int(0) throws assertion #1944.
  • write of qoi would leak memory.
  • Issue when having an empty Path or just "."
  • set_env would leak memory.
  • Fix issue where aligned bitstructs did not store/load with the given alignment.
  • Fix issue in GrowableBitSet with sanitizers.
  • Fix issue in List with sanitizers.
  • Circumvent Aarch64 miscompilations of atomics.
  • Fixes to ByteBuffer allocation/free.
  • Fix issue where compiling both for asm and object file would corrupt the obj file output.
  • Fix poll and POLL_FOREVER.
  • Missing end padding when including a packed struct #1966.
  • Issue when scalar expanding a boolean from a conditional to a bool vector #1954.
  • Fix issue when parsing bitstructs, preventing them from implementing interfaces.
  • Regression String! a; char* b = a.ptr; would incorrectly be allowed.
  • Fix issue where target was ignored for projects.
  • Fix issue when dereferencing a constant string.
  • Fix problem where a line break in a literal was allowed.

Stdlib changes

  • Added '%h' and '%H' for printing out binary data in hexadecimal using the formatter.
  • Added weakly linked __powidf2
  • Added channels for threads.
  • New std::core::test module for unit testing machinery.
  • New unit test default runner.
  • Added weakly linked fmodf.
  • Add @select to perform the equivalent of a ? x : y at compile time.
  • HashMap is now Printable.
  • Add allocator::wrap to create an arena allocator on the stack from bytes.

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

Another monthly release: C3 0.6.6 is here

Originally from: https://c3.handmade.network/blog/p/8983-another_monthly_release__c3_0.6.6_is_here

The 0.6.6 release is surprisingly on time despite (or perhaps thanks to?) the Christmas holidays. It's a new year, and this summer C3 will turn 6. By April, version 0.7.0 will be released, removing deprecated code. The plan is to have one "dot one" release each year until 1.0 is reached (and if everything goes according to plan, the version after 0.9 will be 1.0).

But let's dive into what's new in this release!

Enum from_ordinal / ordinal changes

A longstanding pain point with enums has been modeling C enums with "gaps," as in this example:

typedef enum
{
    ABC = 42,
    GHJ = 101
} MyEnum;

A workaround in C3 using associated values like this doesn't feel ideal:

enum MyEnum : (int val)
{
    ABC = 42,
    GHJ = 101
}

This is because you can get the ordinal of the enum with a cast, e.g., (int)MyEnum.ABC, whereas retrieving the an associated value requires access, like MyEnum.ABC.val.

There are some ideas to bridge this gap, but the fact that the enums would cast to their ordinal by default made it hard to create a reasonable feature for it.

To address this, casts are now deprecated and replaced by MyEnum.from_ordinal(value). This type method is essentially a no-op, as is converting an enum to a number with .ordinal (e.g. MyEnum.ABC.ordinal).

This puts something like MyEnum.ABC.val and MyEnum.ABC.ordinal on, more equal footing which will make it easier to implement possible solutions to the gap enum problem.

No more & macro arguments

Macros differ from functions by allowing 5 (!) additional types of arguments:

  1. Compile-time constant arguments ($foo)
  2. Compile-time type arguments ($Type)
  3. Unevaluated expression arguments (#expr)
  4. And, finally, reference arguments (&ref)

With 0.6.6 &ref arguments become deprecated. With #expr arguments actually already being able to do what &ref does, this is not a loss in functionality. It does put somewhat more effort in using an #expr argument in terms of checking, but with the fairly marginal use of &ref in the standard library it didn't seem like a loss.

The main advantage for dropping &ref is that it no longer needs to be explained or motivated. It also simplifies the compiler in places where it ended up adding surprising complexities – but those simplifications can't be done until 0.7 when &ref is removed (rather than deprecated).

More important than the change is that the removal of & (which has been around for as long as the macros), marks the beginning of consolidating the language: removing things that have proven over time to be superfluous.

void rather than void! as default for main, tests and benchmarks

Another consolidation is the removal of void! as a return type for main.

Previously, it was possible to return an optional Empty value from main and have the compiler turn it into a 1 exit code.

This was problematic because it effectively discarded the Optional’s excuse. It also encouraged bad code which would just rethrow Optionals from functions called by main

void! was removed from @test functions for a similar reason. Actually panicking (with !! rather than !) would yield much better error messages for free. So again the choice between void and void! for test functions were not really helpful.

Starting from version 0.6.6, use void or int as the return type for main functions, and void for @test and @benchmark functions.

$foreach iterating over Strings

An oversight previously made it impossible to iterate over Strings and bytes. With 0.6.6, this now works correctly.

var declaring lambdas

Using var has been limited to macros in C3, but now they're also allowed in functions when declaring lambdas.

@operator(construct)

An experimental feature called construct is available from 0.6.6.

This allows a limited form of static methods on types. For example:

fn Foo Foo.new_with_bar(Bar b) @operator(construct)
{
    return { .b = b }; // Same as Foo { .b = b }
}

The limitation is that such a method must either return the type or a pointer to the type with the method. (So in the above example, either returning Foo or Foo* would be possible).

Consider this an experimental preview for now!

Standard Library Improvements The standard library has seen useful additions, including foreach-compatible iterators for HashMap and an URL parser.

The default hash functions have been improved, enhancing HashMap performance. There have also been other minor additions and changes.

Bug fixes

This version contains over 50 bug fixes, which is about twice from 0.6.5, but less than the record setting 0.6.2.

Refactoring

0.6.6 includes initial steps toward refactoring parts of the frontend representation to facilitate the addition of new backends. A C backend is planned for this year, making these improvements essential.

What's next?

The enums might finally get the "gap enum" usecase filled (pun intended), but it's not clear whether this will happen in 0.6.7 or will be pushed to later.

There is also a glaring hole in compile time evaluation where $i[1] = 2; isn't allowed, but $a = $i[1]; is. This will need to change, but to do so there needs to be some refactoring, which ties into the overall changes in the frontend representation to help the backend lowering, but also static analysis.

Users, despite work under the hood, should see very little of these changes, except maybe that some corner cases (like compile time subscript above) starts working.

There are some syntax tweaks coming up, but they should be very minor and probably not even be visible to most users.

More importantly though, is that C3 will see the beginning of work to prune unused features from the language, which will then eventually be removed with 0.7.0.

0.6.6 Change list

Here is the full change list

Changes / improvements

  • Split help into normal and "full" help, #1703
  • Removed 'headers' command line option.
  • Add enum.from_ordinal and fault.from_ordinal
  • Deprecate cast-style conversion from integer <-> enum.
  • Make deprecation an error in test mode.
  • Add --win-vs-dirs to override VS detection dirs.
  • Add "name" project property to override the name of the resulting binary. #1719
  • Improved add-project to take arguments.
  • Improve error reporting when using type names as the function argument #1750.
  • Improve ordering of method registration to support adding methods to generic modules with method constraints #1746
  • Support experimental @operator(construct) operator overload.
  • Allow using 'var' to declare lambdas in functions.
  • Add 'validation' setting and make dead code a warning.
  • Allow compile time $foreach iteration over constant Strings and bytes.
  • Improved error message when accessing @private from other modules #1769.
  • Include @name when searching for possible matches to name in the error message. #1779
  • Improve @param parse errors #1777
  • Improved #foo resolution inside of the compiler.
  • Deprecated '&' macro arguments.
  • Deprecate `fn void! main() type main functions.
  • Deprecate old void! @benchmark and @test functions.
  • Allow test runners to take String[] arguments.
  • Added --lsp output.
  • Improve the error message when running out of memory.
  • Allowed passing arguments to @test / @benchmark runners via c3c test[benchmark] -- -o --opt1 <arg1>
  • Handle bytes and string literals the same way in terms of zero termination.
  • Function comments are stored and displayed with -P.
  • Prevent #hash arguments from taking code that modifies ct variables. #1794
  • Make stringify to recursively enter #hash expressions #1834.

Fixes

  • Fix case trying to initialize a char[*]* from a String.
  • Fix Map & HashMap put_all_for_create not copying all elements, causing init_from_map to create incomplete copy.
  • Fix bug when a macro calling an extern function was called in another module also declaring and calling the same function. #1690
  • static-lib and dynamic-lib options from the command line now produces headers.
  • Fix bug outputting exported functions without predefined extname.
  • Fix problem where crt1 was linked for dynamic libraries on Linux and BSD. #1710
  • Fix CRT detection on Arch Linux.
  • Fix lexer allowing a trailing underscore (_) with hex and binary literals.
  • Fix --list-operators CLI command printing underscore (_) and hash (#).
  • Fix bug in temp allocator when temp memory is exhausted and allocation needs overaligned mem. #1715
  • Incorrectly handles distinct enums and pointers with '+=' and '-=' #1717.
  • Prevent DString from being initialized with "".
  • Fix bug in OnStackAllocator when freeing overallocated data. #1720
  • Use weak_odr rather than weak on Windows which seems to prevent issues such as #1704.
  • Use weak on dyn-symbols on Linux.
  • Fix crash on project.json not having an empty set of targets.
  • Miscompile when indexing an array with small unsigned types for enums.
  • Change CBool to be 1 byte.
  • any_to_int checks value to be int and no longer works with enum.
  • Add check in formatter printing "%c".
  • Fix bug where !! and ! was not recognized to jump out of the current scope.
  • Fix bug when including compile time parameters in trailing body more than once.
  • Fix issue with compiling a constant struct containing a string array in a local context.
  • Fix error where panic would not properly stop the program when stacktrace couldn't be printed #1751.
  • Macros with default arguments to &, # and type parameters didn't work as expected. #1754.
  • net::poll() with negative timeout behaved incorrectly.
  • Return type inference bugs with macros #1757
  • $defined in a global scope should accept testing normal macros.
  • Assert on add to uninitialized ct variable #1765.
  • Dynamic function lookup fails after changing type without dummy anycast #1761
  • $vasplat was allowed inside of a function when passed as an argument to a function.
  • Prohibit raw vaargs in regular functions with a function body.
  • Assert on certain slice to slice casts. #1768.
  • Fix vector float -> bool conversion.
  • Fix +a = 1 erronously being accepted.
  • Fix not freeing a zero length String
  • Macros with trailing bodys aren't allowed as the single statement after a while loop with no body #1772.
  • Deref subscripts as needed for macro ref method arguments. #1789
  • Change ordering to simplify adding methods to type in conditional modules.
  • #foo style arguments were not type checked when given a type. #1790
  • Bug when using +++ on value build a slice or array: the rhs cast was not done.
  • Fix bug preventing compile time slices from being iterated over with $foreach.
  • Fix bug with defer assignment in macro #1807.
  • Fix regression with swizzle references for vectors #1810.
  • Assert when partially initializing a constant struct containing a slice #1812.
  • Assert concatenating constant slices #1805.
  • Do not link "ld" on Linux with no libc.
  • Fix bug when multiple $else clauses followed an $if #1824.
  • Report the correct type as not having a method when access fails #1828.
  • Prevent temp arena scribbling from causing an asan warning. #1825
  • Fix bug where &i[0] = null was not detected to be an error #1833.

Stdlib changes

  • Increase BitWriter.write_bits limit up to 32 bits.
  • Updates to Slice2d, like get_xy and others.
  • Added iter() value_iter() and key_iter() to HashMap.
  • Add "tokenizer" to String.
  • Add "skip_empty" to split methods. Add split_to_buffer method.
  • Add @enum_from_value.
  • Updated hash function.
  • Added URL parser.
  • Added convenience functions to Maybe.
  • Added String.trim_left() and .trim_right().
  • Deprecation of several & macros.
  • Format functions for timedates.
  • Add @assert_leak() to assert on memory leaks in the scope.
  • Added double.set_high_word(), double.set_low_word(), and float.set_word().

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ö

The 0.6.6 release is surprisingly on time despite (or perhaps thanks to?) the Christmas holidays. It's a new year, and this summer C3 will turn 6. By April, version 0.7.0 will be released, removing deprecated code. The plan is to have one "dot one" release each year until 1.0 is reached (and if everything goes according to plan, the version after 0.9 will be 1.0).

But let's dive into what's new in this release!

Enum from_ordinal / ordinal changes

A longstanding pain point with enums has been modeling C enums with "gaps," as in this example:

typedef enum
{
    ABC = 42,
    GHJ = 101
} MyEnum;

A workaround in C3 using associated values like this doesn't feel ideal:

enum MyEnum : (int val)
{
    ABC = 42,
    GHJ = 101
}

This is because you can get the ordinal of the enum with a cast, e.g., (int)MyEnum.ABC, whereas retrieving the an associated value requires access, like MyEnum.ABC.val.

There are some ideas to bridge this gap, but the fact that the enums would cast to their ordinal by default made it hard to create a reasonable feature for it.

To address this, casts are now deprecated and replaced by MyEnum.from_ordinal(value). This type method is essentially a no-op, as is converting an enum to a number with .ordinal (e.g. MyEnum.ABC.ordinal).

This puts something like MyEnum.ABC.val and MyEnum.ABC.ordinal on, more equal footing which will make it easier to implement possible solutions to the gap enum problem.

No more & macro arguments

Macros differ from functions by allowing 5 (!) additional types of arguments:

  1. Compile-time constant arguments ($foo)
  2. Compile-time type arguments ($Type)
  3. Unevaluated expression arguments (#expr)
  4. And, finally, reference arguments (&ref)

With 0.6.6 &ref arguments become deprecated. With #expr arguments actually already being able to do what &ref does, this is not a loss in functionality. It does put somewhat more effort in using an #expr argument in terms of checking, but with the fairly marginal use of &ref in the standard library it didn't seem like a loss.

The main advantage for dropping &ref is that it no longer needs to be explained or motivated. It also simplifies the compiler in places where it ended up adding surprising complexities – but those simplifications can't be done until 0.7 when &ref is removed (rather than deprecated).

More important than the change is that the removal of & (which has been around for as long as the macros), marks the beginning of consolidating the language: removing things that have proven over time to be superfluous.

void rather than void! as default for main, tests and benchmarks

Another consolidation is the removal of void! as a return type for main.

Previously, it was possible to return an optional Empty value from main and have the compiler turn it into a 1 exit code.

This was problematic because it effectively discarded the Optional’s excuse. It also encouraged bad code which would just rethrow Optionals from functions called by main

void! was removed from @test functions for a similar reason. Actually panicking (with !! rather than !) would yield much better error messages for free. So again the choice between void and void! for test functions were not really helpful.

Starting from version 0.6.6, use void or int as the return type for main functions, and void for @test and @benchmark functions.

$foreach iterating over Strings

An oversight previously made it impossible to iterate over Strings and bytes. With 0.6.6, this now works correctly.

var declaring lambdas

Using var has been limited to macros in C3, but now they're also allowed in functions when declaring lambdas.

@operator(construct)

An experimental feature called construct is available from 0.6.6.

This allows a limited form of static methods on types. For example:

fn Foo Foo.new_with_bar(Bar b) @operator(construct)
{
    return { .b = b }; // Same as Foo { .b = b }
}

The limitation is that such a method must either return the type or a pointer to the type with the method. (So in the above example, either returning Foo or Foo* would be possible).

Consider this an experimental preview for now!

Standard Library Improvements The standard library has seen useful additions, including foreach-compatible iterators for HashMap and an URL parser.

The default hash functions have been improved, enhancing HashMap performance. There have also been other minor additions and changes.

Bug fixes

This version contains over 50 bug fixes, which is about twice from 0.6.5, but less than the record setting 0.6.2.

Refactoring

0.6.6 includes initial steps toward refactoring parts of the frontend representation to facilitate the addition of new backends. A C backend is planned for this year, making these improvements essential.

What's next?

The enums might finally get the "gap enum" usecase filled (pun intended), but it's not clear whether this will happen in 0.6.7 or will be pushed to later.

There is also a glaring hole in compile time evaluation where $i[1] = 2; isn't allowed, but $a = $i[1]; is. This will need to change, but to do so there needs to be some refactoring, which ties into the overall changes in the frontend representation to help the backend lowering, but also static analysis.

Users, despite work under the hood, should see very little of these changes, except maybe that some corner cases (like compile time subscript above) starts working.

There are some syntax tweaks coming up, but they should be very minor and probably not even be visible to most users.

More importantly though, is that C3 will see the beginning of work to prune unused features from the language, which will then eventually be removed with 0.7.0.

0.6.6 Change list

Here is the full change list

Changes / improvements

  • Split help into normal and "full" help, #1703
  • Removed 'headers' command line option.
  • Add enum.from_ordinal and fault.from_ordinal
  • Deprecate cast-style conversion from integer <-> enum.
  • Make deprecation an error in test mode.
  • Add --win-vs-dirs to override VS detection dirs.
  • Add "name" project property to override the name of the resulting binary. #1719
  • Improved add-project to take arguments.
  • Improve error reporting when using type names as the function argument #1750.
  • Improve ordering of method registration to support adding methods to generic modules with method constraints #1746
  • Support experimental @operator(construct) operator overload.
  • Allow using 'var' to declare lambdas in functions.
  • Add 'validation' setting and make dead code a warning.
  • Allow compile time $foreach iteration over constant Strings and bytes.
  • Improved error message when accessing @private from other modules #1769.
  • Include @name when searching for possible matches to name in the error message. #1779
  • Improve @param parse errors #1777
  • Improved #foo resolution inside of the compiler.
  • Deprecated '&' macro arguments.
  • Deprecate `fn void! main() type main functions.
  • Deprecate old void! @benchmark and @test functions.
  • Allow test runners to take String[] arguments.
  • Added --lsp output.
  • Improve the error message when running out of memory.
  • Allowed passing arguments to @test / @benchmark runners via c3c test[benchmark] -- -o --opt1 <arg1>
  • Handle bytes and string literals the same way in terms of zero termination.
  • Function comments are stored and displayed with -P.
  • Prevent #hash arguments from taking code that modifies ct variables. #1794
  • Make stringify to recursively enter #hash expressions #1834.

Fixes

  • Fix case trying to initialize a char[*]* from a String.
  • Fix Map & HashMap put_all_for_create not copying all elements, causing init_from_map to create incomplete copy.
  • Fix bug when a macro calling an extern function was called in another module also declaring and calling the same function. #1690
  • static-lib and dynamic-lib options from the command line now produces headers.
  • Fix bug outputting exported functions without predefined extname.
  • Fix problem where crt1 was linked for dynamic libraries on Linux and BSD. #1710
  • Fix CRT detection on Arch Linux.
  • Fix lexer allowing a trailing underscore (_) with hex and binary literals.
  • Fix --list-operators CLI command printing underscore (_) and hash (#).
  • Fix bug in temp allocator when temp memory is exhausted and allocation needs overaligned mem. #1715
  • Incorrectly handles distinct enums and pointers with '+=' and '-=' #1717.
  • Prevent DString from being initialized with "".
  • Fix bug in OnStackAllocator when freeing overallocated data. #1720
  • Use weak_odr rather than weak on Windows which seems to prevent issues such as #1704.
  • Use weak on dyn-symbols on Linux.
  • Fix crash on project.json not having an empty set of targets.
  • Miscompile when indexing an array with small unsigned types for enums.
  • Change CBool to be 1 byte.
  • any_to_int checks value to be int and no longer works with enum.
  • Add check in formatter printing "%c".
  • Fix bug where !! and ! was not recognized to jump out of the current scope.
  • Fix bug when including compile time parameters in trailing body more than once.
  • Fix issue with compiling a constant struct containing a string array in a local context.
  • Fix error where panic would not properly stop the program when stacktrace couldn't be printed #1751.
  • Macros with default arguments to &, # and type parameters didn't work as expected. #1754.
  • net::poll() with negative timeout behaved incorrectly.
  • Return type inference bugs with macros #1757
  • $defined in a global scope should accept testing normal macros.
  • Assert on add to uninitialized ct variable #1765.
  • Dynamic function lookup fails after changing type without dummy anycast #1761
  • $vasplat was allowed inside of a function when passed as an argument to a function.
  • Prohibit raw vaargs in regular functions with a function body.
  • Assert on certain slice to slice casts. #1768.
  • Fix vector float -> bool conversion.
  • Fix +a = 1 erronously being accepted.
  • Fix not freeing a zero length String
  • Macros with trailing bodys aren't allowed as the single statement after a while loop with no body #1772.
  • Deref subscripts as needed for macro ref method arguments. #1789
  • Change ordering to simplify adding methods to type in conditional modules.
  • #foo style arguments were not type checked when given a type. #1790
  • Bug when using +++ on value build a slice or array: the rhs cast was not done.
  • Fix bug preventing compile time slices from being iterated over with $foreach.
  • Fix bug with defer assignment in macro #1807.
  • Fix regression with swizzle references for vectors #1810.
  • Assert when partially initializing a constant struct containing a slice #1812.
  • Assert concatenating constant slices #1805.
  • Do not link "ld" on Linux with no libc.
  • Fix bug when multiple $else clauses followed an $if #1824.
  • Report the correct type as not having a method when access fails #1828.
  • Prevent temp arena scribbling from causing an asan warning. #1825
  • Fix bug where &i[0] = null was not detected to be an error #1833.

Stdlib changes

  • Increase BitWriter.write_bits limit up to 32 bits.
  • Updates to Slice2d, like get_xy and others.
  • Added iter() value_iter() and key_iter() to HashMap.
  • Add "tokenizer" to String.
  • Add "skip_empty" to split methods. Add split_to_buffer method.
  • Add @enum_from_value.
  • Updated hash function.
  • Added URL parser.
  • Added convenience functions to Maybe.
  • Added String.trim_left() and .trim_right().
  • Deprecation of several & macros.
  • Format functions for timedates.
  • Add @assert_leak() to assert on memory leaks in the scope.
  • Added double.set_high_word(), double.set_low_word(), and float.set_word().

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