Skip to content

The C3 Blog

C3 0.7.3 - Small Improvements

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

Type / typeid

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

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

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

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

Foo* f = new_thing(Foo);

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

const typeid T = Foo.typeid;

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

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

const typeid T = Foo.typeid;

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

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

typeid $type = @select_the_type($foo);

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

It can now be this instead:

typeid $type = @select_the_type($foo);

void* thing = new_thing($type);

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

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

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

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

Removing $evaltype

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

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

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

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

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

Improved type inference

C3 has type inference for initializers:

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

test({1, 2, 3});

However, this did not extend to &&:

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

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

This has now been improved:

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

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

Compile time @sprintf

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

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

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

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

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

$assert and $error "printf"

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

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

Better $eval

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

More liberal "main"

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

--sources to add additional files to compile

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

c3c build --sources inject.c3

Improved overloading with wildcard types

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

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

Distinct improvements

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

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

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

module foo { VAL };
const GLOBAL = VAL;

module test;
import foo;
typedef Bar = int;

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

Constant vector comparison

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

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

Silence deprecation

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

fn void old() @deprecated {}

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

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

Removal of '\f' as whitespace

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

$memberof get/set improvements

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

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

Custom file extensions in build targets

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

Bug fixes

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

Stdlib additions

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

Stdlib deprecations

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

Change Log

Click for full change log

Changes / improvements

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

Fixes

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

Stdlib changes

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

Want To Dive Into C3?

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

Have questions? Come and chat with us on Discord.

Discuss this article on Reddit or Hacker News.

C3 0.7.2 - Quality Of Life

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

Compile time additions

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

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

Simplifying compile time evaluated code

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

The original code to set a variable at compile time:

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

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

bool x = FOO ||| foo();

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

Compile time random: @rnd

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

Compile time ceil: math::@ceil

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

General additions

Set the run directory

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

Limits to SIMD sizes

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

has_tagof works on builtin types

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

Allow recursive generic modules

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

Deprecations

Bitsize suffix deprecations

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

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

Compile time reflection deprecations

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

Deprecation for old @param docs

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

Generic faults are not allowed

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

This is no longer allowed:

module Foo {Type};
typedef BAZ, HELLO;

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

Fixes

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

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

Stdlib updates

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

Optimized String -> ZString conversions

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

Learn more about strings. :::

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

std::ascii moves into std::core

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

Better String tokenization

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

Count and replace

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

Operator overloads for std::time and Maybe

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

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

Subprocess improvements

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

Change Log

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

Want To Dive Into C3?

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

Have questions? Come and chat with us on Discord.

Gradual improvements: C3 0.7.2

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

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

Additions

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

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

Rather than writing:

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

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

bool x = FOO ||| foo();

&&& also supports this.

Compile time random: @rnd

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

Compile time ceil: math::@ceil

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

Set the run directory

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

Suffix deprecations

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

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

Compile time reflection deprecations

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

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

Limits to SIMD sizes

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

has_tagof works on builtin types

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

Allow recursive generic modules

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

Deprecation for old @param docs

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

Generic faults are not allowed

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

So no more

module Foo {Type};
typedef BAZ, HELLO;

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

Fixes

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

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

Stdlib updates

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

Optimized String -> ZString conversions

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

std::ascii moves into core

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

Better String tokenization

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

Count and replace

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

Operator overloads for std::time and Maybe

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

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

Subprocess improvements

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

Changelist

The full changelist is here:

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

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

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

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

Comments


Comment by Christoffer Lernö

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

Additions

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

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

Rather than writing:

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

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

bool x = FOO ||| foo();

&&& also supports this.

Compile time random: @rnd

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

Compile time ceil: math::@ceil

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

Set the run directory

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

Suffix deprecations

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

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

Compile time reflection deprecations

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

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

Limits to SIMD sizes

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

has_tagof works on builtin types

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

Allow recursive generic modules

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

Deprecation for old @param docs

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

Generic faults are not allowed

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

So no more

module Foo {Type};
typedef BAZ, HELLO;

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

Fixes

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

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

Stdlib updates

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

Optimized String -> ZString conversions

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

std::ascii moves into core

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

Better String tokenization

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

Count and replace

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

Operator overloads for std::time and Maybe

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

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

Subprocess improvements

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

Changelist

The full changelist is here:

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

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

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

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

C3 0.7.1 - Operator Overloading

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

Operator overloading

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

Operator overloading overview

The @operator attribute defines the overload for Vec + int

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

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

Overloading with the @operator overload attribute:

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

Overloading with the @operator_s symmetric overload attribute:

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

Overloading with the @operator_r reverse overload attribute:

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

More static checking

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

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

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

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

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

Improvements to attributes

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

Enum lookup

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

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

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

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

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

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

Other changes

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

What's next?

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

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

Change Log

Click for full change log

Changes / improvements

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

Fixes

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

Stdlib changes

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

Want To Dive Into C3?

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

Have questions? Come and chat with us on Discord.

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!