C3


title: What Is C3? description: A guide to the C3 Programming Language sidebar:

order: 1

:::note[Want To Download C3?] Download C3, available on Mac, Windows and Linux. :::

C3 Programming Language

C3 is an evolution of C and a minimalist systems programming language.

🦺 Ergonomics and Safety

⚡ Performance by default

🔋Batteries included standard library

🔧 Leverage existing C or C++ libraries

📦 Modules are simple

🎓 Macros without a PhD


title: Design Goals & Background description: Design Goals & Background sidebar:

order: 2

:::note[Want To Download C3?] Download C3, available on Mac, Windows and Linux. :::

Design goals

“Zero Is Initialization” is an idiom where types and code are written so that the zero value is a meaningful, initialized state.*

Features

C3 Background

C3 is an evolution of C, a minimalistic language designed for systems programming, enabling the same paradigms and retaining the same syntax as far as possible.

C3 started as an experimental fork of the C2 language by Bas van den Berg. It has evolved significantly, not just in syntax but also in regard to error handling, macros, generics and strings.


title: Roadmap For C3 description: Roadmap For C3 sidebar:

order: 3

:::note[Want To Download C3?] Download C3, available on Mac, Windows and Linux. :::

C3 Roadmap

C3 Is Feature Stable

The C3 0.6.x series can be run in production with the same general caveats for using any pre-1.0 software.

While we strive to have zero bug count, there are still bugs being found. This means that anyone using it in production would need to stay updated with the latest fixes.

The focus of 0.70.9 will be fleshing out the cross-platform standard library and make sure the syntax and semantics are solid. Also, the toolchain will expand and improve. Please refer to this issue for what’s left in terms of features for 1.0.

The intended roadmap has one major 0.1 release per year: | Date | Release | |——————|—————-| | 2025-04-01 | 0.7 | | 2026-04-01 | 0.8 | | 2027-04-01 | 0.9 | | 2028-04-01 | 1.0 |

Compatibility

Minor releases in the same major release series are compatible.

For example 0.6.0, 0.6.1, … 0.6.x are compatible and 0.7.0, 0.7.1, … 0.7.x are compatible.

Standard library

The standard library is less mature than the compiler. It needs more functionality and more tests. The compiler reaching a 1.0 release only means a language freeze, the standard library will continue to evolve past the 1.0 release.


title: Install C3 Compiler Binary description: Installing C3 Compiler Binary sidebar:

order: 20

Prebuilt binaries

Installing on Windows

  1. Download the C3 compiler. Or the debug build
  2. Unzip it into a folder
  3. Either Visual Studio 17 or follow the next two steps.
  4. Run the msvc_build_libraries.py Python script which will download the necessary files to compile on Windows.

:::note[Running the Python script]

If you don’t have Python installed, you can download it from the Python Website. or get it from the the Microsoft Store

Afterwards you can double click the msvc_build_libraries.py file and pick “python” from the list of programs in the “Select an app to open this .py file” window. Python

:::

Optional: set c3c as a global environment variable

  1. copy the folder
  2. navigate to C:\Program Files
  3. paste the folder here
  4. navigate inside the folder you’ve pasted
  5. copy the path of the folder
  6. search for “edit the system environment variables” on your computer
  7. click on the “environment variables” button on the bottom right
  8. under “user variables” double click on “path”
  9. click on “new” and paste the path to the folder
  10. run c3c on anywhere in your computer!
    c3c compile ./hello.c3

Installing on Mac Arm64

  1. Make sure you have XCode with command line tools installed.
  2. Download the zip file (debug version here)
  3. Unzip executable and standard lib.
  4. Run ./c3c.

Installing on Ubuntu

  1. Download tar file (debug version here)
  2. Unpack executable and standard lib.
  3. Run ./c3c.

Installing on Debian

  1. Download tar file (debug version here)
  2. Unpack executable and standard lib.
  3. Run ./c3c.

Installing on Arch Linux

There is an AUR package for the c3c compiler : c3c-git.

Due to some issues with the LLVM packaged for Arch Linux, the AUR package will download and use LLVM 16 from Ubuntu-23.04 to compile the c3c compiler.

You can use your AUR package manager:

paru -S c3c-git
# or yay -S c3c-git
# or aura -A c3c-git

Or clone it manually:

git clone https://aur.archlinux.org/c3c-git.git
cd c3c-git
makepkg -si

title: Build C3 From Source description: Build C3 From Source sidebar:

order: 21

:::note[Want To Download Pre-Built C3 Binaries?] Download C3, available on Mac, Windows and Linux. :::

For other platforms it should be possible to compile it on any platform LLVM can compile to. You will need git and CMake installed.

1. Install LLVM

See LLVM the LLVM documentation on how to set up LLVM for development. - On MacOS, installing through Homebrew or MacPorts works fine. - Using apt-get on Linux should work fine as well. - For Windows you can download suitable pre-compiled LLVM binaries from https://github.com/c3lang/win-llvm

2. Clone the C3 compiler source code from Github

This should be as simple as doing:

git clone https://github.com/c3lang/c3c.git

… from the command line.

3. Build the compiler

Create the build directory:

MyMachine:c3c$ mkdir build
MyMachine:c3c$ cd build/

Use CMake to set up:

MyMachine:c3c/build$ cmake ../

Build the compiler:

MyMachine:c3c/build$ make

4. Test it out

MyMachine:c3c/build$ ./c3c compile-run ../resources/testfragments/helloworld.c3

Building via Docker

You can build c3c using either an Ubuntu 18.04 or 20.04 container:

./build-with-docker.sh 18

Replace 18 with 20 to build through Ubuntu 20.04.

For a release build specify:

./build-with-docker.sh 20 Release

A c3c executable will be found under bin/.

Building on Mac using Homebrew

  1. Install CMake: brew install cmake
  2. Install LLVM 17+: brew install llvm
  3. Clone the C3C github repository: git clone https://github.com/c3lang/c3c.git
  4. Enter the C3C directory cd c3c.
  5. Create a build directory mkdir build
  6. Change directory to the build directory cd build
  7. Set up CMake build for debug: cmake ..
  8. Build: cmake --build .

Building on Mac using MacPorts

c3c may be built on Mac systems not supported by Homebrew using the cmake, llvm-17 and clang-17 ports from MacPorts.

  1. Install CMake: sudo port install cmake
  2. Install LLVM 17: sudo port install llvm-17
  3. Install clang 17: sudo port install clang-17
  4. Clone the C3C github repository: git clone https://github.com/c3lang/c3c.git
  5. Enter the C3C directory cd c3c.
  6. Create a build directory mkdir build
  7. Change directory to the build directory cd build
  8. ❗️Important before you run cmake❗️
    Set LLVM_DIR to the directory with the llvm-17 macport .cmake files
    export LLVM_DIR=/opt/local/libexec/llvm-17/lib/cmake/llvm
  9. Set up CMake build for debug: cmake ..
  10. Build: cmake --build .

See also discussion #1701


title: Hello World description: Learn to write hello world sidebar:

order: 30

:::note[Not installed the C3 compiler yet?] Download C3, available on Mac, Windows and Linux. :::

👋 Hello world

Let’s start with the traditional first program, Hello World in C3:

import std::io;

fn void main()
{
    io::printn("Hello, World!");
}

The import statement imports other modules, and we want printn which is in std::io.

Next we define a function which starts with the fn keyword followed by the return type. We don’t need to return anything, so return void. The function name main then follows, followed by the function’s parameter list, which is empty.

fn void main() {}

:::note The function named main is a bit special, as it is where the program starts, or the entry point of the program.

For Unix-like OSes there are a few different variants, for example we might declare it as fn void main(String[] args). In that case the parameter “args” contains a slice of strings, of the program’s command line arguments, starting with the name of the program, itself. :::

🔭 Function scope

{ and } signifies the start and end of the function respectively, we call this the function’s scope. Inside the function scope we have a single function call to printn inside std::io. We use the last part of the path “io” in front of the function to identify what module it belongs to.

📏 Imports can use a shorthand

We could have used the original longer path: std::io::printn if we wanted, but we can shorten it to just the lowest level module like io::printn. This is the convention in C3 and is is known as “path-shortening”, it avoids writing long import paths that can make code harder to read.

```diff lang=”cpp” - std::io::printn(“Hello, World!”); + io::printn(“Hello, World!”);


The `io::printn` function takes a single argument and prints it, followed by a newline, then the function ends and the program terminates.


## 🔧 Compiling the program

Let's take the above program and put it in a file called `hello_world.c3`.

We can then compile it with:

```bash 
c3c compile hello_world.c3

And run it:

./hello_world

It should print Hello, World! and return back to the command line prompt. If you are on Windows, you will have hello_world.exe instead. Call it in the same way.

🏃 Compiling and running

When we start out it can be useful to compile and then have the compiler start the program immediately. We can do that with compile-run:

```bash {4} $ c3c compile-run hello_world.c3

Program linked to executable ‘hello_world’. Launching hello_world… Hello, World ```

Want more options when compiling? Check the c3c compiler build options.

🎉 Successfully working?

Congratulations! You’re now up and running with C3.

❓ Need help?

We’re happy to help on the C3 Discord.


title: Projects description: Learn to create C3 projects sidebar:

order: 31

import { FileTree } from ‘@astrojs/starlight/components’;

:::note[Not installed the C3 compiler yet?] Download C3, available on Mac, Windows and Linux. :::

Projects in C3

Projects are optional, but are a good way to manage compiling code when there are a lot of files and modules. They also allow you to specify libraries to link, and define how your project should be built for specific targets.

💡 Creating a new project

The c3c init command will create a new directory containing your project structure. It requires a name of the project, we will use myc3project in its place.

c3c init myc3project

You can also customize the path where the project will be created or specify a template. For more information check the init command reference.

📁 Project structure

If you check the directory that was created you might find it a bit confusing with a bunch of different directories, but worry not because if you expand them you will realise that most of them are actually empty!

- build/ - docs/ - lib/ - resources/ - scripts/ - src/ - main.c3 - test/ - LICENSE - project.json - README.md

Directory Overview

Directory Usage
./build Where your temporary files and build results will go.
./docs Code Documentation
./lib C3 libraries (with the .c3l suffix)
./resources Non-code resources like images, sound effects etc.
./scripts Scripts, including .c3 scripts that generate code at compile time.
./src Storing our code, by default contains main.c3 with “Hello World”.
project.json Record project information, similar to package.json in NodeJS.
LICENSE Project license.
README.md Help others understand and use your code.

🔧 Building the project

Assuming you have successfully initialized a project as seen above, we can now look at how to compile it.

🏃 Build & run

C3 has a simple command to build & run our project.

```bash {4} c3c run

Program linked to executable ‘build/myc3project’. Launching ./build/myc3project… Hello, World ```

You can also specify the target to build & run.

c3c run myc3project

🔧 Build

If you only want to build the project, you can use the build command:

c3c build

This command builds the project targets defined in our project.json file.

:::note If you want to build a specific target, you can do so by specifying its name. The default target is created with the name of the project, such as myc3project.

c3c build myc3project

:::

We will now have a binary in build, which we can run:

./build/myc3project

It should print Hello, World! and return back to the command line prompt. If you are on Windows, you will have myc3project.exe instead. Call it in the same way.

If you need more detail later on check C3 project build commands and C3 project configuration to learn more.


title: Community & Contribute description: Info regarding the development of C3 sidebar:

order: 10

Contributions Welcome!

The C3 language is still in its development phase, which means functionality and specification are subject to change. That also means that any contribution right now will have a big impact on the language. So if you find the project interesting, here’s what you can do to help:

💬 Discuss The Language

💡 Suggest Improvements

💪 Contribute

Now the compiler is stable, what is needed now are the non-essentials, such as a docs generator, editor plugins, LSP etc.


title: Basic Types and Values description: Get an overview of C3’s basic types and values sidebar:

order: 33

C3 provides a similar set of fundamental data types as C: integers, floats, arrays and pointers. On top of this it expands on this set by adding slices and vectors, as well as the any and typeid types for advanced use.

Integers

C3 has signed and unsigned integer types. The built-in signed integer types are ichar, short, int, long, int128, iptr and isz. ichar to int128 have all well-defined power-of-two bit sizes, whereas iptr has the same number of bits as a void* and isz has the same number of bits as the maximum difference between two pointers. For each signed integer type there is a corresponding unsigned integer type: char, ushort, uint, ulong, uint128, uptr and usz.

type signed? min max bits
ichar yes -128 127 8
short yes -32768 32767 16
int yes -2^31 2^31 - 1 32
long yes -2^63 2^63 - 1 64
int128 yes -2^127 2^127 - 1 128
iptr yes varies varies varies
isz yes varies varies varies
char no 0 255 8
ushort no 0 65535 16
uint no 0 2^32 - 1 32
ulong no 0 2^64 - 1 64
uint128 no 0 2^128 - 1 128
uptr no 0 varies varies
usz no 0 varies varies

On 64-bit machines iptr/uptr and isz/usz are usually 64-bits, like long/ulong. On 32-bit machines on the other hand they are generally int/uint.

Integer constants

Numeric constants typically use decimal, e.g. 234, but may also use hexadecimal (base 16) numbers by prefixing the number with 0x or 0X, e.g. int a = 0x42edaa02;. There is also octal (base 8) using the 0o or 0O prefix, and 0b for binary (base 2) numbers:

Numbers may also insert underscore _ between digits to improve readability, e.g. 1_000_000.

a = -2_000;
b = 0o770;
c = 0x7f7f7f;

For decimal numbers, the value is assumed to be a signed int, unless the number doesn’t fit in an int, in which case it is assumed to be the smallest signed type it does fit in (long or int128).

For hexadecimal, octal and binary, the type is assumed to be unsigned.

A integer literal can implicitly convert to a floating point literal, or an integer of a different type provided the number fits in the type.

Constant suffixes

If you want to ensure that a constant is of a certain type, you can either add an explicit cast like: (ushort)345, or use an integer suffix: 345u16.

The following integer suffixes are available:

suffix type
i8 ichar
i16 short
i32 int
i64 long
i128 int128
u8 char
u16 ushort
u32 uint
u uint
u64 ulong
u128 uint128

Note how uint also has the u suffix.

Booleans

A bool will be either true or false. Although a bool is only a single bit of data, it should be noted that it is stored in a byte.

bool b = true;
bool f = false;

Character literals

A character literal is a value enclosed in ''. Its value is interpreted as being its ASCII value for a single character.

It is also possible to use 2, 4 or 8 character wide character literals. Such are interpreted as ushort, uint and ulong respectively and are laid out in memory from left to right. This means that the actual value depends on the endianness of the target.

The 4 character literals correspond to the layout of FourCC codes. It will also correctly arrange unicode characters in memory. E.g. Char32 smiley = '\u1F603'

Floating point types

As is common, C3 has two floating point types: float and double. float is the 32 bit floating point type and double is 64 bits.

Floating point constants

Floating point constants will at least use 64 bit precision. Just like for integer constants, it is possible to use _ to improve readability, but it may not occur immediately before or after a dot or an exponential.

C3 supports floating points values either written in decimal or hexadecimal formats. For decimal, the exponential symbol is e (or E, both are acceptable), for hexadecimal p (or P) is used: -2.22e-21 -0x21.93p-10

While floating point numbers default to double it is possible to type a floating point by adding a suffix:

Suffix type
f32 or f float
f64 double

Arrays

Arrays have the format Type[size], so for example: int[4]. An array is a type consisting of the same element repeated a number of times. Our int[4] is essentially four int values packed together.

For initialization it’s sometimes convenient to use the wildcard Type[*] declaration, which infers the length from the number of elements:

int[3] abc = { 1, 2, 3 }; // Explicit int[3]
int[*] bcd = { 1, 2, 3 }; // Implicit int[3]

Slices

Slices have the format Type[]. Unlike the array, a slice does not hold the values themselves but instead presents a view of some underlying array or vector.

Slices have two properties: .ptr, which retrieves the array it points to, and .len which is the length of the slice - that is, the number of elements it is possible to index into.

Usually we can get a slice by taking the address of an array:

int[3] abc = { 1, 2, 3 }; 
int[] slice = &abc;       // A slice pointing to abc with length 3

Because indexing into slices is range checked in safe mode, slices are vastly more safe providing pointer + length separately.

Vectors

Vectors similar to arrays, use the format Type[<size>], with the restriction that vectors may only form out of integers, floats and booleans. Similar to arrays, wildcard can be used to infer the size of a vector:

int[<*>] a = { 1, 2 };

Vectors are based on hardware SIMD vectors, and supports many different operations that work on all elements in parallel, including arithmetics:

int[<2>] b = { 3, 8 };
int[<2>] c = { 7, 2 };
int[<2>] d = b * c;    // d is { 21, 16 }

Vector initialization and literals work the same way as arrays, using { ... }

String literals

String literals are special and can convert to several different types: String, char and ichar arrays and slices and finally ichar* and char*.

String literals are text enclosed in " " just like in C. These support escape sequences like \n for line break and need to use \" for any " inside of the string.

C3 also offers raw strings which are enclosed in ` ` . A raw string may span multiple lines. Inside of a raw string, no escapes are available, and to write a ` , simply double the character:

// Note: String is a distinct inline char[]
String three_lines = 
`multi
line
string`;

String foo = `C:\foo\bar.dll`;
String bar = `"Say ``hello``"`;
// Same as
String foo = "C:\\foo\\bar.dll";
String bar = "\"Say `hello`\"";

String is a distinct inline char[], which can implicitly convert to char[] when required.

ZString is a distinct inline char*.ZString is a C compatible null terminated string, which can implicitly convert to char* when required.

Base64 and hex data literals

Base64 literals are strings prefixed with b64 to containing Base64 encoded data, which is converted into a char array at compile time:

// The array below contains the characters "Hello World!"
char[*] hello_world_base64 = b64"SGVsbG8gV29ybGQh";

The corresponding hex data literals convert a hexadecimal string rather than Base64:

// The array below contains the characters "Hello World!"
char[*] hello_world_hex = x"4865 6c6c 6f20 776f 726c 6421";

Pointer types

Pointers have the syntax Type*. A pointer is a memory address where one or possibly more elements of the underlying address are stored. Pointers can be stacked: Foo* is a pointer to a Foo while Foo** is a pointer to a pointer to Foo.

The pointer type has a special literal called null, which is an invalid, empty pointer.

void*

The void* type is a special pointer which implicitly converts to any other pointer. It is not “a pointer to void”, but rather a wildcard pointer which matches any other pointer.

Printing values

Printing values can be done using io::print, io::printn, io::printf and io::printfn. This requires importing the module std::io.

:::note The n variants of the print functions will add a newline after printing, which is what we’ll often use in the examples, but print and printf work the same way.

:::

import std::io; // Get the io functions.

fn void main()
{
    int a = 1234;
    ulong b = 0xFFAABBCCDDEEFF;
    double d = 13.03e-04;
    char[*] hex = x"4865 6c6c 6f20 776f 726c 6421";
    io::printn(a);
    io::printn(b);
    io::printn(d);
    io::printn(hex);
}

If you run this program you will get:

1234
71963842633920255
0.001303
[72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]

To get more control we can format the output using printf and printfn:

import std::io;
fn void main()
{
    int a = 1234;
    ulong b = 0xFFAABBCCDDEEFF;
    double d = 13.03e-04;
    char[*] hex = x"4865 6c6c 6f20 776f 726c 6421";
    io::printfn("a was:                        %d", a);
    io::printfn("b in hex was:                 %x", b);
    io::printfn("d in scientific notation was: %e", d);
    io::printfn("Bytes as string:              %s", (String)&hex);
}

We can apply the standard printf formatting rules, but unlike in C/C++ there is no need to indicate the type when using %d - it will print unsigned and signed up to int128, in fact there is no support for %u, %lld etc in io::printf. Furthermore, %s works not just on strings but on any type:

import std::io;

enum Foo
{
    ABC,
    BCD,
    EFG,
}
fn void main()
{
    int a = 1234;
    uint128 b = 0xFFEEDDCC_BBAA9988_77665544_33221100;
    Foo foo = BCD;
    io::printfn("a: %s, b: %d, foo: %s", a, b, foo);
}

This prints:

a: 1234, b: 340193404210632335760508365704335069440, foo: BCD

title: Comments & Documentation description: Comments & Documentation sidebar:

order: 40

C3 uses three distinct comment types:

  1. The normal // single line comment.
  2. The classic /* ... */ multi-line C style comment, but unlike in C they are allowed to nest.
  3. Documentation comments <* ... *> the text within these comments will be parsed as documentation and optional Contracts on the following code.

Doc contracts

Documentation contracts start with <* and must be terminated using *>. Any initial text up until the first @-directive on a new line will be interpreted as free text documentation.

For example:

<*
 Here are some docs.
 @param num_foo `The number of foos.`
 @require num_foo > 4 
 @deprecated
 @mycustom 2
*>
void bar(int num_foo)
{
    io::printfn("%d", num_foo);
}

Doc Contracts Are Parsed

The following was extracted: - The function description: “Here are some docs.” - The num_foo parameter has the description: “The number of foos”. - A Contract annotation for the compiler: @require num_foo > 4 which tells the compiler and a user of the function that a precondition is that num_foo must be greater than 4. - A function Attribute marking it as @deprecated, which displays warnings. - A custom function Attribute @mycustom. The compiler is free to silently ignore custom Attributes, they can be used to optionally emit warnings, but are otherwise ignored.

Available annotations

Name format
@param @param <param> <description>
@return @return <description>
@return! @return! <fault1>, <fault2>, ...
@deprecated @deprecated <optional description>
@require @require <expr1>, <expr2>, ...
@ensure @ensure <expre1>, <expr2>, ...
@pure @pure

See Contracts for information regarding @require, @ensure, @const, @pure, @checked.


title: Naming Rules description: Naming Rules sidebar:

order: 41

C3 introduces fairly rigid naming rules to reduce ambiguity and make the language easy to parse for tools.

As a basic rule, all identifiers are limited to a-z, A-Z, 0-9 and _. The initial character can not be a number. Furthermore, all identifiers are limited to 31 character.

Structs, unions, enums and faults

All user defined types must start with A-Z after any optional initial _ and include at least 1 lower case letter. Bar, _T_i12 and TTi are all valid names. _1, bAR and BAR are not. For C-compatibility it’s possible to alias the type to a external name using the attribute “extern”.

struct Foo @extern("foo")
{
    int x;
    Bar bar;
}

union Bar 
{
    int i;
    double d;
}

enum Baz 
{
    VALUE_1,
    VALUE_2
}

fault Err 
{
    OOPS,
    LOTS_OF_OOPS
}

Variables and parameters

All variables and parameters except for global constant variables must start with a-z after any optional initial _. ___a fooBar and _test_ are all valid variable / parameter names. _, _Bar, X are not.

int theGlobal = 1;

fn void foo(int x)
{
    Foo foo = getFoo(x);    
    theGlobal++;
}

Global constants

Global constants must start with A-Z after any optional initial _. _FOO2, BAR_FOO, X are all valid global constants, _, _bar, x are not.

const int A_VALUE = 12;

Enum / Fault values

Enum and fault values follow the same naming standard as global constants.

enum Baz 
{
    VALUE_1,
    VALUE_2
}

fault Err 
{
    OOPS,
    LOTS_OF_OOPS
}

Struct / union members

Struct and union members follow the same naming rules as variables.

Modules

Module names may contain a-z, 0-9 and _, no upper case characters are allowed.

module foo;

Functions and macros

Functions and macros must start with a-z after any optional initial _.

fn void theMostAmazingFunction() 
{ 
    return;
}

macro justDoIt(x) 
{
    justDo(x);
}

title: Variables description: Variables sidebar:

order: 41

Zero init by default

Unlike C, C3 local variables are zero-initialized by default. To avoid zero initialization, you need to explicitly opt-out.

int x;               // x = 0
int y @noinit;       // y is explicitly undefined and must be assigned before use.

AStruct foo;         // foo is implicitly zeroed
AStruct bar = {};    // bar is explicitly zeroed
AStruct baz @noinit; // baz is explicitly undefined

Using a variable that is explicitly undefined before will trap or be initialized to a specific value when compiling “safe” and is undefined behaviour in “fast” builds.


title: Expressions description: Expressions sidebar:

order: 42

Expressions work like in C, with one exception: it is possible to take the address of a temporary. This uses the operator && rather than &.

Consequently, this is valid:

fn void test(int* x) { ... }

test(&&1);

// In C:
// int x = 1;
// test(&x);

Well-defined evaluation order

Expressions have a well-defined evaluation order:

  1. Binary expressions are evaluated from left to right.
  2. Assignment occurs right to left, so a = a++ would result in a being unchanged.
  3. Call arguments are evaluated in parameter order.

Compound literals

C3 has C’s compound literals, but unlike C’s cast style syntax (MyStruct) { 1, 2 }, it uses C++ syntax: MyStruct { 1, 2 }.

struct Foo
{
    int a;
    double b;
}

fn void test1(Foo x) { ... }

... 

test1(Foo { 1, 2.0 });

Arrays follow the same syntax:

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

...

test2(int[3] { 1, 2, 3 });

Note that when it’s possible, inferring the type is allowed, so we have for the above examples:

test1({ 1, 2.0 });
test2({ 1, 2, 3 });

One may take the address of temporaries, using && (rather than & for normal variables). This allows the following:

Passing a slice

fn void test(int[] y) { ... }

// Using &&
test(&&int[3]{ 1, 2, 3 });

// Explicitly slicing:
test(int[3]{ 1, 2, 3 }[..]);

// Using a slice directly as a temporary:
test(int[]{ 1, 2, 3 });

// Same as above but with inferred type:
test({ 1, 2, 3 });

Passing the pointer to an array

fn void test1(int[3]* z) { ... }
fn void test2(int* z) { ... }

test1(&&int[3]{ 1, 2, 3 });
test2(&&int[3]{ 1, 2, 3 });

Constant expressions

In C3 all constant expressions are guaranteed to be calculated at compile time. The following are considered constant expressions:

  1. The null literal.
  2. Boolean, floating point and integer literals.
  3. The result of arithmetics on constant expressions.
  4. Compile time variables (prefixed with $)
  5. Global constant variables with initializers that are constant expressions.
  6. The result of macros that does not generate code and only uses constant expressions.
  7. The result of a cast if the value is cast to a boolean, floating point or integer type and the value that is converted is a constant expression.
  8. String literals.
  9. Initializer lists containing constant values.

Some things that are not constant expressions:

  1. Any pointer that isn’t the null literal, even if it’s derived from a constant expression.
  2. The result of a cast except for casts of constant expressions to a numeric type.
  3. Compound literals - even when values are constant expressions.

Including binary data

The $embed(...) function includes the contents of a file into the compilation as a constant array of bytes:

char[*] my_image = $embed("my_image.png");

The result of an embed work similar to a string literal and can implicitly convert to a char*, void*, char[], char[*] and String.

Limiting length

It’s possible to limit the length of included with the optional second parameter.

char[4] my_data = $embed("foo.txt", 4);
Failure to load at compile time and defaults

Usually it’s a compile time error if the file can’t be included, but sometimes it’s useful to only optionally include it. If this is desired, declare the left hand side an Optional:

char[]! my_image = $embed("my_image.png");

my_image with be an optional IoError.FILE_NOT_FOUND? if the image is missing.

This also allows us to pass a default value using ??:

char[] my_image = $embed("my_image.png") ?? DEFAULT_IMAGE_DATA;

title: Statements description: Statements sidebar:

order: 43

Statements largely work like in C, but with some additions.

Expression blocks

Expression blocks (delimited using {| |}) are compound statements that opens their own function scope. Jumps cannot be done into or out of a function block, and return exits the block, rather than the function as a whole.

The function below prints World!

fn void test()
{
    int a = 0;
    {|
        if (!a) return;
        io::printf("Hello ");
        return;
    |};
    io::printf("World!\n");
}

Expression blocks may also return values:

fn void test(int x)
{
    int a = {|
        if (x > 0) return x * 2;
        if (x == 0) return 100;
        return -x;
    |};            
    io::printfn("The result was %d", a);
}

Labelled break and continue

Labelled break and continue lets you break out of an outer scope. Labels can be put on if, switch, while and do statements.

fn void test(int i)
{
    if FOO: (i > 0)
    {
        while (1)
        {
            io::printfn("%d", i);
            // Break out of the top if statement.
            if (i++ > 10) break FOO;
        }
    }
}

Do-without-while

Do-while statements can skip the ending while. In that case it acts as if the while was while(0):

do 
{
    io::printn("FOO");
} while (0);

// Equivalent to the above.
do 
{
    io::printn("FOO");
};

Nextcase and labelled nextcase

The nextcase statement is used in switch and if-catch to jump to the next statement:

switch (i)
{
    case 1:
        doSomething();
        nextcase; // Jumps to case 2
    case 2:
        doSomethingElse();
}

It’s also possible to use nextcase with an expression, to jump to an arbitrary case:

switch (i)
{
    case 1:
        doSomething();
        nextcase 3; // Jump to case 3
    case 2:
        doSomethingElse();
    case 3:
        nextcase rand(); // Jump to random case
    default:
        io::printn("Ended");
}

Which can be used as structured goto when creating state machines.

Switch cases with runtime evaluation

It’s possible to use switch as an enhanced if-else chain:

switch (true)
{
    case x < 0:
        xless();
    case x > 0:
        xgreater();
    default:
        xequals();
}

The above would be equivalent to writing:

if (x < 0)
{
    xless();
}
else if (x > 0)
{
    xgreater();
}
else
{
    xequals();
}

Note that because of this, the first match is always picked. Consider:

switch (true)
{
    case x > 0:
        foo();
    case x > 2:
        bar();
}

Because of the evaluation order, only foo() will be invoked for x > 0, even when x is greater than 2.

It’s also possible to omit the conditional after switch. In that case it is implicitly assumed to be same as writing (true)

switch
{
    case foo() > 0:
        bar();
    case test() == 1:
        baz();
}

title: Functions description: Functions sidebar:

order: 45

C3 has both regular functions and member functions. Member functions are functions namespaced using type names, and allows invocations using the dot syntax.

Regular functions

Regular functions are the same as C aside from the keyword fn, which is followed by the conventional C declaration of <return type> <name>(<parameter list>).

fn void test(int times)
{
    for (int i = 0; i < times; i++)
    {
        io::printfn("Hello %d", i);
    }
}

Function arguments

C3 allows use of default arguments as well as named arguments. Note that any unnamed arguments must appear before any named arguments.

fn int test_with_default(int foo = 1)
{
    return foo;
}

fn void test()
{
    test_with_default();
    test_with_default(100);
}

Named arguments

fn void test_named(int times, double data)
{
    for (int i = 0; i < times; i++)
    {
        io::printf("Hello %d\n", i + data);
    }
}

fn void test()
{
    // Named only
    test_named(times: 1, data: 3.0);

    // Unnamed only
    test_named(3, 4.0);

    // Mixing named and unnamed        
    test_named(15, data: 3.141592);
}

Named arguments with defaults:

fn void test_named_default(int times = 1, double data = 3.0, bool dummy = false)
{
    for (int i = 0; i < times; i++)
    {
        io::printfn("Hello %f", i + data);
    }
}

fn void test()
{
    // Named only
    test_named_default(times: 10, data: 3.5);

    // Unnamed and named
    test_named_default(3, dummy: false);

    // Overwriting an unnamed argument with a named argument is an error:
    // test_named_default(2, times: 3); ERROR!

    // Unnamed may not follow named arguments.
    // test_named_default(times: 3, 4.0); ERROR!
}

Varargs

There are four types of varargs:

  1. single typed
  2. explicitly typed any: pass non-any arguments as references
  3. implicitly typed any: arguments are implicitly converted to references (use with care)
  4. untyped C-style

Examples:

fn void va_singletyped(int... args)
{
    /* args has type int[] */
}

fn void va_variants_explicit(any*... args)
{
    /* args has type any*[] */
}

fn void va_variants_implicit(args...)
{
    /* args has type any*[] */
}

extern fn void va_untyped(...); // only used for extern C functions

fn void test()
{
    va_singletyped(1, 2, 3);

    int x = 1;
    any* v = &x;
    va_variants_explicit(&&1, &x, v); // pass references for non-any arguments

    va_variants_implicit(1, x, "foo"); // arguments are implicitly converted to anys

    va_untyped(1, x, "foo"); // extern C-function
}

For typed varargs, we can pass a slice instead of the individual arguments, by using the splat ... operator for example:

fn void test_splat()
{
   int[] x = { 1, 2, 3 };
   va_singletyped(...x);
}

Splat

fn void va_singletyped(int... args) { 
    io::printfn("%s", args); 
}
fn void main() 
{
    int[2] arr = {1, 2};
    va_singletyped(...arr); // arr is splatting two arguments
}
fn void foo(int a, int b, int c) 
{ 
    io::printfn("%s, %s, %s", a, b, c); 
}
fn void main() 
{
    int[2] arr = {1, 2};
    foo(...arr, 7); // arr is splatting two arguments
}
fn void foo(int a, int b, int c) 
{ 
    io::printfn("%s, %s, %s", a, b, c); 
}
fn void main() 
{
    int[5] arr = {1, 2, 3, 4, 5};
    foo(...arr[:3]); // slice is splatting three arguments
}

Named arguments and varargs

Usually, a parameter after varargs would never be assigned to:

fn void testme(int a, double... x, double rate = 1.0) { /* ... */ }

fn void test()
{
    // x is { 2.0, 5.0, 6.0 } rate would be 1.0
    testme(3, 2.0, 5.0, 6.0); 
}

However, named arguments can be used to set this value:

fn void testme(int a, double... x, double rate = 1.0) { /* ... */ }

fn void test()
{
    // x is { 2.0, 5.0 } rate would be 6.0
    testme(3, 2.0, 5.0, rate: 6.0);
}

Functions and Optional returns

Function return values may be Optionals – denoted by <type>! indicating that this function might either return an Optional with a result, or an Optional with an Excuse.

For example this function might return an Excuse of type SomeError or OtherResult.

fn double! test_error()
{
    double val = random_value();
    if (val >= 0.2) return SomeError.BAD_JOSS_ERROR?;
    if (val > 0.5) return OtherError.BAD_LUCK_ERROR?;
    return val;
}

A function call which is passed one or more Optional arguments will only execute if all Optional values contain a result, otherwise the first Excuse found is returned.

fn void test()
{
    // The following line is either prints a value less than 0.2
    // or does not print at all:
    io::printfn("%d", test_error());

    // ?? sets a default value if an Excuse is found
    double x = (test_error() + test_error()) ?? 100;  

    // This prints either a value less than 0.4 or 100:
    io::printfn("%d", x);
}

This allows us to chain functions:

fn void print_input_with_explicit_checks()
{
    String! line = io::readline();
    if (try line)
    {
        // line is a regular "string" here.
        int! val = line.to_int();
        if (try val)
        {
            io::printfn("You typed the number %d", val);
            return;
        }
    }
    io::printn("You didn't type an integer :/ ");
}

fn void print_input_with_chaining()
{
    if (try int val = io::readline().to_int())
    {
        io::printfn("You typed the number %d", val);
        return;
    }
    io::printn("You didn't type an integer :/ ");
}

Methods

Methods look exactly like functions, but are prefixed with the type name and is (usually) invoked using dot syntax:

struct Point
{
    int x;
    int y;
}

fn void Point.add(Point* p, int x) 
{
    p.x += x;
}

fn void example() 
{
    Point p = { 1, 2 };

    // with struct-functions
    p.add(10);

    // Also callable as:
    Point.add(&p, 10);
}

The target object may be passed by value or by pointer:

enum State
{
    STOPPED,
    RUNNING
}

fn bool State.may_open(State state) 
{
    switch (state)
    {
        case STOPPED: return true;
        case RUNNING: return false;
    }
}

Implicit first parameters

Because the type of the first argument is known, it may be left out. To indicate a pointer & is used.

fn int Foo.test(&self) { /* ... */ }
// equivalent to
fn int Foo.test(Foo* self) { /* ... */ }
fn int Bar.test(self) { /* ... */ }
// equivalent to
fn int Bar.test(Bar self) { /* ... */ }

It is customary to use self as the name of the first parameter, but it is not required.

Restrictions on methods

Contracts

C3’s error handling is not intended to use errors to signal invalid data or to check invariants and post conditions. Instead C3’s approach is to add annotations to the function, that conditionally will be compiled into asserts.

As an example, the following code:

<*
 @param foo `the number of foos`
 @require foo > 0, foo < 1000
 @return `number of foos x 10`
 @ensure return < 10000, return > 0
*>
fn int test_foo(int foo)
{
    return foo * 10;
}

Will in debug builds be compiled into something like this:

fn int test_foo(int foo)
{
    assert(foo > 0);
    assert(foo < 1000);
    int _return = foo * 10;
    assert(_return < 10000);
    assert(_return > 0);
    return _return;
}

The compiler is allowed to use the contracts for optimizations. For example this:

fn int test_example(int bar)
{
    // The following is always invalid due to the `@ensure`
    if (test_foo(bar) == 0) return -1;
    return 1;
}

May be optimized to:

fn int test_example(int bar)
{
    return 1;
}

In this case the compiler can look at the post condition of result > 0 to determine that testFoo(foo) == 0 must always be false.

Looking closely at this code, we note that nothing guarantees that bar is not violating the preconditions. In Safe builds this will usually be checked in runtime, but a sufficiently smart compiler will warn about the lack of checks on bar. Execution of code violating pre and post conditions has unspecified behaviour.

Short function declaration syntax

For very short functions, C3 offers a “short declaration” syntax using =>:

// Regular
fn int square(int x)
{
    return x * x;
}
// Short
fn int square_short(int x) => x * x;

Lambdas

It’s possible to create anonymous functions using the regular fn syntax. Anonymous functions are identical to regular functions and do not capture variables from the surrounding scope:

def IntTransform = fn int(int);
fn void apply(int[] arr, IntTransform t)
{
    foreach (&i : arr) *i = t(*i);
}
fn void main()
{
    int[] x = { 1, 2, 5 };
    // Short syntax with inference:
    apply(x, fn (i) => i * i);
    // Regular syntax without inference: 
    // apply(x, fn int(int i) { return i * i; });
    // Prints [1, 4, 25]
    io::printfn("%s", x);        
}

Static initializer and finalizers

It is sometimes useful to run code at startup and shutdown. Static initializers and finalizers are regular functions annotated with @init and @finalizer that are run at startup and shutdown respectively:

fn void run_at_startup() @init
{
    // Run at startup
    some_function.init(512);
} 

fn void run_at_shutdown() @finalizer
{
    some_thing.shutdown();
}

Note that invoking @finalizer is a best effort attempt by the OS and may not be called during abnormal shutdown.

Changing priority of static initializers and finalizers

It is possible to provide an argument to the attributes to set the actual priority. It is recommended that programs use a priority of 1024 or higher. The higher the value, the later it will be called. The lowest priority is 65535.

// Print "Hello World" at startup.

fn void start_world() @init(3000)
{
    io::printn("World");
}
fn void start_hello() @init(2000)
{
    io::print("Hello ");
}

title: Modules description: Modules sidebar:

order: 46

C3 groups functions, types, variables and macros into namespaces called modules. When doing builds, any C3 file must start with the module keyword, specifying the module. When compiling single files, the module is not needed and the module name is assumed to be the file name, converted to lower case, with any invalid characters replaced by underscore (_).

A module can consist of multiple files, e.g.

file_a.c3

module foo;

/* ... */

file_b.c3

module foo;

/* ... */

file_c.c3

module bar;

/* ... */

Here file_a.c3 and file_b.c3 belong to the same module, foo while file_c.c3 belongs to to bar.

Details

Some details about the C3 module system:

Importing Modules

Modules are imported using the import statement. Imports always recursively import sub-modules. Any module will automatically import all other modules with the same parent module.

foo.c3

module some::foo;
fn void test() {}

bar.c3

module bar;
import some;
// import some::foo; <- not needed, as it is a sub module to "some"
fn void test()
{
    foo::test();
    // some::foo::test() also works.
}

In some cases there may be ambiguities, in which case the full path can be used to resolve the ambiguity:

abc.c3

module abc;
struct Context
{
    int a;
}

def.c3

module def;
struct Context
{
    void* ptr;
}

test.c3

module test1;
import def, abc;
// Context c = {} <- ambiguous
abc::Context c = {};

Implicit Imports

The module system will also implicitly import:

  1. The std::core module (and sub modules).
  2. Any other module sharing the same top module. E.g. the module foo::abc will implicitly also import modules foo and foo::cde if they exist.

Visibility

All files in the same module share the same global declaration namespace. By default a symbol is visible to all other modules. To make a symbol only visible inside the module, use the @private attribute.

module foo;

fn void init() { .. }

fn void open() @private { .. }

In this example, the other modules can use the init() function after importing foo, but only files in the foo module can use open(), as it is specified as private.

It’s possible to further restrict visibility: @local works like @private except it’s only visible in the local context.

// File foo.c3
module foo;
fn void abc() @private { ... }
fn void def() @local { ... }

// File foo2.c3
module foo;
fn void test()
{
    abc(); // Access of private in the same module is ok
    // def(); <- Error: function is local to foo.c3
}

Overriding Symbol Visibility Rules

By using import <module> @public, it’s possible to access another module´s private symbols. Many other module systems have hierarchal visibility rules, but the import @public feature allows visibility to be manipulated in a more ad-hoc manner without imposing hard rules.

For example, you may provide a library with two modules: “mylib::net” and “mylib::file” - which both use functions and types from a common “mylib::internals” module. The two libraries use import mylib::internals @public to access this module’s private functions and type. To an external user of the library, the “mylib::internals” does not seem to exist, but inside of your library you use it as a shared dependency.

A simple example:

// File a.c3
module a;

fn void a_function() @private { ... }

// File b.c3
module b;

fn void b_function() @private { ... }

// File c.c3
module c;
import a;
import b @public;

fn void test()
{
    // Error! a_function() is private
    a::a_function(); 

    // Allowed since `import b @public` allowed `b`
    // to "public" in this context.
    b::b_function(); 
}

Note: @local visibility cannot be overridden using a “@public” import.

Changing The Default Visibility

In a normal module, global declarations will be public by default. If some other visibility is desired, it’s possible to declare @private or @local after the module name. It will affect all declaration in the same section.

module foo @private;

fn void ab_private() { ... } // Private

module foo;

fn void ab_public() { ... } // Public

module bar;
import foo;

fn void test()
{
    foo::ab_public(); // Works
    // foo::ab_private(); <- Error, private method
}

If the default visibility is @private or @local, using @public sets the visibility to public:

module foo @private;

fn void ab_private() { ... }        // Private
fn void ab_public() @public { ... } // Public

Linker Visibility and Exports

A function or global prefixed extern will be assumed to be linked in later. An “extern” function may not have a body, and global variables are prohibited from having an init expression.

The attribute @export explicitly marks a function as being exported when creating a (static or dynamic) library. It can also change the linker name of the function.

Using Functions and Types From Other Modules

As a rule, functions, macros, constants, variables and types in the same module do not need any namespace prefix. For imported modules the following rules hold:

  1. Functions, macros, constants and variables require at least the (sub-) module name.
  2. Types do not require the module name unless the name is ambiguous.
  3. In case of ambiguity, only so many levels of module names are needed as to make the symbol unambiguous.
// File a.c3

module a;

struct Foo { ... }
struct Bar { ... }
struct TheAStruct { ... }

fn void anAFunction() { ... }

// File b.c3

module b;

struct Foo { ... }
struct Bar { ... }
struct TheBStruct { ... }

fn void aBFunction() { ... }

// File c.c3
module c;
import a, b;

struct TheCStruct { ... }
struct Bar { ... }

fn void aCFunction() { ... }

fn void test()
{
    TheAStruct stA;
    TheBStruct stB;
    TheCStruct stC;
    // Name required to avoid ambiguity;
    b::Foo stBFoo;
    // Will always pick the current module's
    // name.
    Bar bar;
    // Namespace required:
    a::aAFunction();
    b::aBFunction();
    // A local symbol does not require it:
    aCFunction();
}

This means that the rule for the common case can be summarized as

Types are used without prefix; functions, variables, macros and constants are prefixed with the sub module name.

Module Sections

A single file may have multiple module declarations, even for the same module. This allows us to write for example:

// File foo.c3
module foo;
fn int hello_world()
{
    return my_hello_world();
}

module foo @private;
import std::io;         // The import is only visible in this section.
fn int my_hello_world() // @private by default
{
    io::printn("Hello, world\n");
    return 0;
}

module foo @test;
fn void test_hello() // @test by default
{
    assert(hello_world() == 0);
}

Versioning and Dynamic Inclusion

NOTE: This feature may significantly change.

When including dynamic libraries, it is possible to use optional functions and globals. This is done using the @dynamic attribute.

An example library could have this:

dynlib.c3i

module dynlib;
fn void do_something() @dynamic(4.0)
fn void do_something_else() @dynamic(0, 5.0)
fn void do_another_thing() @dynamic(0, 2.5)

Importing the dynamic library and setting the base version to 4.5 and minimum version to 3.0, we get the following:

test.c3

import dynlib;
fn void test()
{
    if (@available(dynlib::do_something))
    {
        dynlib::do_something();
    }
    else
    {
        dynlib::do_someting_else();
    }
}

In this example the code would run do_something if available (that is, when the dynamic library is 4.0 or higher), or fallback to do_something_else otherwise.

If we tried to conditionally add something not available in the compilation itself, that is a compile time error:

if (@available(dynlib::do_another_thing))
{
    // Error: This function is not available with 3.0
    dynlib::do_another_thing(); 
}

Versionless dynamic loading is also possible:

maybe_dynlib.c3i

module maybe_dynlib;
fn void testme() @dynamic;

test2.c3

import maybe_dynlib;
fn void testme2()
{
    if (@available(maybe_dynlib::testme))
    {
        dynlib::testme();
    }
}

This allows things like optionally loading dynamic libraries on the platforms where this is available.

Textual Includes

$include

It’s sometimes useful to include an entire file, doing so employs the $include function. Includes are only valid at the top level.

File Foo.c3

module foo;

$include("Foo.x");

fn void test()
{
    io::printf("%d", testX(2));
}

File Foo.x

fn testX(int i)
{
    return i + 1;
}

The result is as if Foo.c3 contained the following:

module foo;

fn testX(int i)
{
    return i + 1;
}

fn void test()
{
    io::printf("%d", testX(2));
}

The include may use an absolute or relative path, the relative path is always relative to the source file in which the include appears.

Note that to use it, the trust level of the compiler must be set to at least 2 with the —trust option (i.e. use --trust=include or --trust=full from the command line).

$exec

An alternative to $include is $exec which is similar to include, but instead includes the output of an external program as the included text.

An example:

import std::io;

// On Linux or MacOS this will insert 'String a = "Hello world!";'
$exec("echo", { "String a = \\\"Hello world!\\\"\\;" });

fn void main()
{
    io::printn(a);
}

Using $exec requires full trust level, which is enabled with -trust=full from the command line.

‘$exec’ will by default run from the /scripts directory for projects, for non-project builds, the current directory is used as well.

$exec Scripting

$exec allows a special scripting mode, where one or more C3 files are compiled on the fly and run by $exec.

import std::io;

// Compile foo.c3 and bar.c3 in the /scripts directory, invoke the resulting binary
// with the argument 'test'
$exec("foo.c3;bar.c3", "test");

fn void main()
{
    ...
}

Non-Recursive Imports

In specific circumstances you only wish to import a module without its submodules. This can be helpful in certain situations where otherwise unnecessary name-collisions would occur, but should not be used in the general case.

The syntax for non-recursive imports is import <module_name> @norecurse; for example:

// Non-recursive import
import mylib @norecurse; 

// Normal import
import mylib;

For example only importing “mylib” into “my_code” and not wishing to import “submod”.

my_code
└── mylib
    └── submod
module mylib;
import std::io;
fn void only_want_this()
{
    io::printn("only_want_this");
}

module mylib::submod;
import std::io;
fn void undesired_fn()
{
    io::printn("undesired_fn");
}

module my_code;
// Using Non-recursive import undesired_fn not found
import mylib @norecurse; 

// Using Recursive import undesired_fn is found
// import mylib;

fn void main()
{
    mylib::only_want_this();
    submod::undesired_fn(); // This should error
}

:::note You can import multiple modules in one line:

import lib1, lib2;

@norecurse can be applied to one of those imports individually:

import lib1 @norecurse, lib2;

Here only lib1 is imported non-recursively and lib2 is imported normally, recursively. :::


title: Examples description: Examples of C3 code sidebar:

order: 35

Overview

This is meant for a quick reference, to the learn more of the details, check the relevant sections.

If Statement

fn void if_example(int a) 
{
    if (a > 0) 
    {
        // ..
    } 
    else 
    {
        // ..
    }
}

For Loop

fn void example_for() 
{
    // the for-loop is the same as C99. 
    for (int i = 0; i < 10; i++) 
    {
        io::printfn("%d", i);
    }

    // also equal
    for (;;) 
    {
        // ..
    }
}

Foreach Loop

// Prints the values in the slice.
fn void example_foreach(float[] values) 
{
    foreach (index, value : values) 
    {
        io::printfn("%d: %f", index, value);
    }
}

// Updates each value in the slice
// by multiplying it by 2.
fn void example_foreach_by_ref(float[] values) 
{
    foreach (&value : values) 
    {
        *value *= 2;
    }
}

While Loop

fn void example_while() 
{
    // again exactly the same as C
    int a = 10;
    while (a > 0) 
    {
        a--;
    }

    // Declaration 
    while (Point* p = getPoint()) 
    {
        // ..
    }
}

Enum And Switch

Switches have implicit break and scope. Use “nextcase” to implicitly fallthrough or use comma:

enum Height : uint
{
    LOW,
    MEDIUM,
    HIGH,
}

fn void demo_enum(Height h)
{
    switch (h)
    {
        case LOW:
        case MEDIUM:
            io::printn("Not high");
            // Implicit break.
        case HIGH:
            io::printn("High");
    }

    // This also works
    switch (h)
    {
        case LOW:
        case MEDIUM:
            io::printn("Not high");
            // Implicit break.
        case Height.HIGH:
            io::printn("High");
    }

    // Completely empty cases are not allowed.
    switch (h)
    {
        case LOW:
            break; // Explicit break required, since switches can't be empty.
        case MEDIUM:
            io::printn("Medium");
        case HIGH:
            break;
    }

    // special checking of switching on enum types
    switch (h)
    {
        case LOW:
        case MEDIUM:
        case HIGH:
            break;
        default:    // warning: default label in switch which covers all enumeration value
            break;
    }

    // Using "nextcase" will fallthrough to the next case statement,
    // and each case statement starts its own scope.
    switch (h)
    {
        case LOW:
            int a = 1;
            io::printn("A");
            nextcase;
        case MEDIUM:
            int a = 2;
            io::printn("B");
            nextcase;
        case HIGH:
            // a is not defined here
            io::printn("C");
    }
}

Enums are always namespaced.

Enum support various reflection properties: .values returns an array with all enums. .len or .elements returns the number of enum values, .inner returns the storage type. .names returns an array with the names of all enums. .associated returns an array of the typeids of the associated values for the enum.

enum State : uint 
{
    START,
    STOP,
}

State start = State.values[0];
usz enums = State.elements;   // 2
String[] names = State.names; // [ "START", "STOP" ]

Defer

Defer will be invoked on scope exit.

fn void test(int x)
{
    defer io::printn();
    defer io::print("A");
    if (x == 1) return;
    {
        defer io::print("B");
        if (x == 0) return;
    }
    io::print("!");
}

fn void main()
{
    test(1); // Prints "A"
    test(0); // Prints "BA"
    test(10); // Prints "B!A"
}

Because it’s often relevant to run different defers when having an error return there is also a way to create an error defer, by using the catch keyword directly after the defer. Similarly using defer try can be used to only run if the scope exits in a regular way.

fn void! test(int x)
{
    defer io::printn("");
    defer io::print("A");
    defer try io::print("X");
    defer catch io::print("B");
    defer (catch err) io::printf("%s", err);
    if (x == 1) return SearchResult.MISSING?;
    io::print("!");
}

test(0); // Prints "!XA"
test(1); // Prints "MISSINGBA" and returns a FooError

Struct Types

def Callback = fn int(char c);

enum Status : int
{
    IDLE,
    BUSY,
    DONE,
}

struct MyData
{
    char* name;
    Callback open;
    Callback close;
    State status;

    // named sub-structs (x.other.value)
    struct other 
    {
        int value;
        int status;   // ok, no name clash with other status
    }

    // anonymous sub-structs (x.value)
    struct 
    {
        int value;
        int status;   // error, name clash with other status in MyData
    }

    // anonymous union (x.person)
    union 
    {
        Person* person;
        Company* company;
    }

    // named sub-unions (x.either.this)
    union either 
    {
        int this;
        bool  or;
        char* that;
    }
}

Function Pointers

module demo;

def Callback = fn int(char* text, int value);

fn int my_callback(char* text, int value) 
{
    return 0;
}

Callback cb = &my_callback;

fn void example_cb() 
{
    int result = cb("demo", 123);
    // ..
}

Error Handling

Errors are handled using optional results, denoted with a ‘!’ suffix. A variable of an optional result type may either contain the regular value or a fault enum value.

fault MathError
{
    DIVISION_BY_ZERO
}

fn double! divide(int a, int b)
{
    // We return an optional result of type DIVISION_BY_ZERO
    // when b is zero.
    if (b == 0) return MathError.DIVISION_BY_ZERO?;
    return (double)a / (double)b;
}

// Re-returning an optional result uses "!" suffix
fn void! testMayError()
{
    divide(foo(), bar())!;
}

fn void main()
{
    // ratio is an optional result.
    double! ratio = divide(foo(), bar());

    // Handle the optional result value if it exists.
    if (catch err = ratio)
    {
        case MathError.DIVISION_BY_ZERO:
            io::printn("Division by zero\n");
            return;
        default:
            io::printn("Unexpected error!");
            return;
    }
    // Flow typing makes "ratio"
    // have the plain type 'double' here.
    io::printfn("Ratio was %f", ratio);
}
fn void print_file(String filename)
{
    String! file = io::load_file(filename);

    // The following function is not called on error,
    // so we must explicitly discard it with a void cast.
    (void)io::printfn("Loaded %s and got:\n%s", filename, file);

    if (catch err = file)
    {
        case IoError.FILE_NOT_FOUND:
            io::printfn("I could not find the file %s", filename);
        default:
            io::printfn("Could not load %s.", filename);
    }
}

// Note that the above is only illustrating how Optionals may skip 
// call invocation. A more normal implementation would be:

fn void print_file2(String filename)
{
    String! file = io::load_file(filename);

    if (catch err = file)
    {
        // Print the error 
        io::printfn("Failed to load %s: %s", filename, err);
        // We return, so that below 'file' will be unwrapped.
        return;
    }    
    // No need for a void cast here, 'file' is unwrappeed to 'String'.
    io::printfn("Loaded %s and got:\n%s", filename, file);
}

Read more about optionals and error handling here.

Contracts

Pre- and postconditions are optionally compiled into asserts helping to optimize the code.

<*
 @param foo "the number of foos" 
 @require foo > 0, foo < 1000
 @return "number of foos x 10"
 @ensure return < 10000, return > 0
*>
fn int testFoo(int foo)
{
    return foo * 10;
}

<*
 @param array "the array to test"
 @param length "length of the array"
 @require length > 0
*>
fn int getLastElement(int* array, int length)
{
    return array[length - 1];
}

Read more about contracts here.

Struct Methods

It’s possible to namespace functions with a union, struct or enum type to enable “dot syntax” calls:

struct Foo
{
    int i;
}

fn void Foo.next(Foo* this)
{
    if (this) this.i++;
}

fn void test()
{
    Foo foo = { 2 };
    foo.next();
    foo.next();
    // Prints 4
    io::printfn("%d", foo.i); 
}

Macros

Macro arguments may be immediately evaluated.

macro foo(a, b)
{
    return a(b);
}

fn int square(int x)
{
    return x * x;
}

fn int test()
{
    int a = 2;
    int b = 3;    
    return foo(&square, 2) + a + b; // 9
    // return foo(square, 2) + a + b; 
    // Error the symbol "square" cannot be used as an argument.
}

Macro arguments may have deferred evaluation, which is basically text expansion using #var syntax.

macro @foo(#a, b, #c)
{
    c = a(b) * b;
}

macro @foo2(#a)
{
    return a * a;
}

fn int square(int x)
{
    return x * x;
}

fn int test1()
{
    int a = 2;
    int b = 3; 
    @foo(square, a + 1, b);
    return b; // 27   
}

fn int test2()
{
    return @foo2(1 + 1); // 1 + 1 * 1 + 1 = 3
}

Improve macro errors with preconditions:

<*
 @param x "value to square"
 @require types::is_numeric($typeof(x)) "cannot multiply"
*>
macro square(x)
{
    return x * x;
}

fn void test()
{
    square("hello"); // Error: cannot multiply "hello"
    int a = 1;
    square(&a); // Error: cannot multiply '&a'
}

Read more about macros here.

Compile Time Reflection & Execution

Access type information and loop over values at compile time:

import std::io;

struct Foo
{
    int a;
    double b;
    int* ptr;
}

macro print_fields($Type)
{
    $foreach ($field : $Type.membersof)
        io::printfn("Field %s, offset: %s, size: %s, type: %s", 
                $field.nameof, $field.offsetof, $field.sizeof, $field.typeid.nameof);
    $endforeach
}


fn void main()
{
    print_fields(Foo);
}

This prints on x64:

Field a, offset: 0, size: 4, type: int
Field b, offset: 8, size: 8, type: double
Field ptr, offset: 16, size: 8, type: int*

Compile Time Execution

Macros with only compile time variables are completely evaluated at compile time:

macro long fib(long $n)
{
    $if $n <= 1:
        return $n;
    $else
        return fib($n - 1) + fib($n - 2);
    $endif
}

const long FIB19 = fib(19); 
// Same as const long FIB19 = 4181;

:::note C3 macros are designed to provide a replacement for C preprocessor macros. They extend such macros by providing compile time evaluation using constant folding, which offers an IDE friendly, limited, compile time execution.

However, if you are doing more complex compile time code generation it is recommended to use $exec and related techniques to generate code in external scripts instead. ::: Read more about compile time execution here.

Generic Modules

Generic modules implements a generic system.

module stack(<Type>);
struct Stack
{
    usz capacity;
    usz size;
    Type* elems;
}


fn void Stack.push(Stack* this, Type element)
{
    if (this.capacity == this.size)
    {
        this.capacity *= 2;
        this.elems = realloc(this.elems, Type.sizeof * this.capacity);
    }
    this.elems[this.size++] = element;
}

fn Type Stack.pop(Stack* this)
{
    assert(this.size > 0);
    return this.elems[--this.size];
}

fn bool Stack.empty(Stack* this)
{
    return !this.size;
}

Testing it out:

def IntStack = Stack(<int>);

fn void test()
{
    IntStack stack;
    stack.push(1);
    stack.push(2);
    // Prints pop: 2
    io::printfn("pop: %d", stack.pop());
    // Prints pop: 1
    io::printfn("pop: %d", stack.pop());

    Stack(<double>) dstack;
    dstack.push(2.3);
    dstack.push(3.141);
    dstack.push(1.1235);
    // Prints pop: 1.1235
    io::printfn("pop: %f", dstack.pop());
}

Read more about generic modules here

Dynamic Calls

Runtime dynamic dispatch through interfaces:

import std::io;

// Define a dynamic interface
interface MyName
{
    fn String myname();
}

struct Bob (MyName) { int x; }

// Required implementation as Bob implements MyName
fn String Bob.myname(Bob*) @dynamic { return "I am Bob!"; }

// Ad hoc implementation
fn String int.myname(int*) @dynamic { return "I am int!"; }

fn void whoareyou(any a)
{
    MyName b = (MyName)a;
    if (!&b.myname)
    {
        io::printn("I don't know who I am.");
        return;
    }
    io::printn(b.myname());
}

fn void main()
{
    int i = 1;
    double d = 1.0;
    Bob bob;

    any a = &i;
    whoareyou(a);
    a = &d;
    whoareyou(a);
    a = &bob;
    whoareyou(a);
}

Read more about dynamic calls here.


title: Changes From C description: Changes From C sidebar:

order: 702

Although C3 is trying to improve on C, this does not only mean addition of features, but also removal, or breaking changes:

No mandatory header files

There is a C3 interchange header format for declaring interfaces of libraries, but it is only used for special applications.

Removal of the old C macro system

The old C macro system is replaced by a new C3 macro system.

Import and modules

C3 uses module imports instead of header includes to link modules together.

Member access using . even for pointers

The -> operator is removed, access uses dot for both direct and pointer access. Note that this is just single access: to access a pointer of a pointer (e.g. int**) an explicit dereference would be needed.

Different operator precedence

Notably bit operations have higher precedence than +/-, making code like this: a & b == c evaluate like (a & b) == c instead of C’s a & (b == c). See the page about precedence rules.

Removal of the const type qualifier

The const qualifier is only retained for actual constant variables. C3 uses a special type of post condition for functions to indicate that they do not alter in parameters.

<*
 This function ensures that foo is not changed in the function.
 @param [in] foo
 @param [out] bar
*>
fn void test(Foo* foo, Bar* bar)
{
    bar.y = foo.x;
    // foo.x = foo.x + 1 - compile time error, can't write to 'in' param.
    // int x = bar.y     - compile time error, can't read from an 'out' param.
}

Rationale: const correctness requires littering const across the code base. Although const is useful, it provides weaker guarantees that it appears.

Fixed arrays do not decay and have copy semantics

C3 has three different array types. Variable arrays and slices decay to pointers, but fixed arrays are value objects and do not decay.

int[3] a = { 1, 2, 3 };
int[4]* b = &a; // No conversion
int* c = a; // ERROR
int* d = &a; // Valid implicit conversion
int* e = b; // Valid implicit conversion
int[3] f = a; // Copy by value!
Removal of multiple declaration syntax with initialization

Only a single declaration with initialization is allowed per statement in C3:

int i, j = 1; // ERROR
int a = 1;    // Ok
int b, c;     // Ok

In conditionals, a special form of multiple declarations are allowed but each must then provide its type:

for (int i = 0, int j = 1; i < 10; i++, j++) { ... }
Integer promotions rules and safe signed-unsigned comparisons

Promotion rules for integer types are different from C. C3 allows implicit widening only where there is only a single way to widen the expression. To explain the latter: take the case of long x = int_val_1 + int_val_2. In C this would widen the result of the addition: long x = (long)(int_val_1 + int_val_2), but there is another possible way to widen: long x = (long)int_val_1 + (long)int_val_2. so in this case, the widening is disallowed. However, long x = int_val_1 is unambiguous, so C3 permits it just like C (read more on the conversion page.

C3 also adds safe signed-unsigned comparisons: this means that comparing signed and unsigned values will always yield the correct result:

// The code below would print "Hello C3!" in C3 and "Hello C!" in C.
int i = -1;
uint j = 1;
if (i < j)
{
    printf("Hello C3!\n");
}
else
{
    printf("Hello C!\n");
}
Goto removed

goto is removed and replaced with labelled break and continue together with the nextcase statement that allows you to jump between cases in a switch statement.

Rationale: It is very difficult to make goto work well with defer and implicit unwrapping of optional results. It is not just making the compiler harder to write, but the code is harder to understand as well. The replacements together with defer cover many if not all usages of goto in regular code.

Implicit break in switches

Empty case statements have implicit fall through in C3, otherwise the nextcase statement is needed nextcase can also be used to jump to any other case statement in the switch.

switch (h)
{
    case 1:
        a = 1;
        nextcase; // Fall through
    case 2:
        b = 123;
    case 3:
        a = 2;
        nextcase 2; // Jump to case 2
    default:
        a = 111;
}
Locals variables are implicitly zeroed

In C global variables are implicitly zeroed out, but local variables aren’t. In C3 local variables are zeroed out by default, but may be explicitly undefined (using the @noinit attribute) if you wish to match the C behaviour.

Rationale for this change
Compound literal syntax changed
// C style:
call_foo((Foo) { 1, 2, 3 });

// C++ style (1):
call_foo(Foo(1, 2, 3));

// C++ style (2):
call_foo(Foo { 1, 2, 3 });

// C3:
call_foo(Foo { 1, 2, 3 } );

// C3 with inference:
call_foo({ 1, 2, 3 });
Bitfields replaced by bitstructs

Bitfields are replaced by bitstructs that have a well-defined encapsulating type, and an exact bit layout.

// C
struct Foo
{
    int a : 3;
    unsigned b : 4;
    MyEnum c : 7;
};

struct Flags
{
    bool has_hyperdrive : 1;
    bool has_tractorbeam : 1;
    bool has_plasmatorpedoes : 1;
}    

// C3
bitstruct Foo : short
{  
    int a : 0..2;
    uint b : 3..6;
    MyEnum c : 7..13;
}

// Simple form, only allowed when all fields are bools.
struct Flags : char
{
    bool has_hyperdrive;
    bool has_tractorbeam;
    bool has_plasmatorpedoes;
}
Evaluation order is well-defined

Evaluation order is left-to-right, and in assignment expressions, assignment happens after expression evaluation.

Signed overflow is well-defined

Signed integer overflow always wraps using 2s complement. It is never undefined behaviour.

Octal removed

The old 0777 octal syntax is removed and replaced by a 0o prefix, e.g. 0o777. Strings do not support octal sequences aside from '\0'.


title: Types description: Types sidebar:

order: 38

Overview

As usual, types are divided into basic types and user defined types (enum, union, struct, fault, def). All types are defined on a global level.

Naming

All user defined types in C3 starts with upper case. So MyStruct or Mystruct would be fine, mystruct_t or mystruct would not. This naming requirement ensures that the language is easy to parse for tools. It is possible to use attributes to change the external name of a type:

struct Stat @extern("stat")
{
    // ...
} 

fn CInt stat(char* pathname, Stat* buf);

This would affect things like generated C headers.

Differences from C

Unlike C, C3 does not use type qualifiers. const exists, but is a storage class modifier, not a type qualifier. Instead of volatile, volatile loads and stores are used. Restrictions on function parameter usage are instead described by parameter preconditions.

typedef has a slightly different syntax and renamed def.

C3 also requires all function pointers to be used with a def for example:

def Callback = fn void();
Callback a = null; // Ok!
fn Callback getCallback() { /* ... */ } // Ok!

// fn fn void() getCallback() { /* ... */ } - ERROR!
// fn void() a = null; - ERROR!

Basic types

Basic types are divided into floating point types, and integer types. Integer types being either signed or unsigned.

Integer types
Name bit size signed
bool* 1 no
ichar 8 yes
char 8 no
short 16 yes
ushort 16 no
int 32 yes
uint 32 no
long 64 yes
ulong 64 no
int128 128 yes
uint128 128 no
iptr** varies yes
uptr** varies no
isz** varies yes
usz** varies no

* bool will be stored as a byte.
** size, pointer and pointer sized types depend on platform.

Integer arithmetics

All signed integer arithmetics uses 2’s complement.

Integer constants

Integer constants are 1293832 or -918212. Without a suffix, suffix type is assumed to the signed integer of arithmetic promotion width. Adding the u suffix gives a unsigned integer of the same width. Use ixx and uxx – where xx is the bit width for typed integers, e.g. 1234u16

Integers may be written in decimal, but also

In the case of binary, octal and hexadecimal, the type is assumed to be unsigned.

Furthermore, underscore _ may be used to add space between digits to improve readability e.g. 0xFFFF_1234_4511_0000, 123_000_101_100

TwoCC, FourCC and EightCC

FourCC codes are often used to identify binary format types. C3 adds direct support for 4 character codes, but also 2 and 8 characters:

Conversion is always done so that the character string has the correct ordering in memory. This means that the same characters may have different integer values on different architectures due to endianness.

Base64 and hex data literals

Base64 encoded values work like TwoCC/FourCC/EightCC, in that is it laid out in byte order in memory. It uses the format b64'<base64>'. Hex encoded values work as base64 but with the format x'<hex>'. In data literals any whitespace is ignored, so '00 00 11'x encodes to the same value as x'000011'.

In our case we could encode b64'Rk9PQkFSMTE=' as 'FOOBAR11'.

Base64 and hex data literals initializes to arrays of the char type:

char[*] hello_world_base64 = b64"SGVsbG8gV29ybGQh";
char[*] hello_world_hex = x"4865 6c6c 6f20 776f 726c 6421";
String literals, and raw strings

Regular string literals is text enclosed in " ... " just like in C. C3 also offers two other types of literals: multi-line strings and raw strings.

Raw strings uses text between ` `. Inside of a raw string, no escapes are available. To write a ` double the character:

char* foo = `C:\foo\bar.dll`;
char* bar = `"Say ``hello``"`;
// Same as
char* foo = "C:\\foo\\bar.dll";
char* bar = "\"Say `hello`\"";
Floating point types
Name bit size
bfloat16* 16
float16* 16
float 32
double 64
float128* 128

*support is still incomplete.

Floating point constants

Floating point constants will at least use 64 bit precision. Just like for integer constants, it is allowed to use underscore, but it may not occur immediately before or after a dot or an exponential.

Floating point values may be written in decimal or hexadecimal. For decimal, the exponential symbol is e (or E, both are acceptable), for hexadecimal p (or P) is used: -2.22e-21 -0x21.93p-10

It is possible to type a floating point by adding a suffix:

Suffix type
bf16 bfloat16
f16 float16
f32 or f float
f64 double
f128 float128

C compatibility

For C compatibility the following types are also defined in std::core::cinterop

Name c type
CChar char
CShort short int
CUShort unsigned short int
CInt int
CUInt unsigned int
CLong long int
CULong unsigned long int
CLongLong long long
CULongLong unsigned long long
CLongDouble long double

float and double will always match their C counterparts.

Note that signed C char and unsigned char will correspond to ichar and char. CChar is only available to match the default signedness of char on the platform.

Other built-in types

Pointer types

Pointers mirror C: Foo* is a pointer to a Foo, while Foo** is a pointer to a pointer of Foo.

The typeid type

The typeid can hold a runtime identifier for a type. Using <typename>.typeid a type may be converted to its unique runtime id, e.g. typeid a = Foo.typeid;. This value is pointer-sized.

The any type

C3 contains a built-in variant type, which is essentially struct containing a typeid plus a void* pointer to a value. While it is possible to cast the any pointer to any pointer type, it is recommended to use the anycast macro or checking the type explicitly first.

fn void main()
{
    int x;
    any y = &x;
    int* w = (int*)y;                // Returns the pointer to x
    double* z_bad = (double*)y;      // Don't do this!
    double! z = anycast(y, double);  // The safe way to get a value
    if (y.type == int.typeid)
    {
        // Do something if y contains an int*
    }
}

Switching over the any type is another method to unwrap the pointer inside:

fn void test(any z)
{
    // Unwrapping switch
    switch (z)
    {
        case int: 
            // z is unwrapped to int* here
        case double:
            // z is unwrapped to double* here
    }
    // Assignment switch
    switch (y = z)
    {
        case int:
            // y is int* here
    }
    // Direct unwrapping to a value is also possible:
    switch (w = *z)
    {
        case int:
            // w is int here
    }
    // Finally, if we just want to deal with the case
    // where it is a single specific type:
    if (z.type == int.typeid)
    {
        // This is safe here:
        int* a = (int*)z;
    }
    if (try b = *anycast(z, int))
    {
        // b is an int:
        foo(b * 3);
    }
}

any.type returns the underlying pointee typeid of the contained value. any.ptr returns the raw void* pointer.

Array types

Arrays are indicated by [size] after the type, e.g. int[4]. Slices use the type[]. For initialization the wildcard type[*] can be used to infer the size from the initializer. See the chapter on arrays.

Vector types

Vectors use [<size>] after the type, e.g. float[<3>], with the restriction that vectors may only form out of integers, floats and booleans. Similar to arrays, wildcard can be used to infer the size of a vector: int[<*>] a = { 1, 2 }.

Types created using def

“typedef”

Like in C, C3 has a “typedef” construct, def <typename> = <type>

def Int32 = int;
def Vector2 = float[<2>];

/* ... */

Int32 a = 1;
int b = a;

Function pointer types

Function pointers are always used through a def:

def Callback = fn void(int value);
Callback callback = &test;

fn void test(int a) { /* ... */ }

To form a function pointer, write a normal function declaration but skipping the function name. fn int foo(double x) -> fn int(double x).

Function pointers can have default arguments, e.g. def Callback = fn void(int value = 0) but default arguments and parameter names are not taken into account when determining function pointer assignability:

def Callback = fn void(int value = 1);
fn void test(int a = 0) { /* ... */ }

Callback callback = &main; // Ok

fn void main()
{
    callback(); // Works, same as test(0);
    test(); // Works, same as test(1);
    callback(.value = 3); // Works, same as test(3)
    test(.a = 4); // Works, same as test(4)
    // callback(.a = 3); ERROR!
}

Distinct types

Distinct types is a kind of type alias which creates a new type that has the same properties as the original type but is - as the name suggests - distinct from it. It cannot implicitly convert into the other type using the syntax distict <name> = <type>

distinct MyId = int;
fn void* get_by_id(MyId id) { ... }

fn void test(MyId id)
{
    void* val = get_by_id(id); // Ok
    void* val2 = get_by_id(1); // Literals convert implicitly
    int a = 1;
    // void* val3 = get_by_id(a); // ERROR expected a MyId
    void* val4 = get_by_id((MyId)a); // Works
    // a = id; // ERROR can't assign 'MyId' to 'int'
}

Inline distinct

Using inline in the distinct declaration allows a distinct type to implicitly convert to its underlying type:

distinct Abc = int;
distinct Bcd = inline int;

fn void test()
{
    Abc a = 1;
    Bcd b = 1;

    // int i = a; Error: Abc cannot be implicitly converted to 'int'
    int i = b; // This is valid

    // However, 'inline' does not allow implicit conversion from 
    // the inline type to the distinct type:
    // a = i; Error: Can't implicitly convert 'int' to 'Abc'
    // b = i; Error: Can't implicitly convert 'int' to 'Bcd'
}

Generic types

import generic_list; // Contains the generic MyList

struct Foo { 
    int x; 
}

// ✅ def for each type used with a generic module.
def IntMyList = MyList(<Foo>);
MyListFoo working_example;

// ❌ An inline type definition will give an error.
// Only allowed in a type definition or macro 
// To avoid this A type may be declared with @adhoc
MyList<Foo> failing_example = MyList(<Foo>);

Find out more about generic types.

Enum

Enum or enumerated types use the following syntax:

enum State : int 
{
    WAITING,
    RUNNING,
    TERMINATED
}

// Access enum values via:
State current_state = State.WAITING;

The access requires referencing the enum‘s name as State.WAITING because an enum like State is a separate namespace by default, just like C++’s class enum.

Enum associated values

It is possible to associate each enum value with one or more a static values.

enum State : int (String description) 
{
    WAITING = "waiting",
    RUNNING = "running",
    TERMINATED = "ended",
}

fn void main() 
{
    State process = State.RUNNING;
    io::printfn("%s", process.description);
}

Multiple static values can be associated with an enum value, for example:

struct Position
{
    int x;
    int y;
}

enum State : int (String desc, bool active, Position pos)
{
    WAITING    = { "waiting", false, { 1, 2} },
    RUNNING    = { "running", true,  {12,22} },
    TERMINATED = { "ended",   false, { 0, 0} },
}

fn void main()
{
    State process = State.RUNNING;
    if (process.active)
    {
        io::printfn("Process is: %s", process.desc);
        io::printfn("Position x: %d", process.pos.x);
    }
}

Enum type inference

When an enum is used where the type can be inferred, like in switch case-clauses or in variable assignment, the enum name is not required:

State process = WAITING; // State.WAITING is inferred.
switch (process)
{
    case RUNNING: // State.RUNNING is inferred
        io::printfn("Position x: %d", process.pos.x);
    default:
        io::printfn("Process is: %s", process.desc);
}

fn void test(State s) { ... }

test(RUNNING); // State.RUNNING is inferred

If the enum without it’s name matches with a global in the same scope, it needs the enum name to be added as a qualifier, for example:

module test;

// Global variable
// ❌ Don't do this!
const State RUNNING = State.TERMINATED; 

test(RUNNING);       // Ambiguous
test(test::RUNNING); // Uses global variable.
test(State.RUNNING); // Uses enum constant.

Optional Type

An Optional type is created by taking a type and appending !. An Optional type behaves like a tagged union, containing either the result or an Excuse that is of a fault type.

Once extracted, any specific fault can be converted to an anyfault.

int! i;
i = 5; // Assigning a real value to i.
i = IOResult.IO_ERROR?; // Assigning an optional result to i.
anyfault b = SearchError.MISSING;
b = @catch(i); // Assign the Excuse in i to b (IO_ERROR)

Only variables, expressions and function returns may be Optionals. Function and macro parameters in their definitions may not be optionals.

fn Foo*! getFoo() { /* ... */ } // ✅ Ok!
int! x = 0; // ✅ Ok!
fn void processFoo(Foo*! f) { /* ... */ } // ❌ fn paramater

Read more about the Optional types on the page about Optionals and error handling.

Optional Excuses are of type Fault

When an Optional does not contain a result, it is empty, and has an Excuse, which is afault. The anyfault type may contain any such fault.

fault IOResult
{
    IO_ERROR,
    PARSE_ERROR
}   

fault MapResult
{
    NOT_FOUND
}

Like the typeid type, the constants are pointer sized and each value is globally unique. For example the underlying value of MapResult.NOT_FOUND is guaranteed to be different from IOResult.IO_ERROR. This is true even if they are separately compiled.

:::note The underlying values assigned to a fault may vary each time a program is compiled. :::

A fault may be stored as a normal value, but is also unique so that it may be passed in an Optional as a function return value using the rethrow ! operator.

Struct types

Structs are always named:

struct Person  
{
    char age;
    String name;
}

A struct’s members may be accessed using dot notation, even for pointers to structs.

fn void test()
{
    Person p;
    p.age = 21;
    p.name = "John Doe";

    io::printfn("%s is %d years old.", p.name, p.age);

    Person* p_ptr_ = &p;
    p_ptr.age = 20; // Ok!

    io::printfn("%s is %d years old.", p_ptr.name, p_ptr.age);
}

(One might wonder whether it’s possible to take a Person** and use dot access. – It’s not allowed, only one level of dereference is done.)

To change alignment and packing, attributes such as @packed may be used.

Struct subtyping

C3 allows creating struct subtypes using inline:

struct ImportantPerson 
{
    inline Person person;
    String title;
}

fn void print_person(Person p)
{
    io::printfn("%s is %d years old.", p.name, p.age);
}


fn void test()
{
    ImportantPerson important_person;
    important_person.age = 25;
    important_person.name = "Jane Doe";
    important_person.title = "Rockstar";

    // Only the first part of the struct is copied.
    print_person(important_person); 
}

Union types

Union types are defined just like structs and are fully compatible with C.

union Integral  
{
    char as_byte;
    short as_short;
    int as_int;
    long as_long;
}

As usual unions are used to hold one of many possible values:

fn void test()
{
    Integral i;
    i.as_byte = 40; // Setting the active member to as_byte

    i.as_int = 500; // Changing the active member to as_int

    // Undefined behaviour: as_byte is not the active member, 
    // so this will probably print garbage.
    io::printfn("%d\n", i.as_byte);
}

Note that unions only take up as much space as their largest member, so Integral.sizeof is equivalent to long.sizeof.

Nested sub-structs / unions

Just like in C99 and later, nested anonymous sub-structs / unions are allowed. Note that the placement of struct / union names is different to match the difference in declaration.

struct Person  
{
    char age;
    String name;
    union 
    {
        int employee_nr;
        uint other_nr;
    }
    union subname 
    {
        bool b;
        Callback cb;
    }
}

Bitstructs

Bitstructs allows storing fields in a specific bit layout. A bitstruct may only contain integer types and booleans, in most other respects it works like a struct.

The main differences is that the bitstruct has a backing type and each field has a specific bit range. In addition, it’s not possible to take the address of a bitstruct field.

bitstruct Foo : char
{
    int a : 0..2;
    int b : 4..6;
    bool c : 7;
}

fn void test()
{   
    Foo f;
    f.a = 2;
    char x = (char)f;
    io::printfn("%d", (char)f); // prints 2
    f.b = 1;
    io::printfn("%d", (char)f); // prints 18 
    f.c = true;
    io::printfn("%d", (char)f); // prints 146
}

The bitstruct will follow the endianness of the underlying type:

bitstruct Test : uint
{
    ushort a : 0..15;
    ushort b : 16..31;
}

fn void test()
{
    Test t;
    t.a = 0xABCD;
    t.b = 0x789A;
    char* c = (char*)&t;

    // Prints 789AABCD
    io::printfn("%X", (uint)t); 

    for (int i = 0; i < 4; i++) 
    {
        // Prints CDAB9A78
        io::printf("%X", c[i]); 
    }
    io::printn();
}

It is however possible to pick a different endianness, in which case the entire representation will internally assume big endian layout:

bitstruct Test : uint @bigendian
{
    ushort a : 0..15;
    ushort b : 16..31;
}

In this case the same example yields CDAB9A78 and 789AABCD respectively.

Bitstruct backing types may be integers or char arrays. The difference in layout is somewhat subtle:

bitstruct Test1 : char[4]
{
    ushort a : 0..15;
    ushort b : 16..31;
}
bitstruct Test2 : char[4] @bigendian
{
    ushort a : 0..15;
    ushort b : 16..31;
}

fn void test()
{
    Test1 t1;
    Test2 t2;
    t1.a = t2.a = 0xABCD;
    t1.b = t2.b = 0x789A;

    char* c = (char*)&t1;
    for (int i = 0; i < 4; i++) 
    {
        // Prints CDAB9A78 on x86
        io::printf("%X", c[i]); 
    }
    io::printn();

    c = (char*)&t2;
    for (int i = 0; i < 4; i++) 
    {
        // Prints ABCD789A
        io::printf("%X", c[i]); 
    }
    io::printn();
}

Bitstructs can be made to have overlapping bit fields. This is useful when modelling a layout which has multiple different layouts depending on flag bits:

bitstruct Foo : char @overlap
{
    int a : 2..5;
    // "b" is valid due to the @overlap attribute
    int b : 1..3; 
}

title: Arrays description: Arrays sidebar:

order: 60

Arrays have a central role in programming. C3 offers built-in arrays, slices and vectors. The standard library enhances this further with dynamically sized arrays and other collections.

Fixed Size 1D Arrays

These are declared as <type>[<size>], e.g. int[4]. Fixed arrays are treated as values and will be copied if given as parameter. Unlike C, the number is part of its type. Taking a pointer to a fixed array will create a pointer to a fixed array, e.g. int[4]*.

Unlike C, fixed arrays do not decay into pointers. Instead, an int[4]* may be implicitly converted into an int*.

// C
int foo(int *a) { ... }

int x[3] = { 1, 2, 3 };
foo(x);

// C3
fn int foo(int *a) { ... }

int[3] x = { 1, 2, 3 };
foo(&x);

When you want to initialize a fixed array without specifying the size, use the [*] array syntax:

int[3] a = { 1, 2, 3 };
int[*] b = { 4, 5, 6 }; // Type inferred to be int[3]

Slice

The final type is the slice <type>[] e.g. int[]. A slice is a view into either a fixed or variable array. Internally it is represented as a struct containing a pointer and a size. Both fixed and variable arrays may be converted into slices, and slices may be implicitly converted to pointers.

fn void test() 
{
    int[4] arr = { 1, 2, 3, 4 };
    int[4]* ptr = &arr;

    // Assignments to slices
    int[] slice1 = &arr;                // Implicit conversion
    int[] slice2 = ptr;                 // Implicit conversion

    // Assignments from slices
    int[] slice3 = slice1;              // Assign slices from other slices
    int* int_ptr = slice1;              // Assign from slice
    int[4]* arr_ptr = (int[4]*)slice1;  // Cast from slice
}

Slicing Arrays

It’s possible to use the range syntax to create slices from pointers, arrays, and other slices.

This is written arr[<start-index> .. <end-index>], where the end-index is inclusive.

fn void test() 
{
    int[5] a = { 1, 20, 50, 100, 200 };

    int[] b = a[0 .. 4]; // The whole array as a slice.
    int[] c = a[2 .. 3]; // { 50, 100 }
}

You can also use the arr[<start-index> : <slice-length>]

fn void test()
{
    int[5] a = { 1, 20, 50, 100, 200 };

    int[] b2 = a[0 : 5]; // { 1, 20, 50, 100, 200 } start-index 0, slice-length 5
    int[] c2 = a[2 : 2]; // { 50, 100 } start-index 2, slice-length 2
}

It’s possible to omit the first and last indices of a range: - arr[..<end-index>] Omitting the start-index will default it to 0 - arr[<start-index>..] Omitting the end-index will assign it to arr.len-1 (this is not allowed on pointers)

Equivalently with index offset arr[:<slice-length>] you can omit the start-index

The following are all equivalent and slice the whole array

fn void test() 
{
    int[5] a = { 1, 20, 50, 100, 200 };

    int[] b = a[0 .. 4];
    int[] c = a[..4];
    int[] d = a[0..];
    int[] e = a[..];

    int[] f = a[0 : 5];
    int[] g = a[:5];
}

You can also slice in reverse from the end with ^i where the index is len-i for example: - ^1 means len-1 - ^2 means len-2 - ^3 means len-3

Again, this is not allowed for pointers since the length is unknown.

fn void test() 
{
    int[5] a = { 1, 20, 50, 100, 200 };

    int[] b1 = a[1 .. ^1];  // { 20, 50, 100, 200 } a[1 .. (a.len-1)]
    int[] b2 = a[1 .. ^2];  // { 20, 50, 100 }      a[1 .. (a.len-2)]
    int[] b3 = a[1 .. ^3];  // { 20, 50 }           a[1 .. (a.len-3)]

    int[] c1 = a[^1..];     // { 200 }              a[(a.len-1)..]
    int[] c2 = a[^2..];     // { 100, 200 }         a[(a.len-2)..]
    int[] c3 = a[^3..];     // { 50, 100, 200 }     a[(a.len-3)..]

    int[] d = a[^3 : 2];    // { 50, 100 }          a[(a.len-3) : 2]

    // Slicing a whole array, the inclusive index of : gives the difference
    int[] e = a[0 .. ^1];   // a[0 .. a.len-1]
    int[] f = a[0 : ^0];    // a[0 : a.len]

}

One may also assign to slices:

int[3] a = { 1, 20, 50 };
a[1..2] = 0; // a = { 1, 0, 0 }

Or copy slices to slices:

int[3] a = { 1, 20, 50 };
int[3] b = { 2, 4, 5 };
a[1..2] = b[0..1]; // a = { 1, 2, 4 }

Copying between two overlapping ranges, e.g. a[1..2] = a[0..1] is unspecified behaviour.

Conversion List

int[4] int[] int[4]* int*
int[4] copy - - -
int[] - assign assign -
int[4]* - cast assign cast
int* - assign assign assign

Note that all casts above are inherently unsafe and will only work if the type cast is indeed compatible.

For example:

int[4] a;
int[4]* b = &a;
int* c = b;

// Safe cast:
int[4]* d = (int[4]*)c; 
int e = 12;
int* f = &e;

// Incorrect, but not checked
int[4]* g = (int[4]*)f;

// Also incorrect but not checked.
int[] h = f[0..2];

Internals

Internally the layout of a slice is guaranteed to be struct { <type>* ptr; usz len; }.

There is a built-in struct std::core::runtime::SliceRaw which has the exact data layout of the fat array pointers. It is defined to be

struct SliceRaw
{
    void* ptr;
    usz len;
}

Iteration Over Arrays

Foreach element by copy

You may iterate over slices, arrays and vectors using foreach (Type x : array). Using compile-time type inference this can be abbreviated to foreach (x : array) for example:

fn void test()
{
    int[4] arr = { 1, 2, 3, 5 };
    foreach (item : arr)
    {
        io::printfn("item: %s", item);
    }

    // Or equivalently, writing the type:
    foreach (int x : arr)
    {
        /* ... */
    }
}

Foreach element by reference

Using & it is possible to get an element by reference rather than by copy. Providing two variables to foreach, the first is assumed to be the index and the second the value:

fn void test()
{
    float[4] arr = { };
    foreach (idx, &item : arr)
    {
        item = 7 + idx; // Mutates the array element
    }

    // Or equivalently, writing the types
    foreach (int idx, Foo* &&item  : arr)
    {
        item = 7 + idx; // Mutates the array element
    }
}

Foreach_r reverse iterating

With foreach_r arrays or slices can be iterated over in reverse order

fn void test()
{
    float[4] arr = { 1.0, 2.0 };
    foreach_r (idx, item : arr)
    {
        // Prints 2.0, 1.0
         io::printfn("item: %s", item); 
    }

    // Or equivalently, writing the types
     foreach_r (int idx, float item : arr)
    {
        // Prints 2.0, 1.0
         io::printfn("item: %s", item); 
    }
}

Iteration Over Array-Like types

It is possible to enable foreach on any custom type by implementing .len and [] methods and annotating them using the @operator attribute:

struct DynamicArray
{
    usz count;
    usz capacity;
    int* elements;
}

macro int DynamicArray.get(DynamicArray* arr, usz element) @operator([])
{
    return arr.elements[element];
}

macro usz DynamicArray.count(DynamicArray* arr) @operator(len)
{
    return arr.count;
}

fn void DynamicArray.push(DynamicArray* arr, int value)
{
    arr.ensure_capacity(arr.count + 1);  // Function not shown in example.
    arr.elements[arr.count++] = value;
}

fn void test()
{
    DynamicArray v;
    v.push(3);
    v.push(7);

    // Will print 3 and 7
    foreach (int i : v)
    {
        io::printfn("%d", i);
    }
}

For more information, see operator overloading

Dynamic Arrays and Lists

The standard library offers dynamic arrays and other collections in the std::collections module.

def ListStr = List(<String>);

fn void test()
{
    ListStr list_str;    

    // Initialize the list on the heap.
    list_str.new_init();    

    list_str.push("Hello");  // Add the string "Hello"
    list_str.push("World");

    foreach (str : list_str)
    {
        io::printn(str);   // Prints "Hello", then "World"
    }
    String str = list_str[1]; // str == "World"
    list_str.free();        // Free all memory associated with list.
}

Fixed Size Multi-Dimensional Arrays

To declare two dimensional fixed arrays as <type>[<x-size>, <y-size>] arr, like int[4][2] arr. Below you can see how this compares to C:

// C 
// Uses: name[<rows>][<columns>]
int array_in_c[4][2] = {
    {1, 2},
    {3, 4},
    {5, 6},
    {7, 8},
};

// C3
// Uses: <type>[<x-size>][<y-size>]
// C3 declares the dimensions, inner-most to outer-most
int[4][2] array = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
};

// To match C we must invert the order of the dimensions 
int[2][4] array = {
    {1, 2},
    {3, 4},
    {5, 6},
    {7, 8},
};

// C3 also supports Irregular arrays, for example:
int[][4] array = {
    { 1 },
    { 2, 3 },
    { 4, 5, 6 },
    { 7, 8, 9, 10 },
};

:::note Accessing the multi-dimensional fixed array has inverted array index order to when the array was declared.

// Uses: <type>[<x-size>][<y-size>]
int[2][4] array = {
    {1, 2},
    {3, 4},
    {5, 6},
    {7, 8},
};

// Access fixed array using: array[<row>][<column>]
int value = array[3][1]; // 8

:::


title: Define description: The def statement sidebar:

order: 61

The “def” statement

The def statement in C3 is intended for aliasing identifiers and types.

Defining a type alias

def <type alias> = <type> creates a type alias. Type aliases need to follow the name convention of user defined types (i.e. capitalized names with at least one lower case letter).

def CharPtr = char*;
def Numbers = int[10];

Function pointers must be aliased in C3. The syntax is somewhat different from C:

def Callback = fn void(int a, bool b);

This defines an alias to function pointer type of a function that returns nothing and requires two arguments: an int and a bool. Here is a sample usage:

Callback cb = my_callback;
cb(10, false);

Distinct types

Similar to def aliases are distinct which create distinct new types. Unlike type aliases, they do not implicitly convert to or from any other type. Literals will convert to the distinct types if they would convert to the underlying type.

Because a distinct type is a standalone type, it can have its own methods, like any other user-defined type.

distinct Foo = int;
Foo f = 0; // Valid since 0 converts to an int.
f = f + 1;
int i = 1;
// f = f + i Error!
f = f + (Foo)i; // Valid

Distinct inline

When interacting with various APIs it is sometimes desirable for distinct types to implicitly convert to its base type, but not from that type.

Behaviour here is analogous how structs may use inline to create struct subtypes.

distinct CString = char*;
distinct ZString = inline char*;
...
CString abc = "abc";
ZString def = "def";
// char* from_abc = abc; // Error!
char* from_def = def; // Valid!

Function and variable aliases

def can also be used to create aliases for functions and variables.

The syntax is def <alias> = <original identifier>.

fn void foo() { ... }
int foo_var;

def bar = foo;
def bar_var = foo_var;

fn void test() 
{
  // These are the same:
  foo();
  bar();

  // These access the same variable:
  int x = foo_var;
  int y = bar_var;
}

Using def to create generic types, functions and variables

It is recommended to favour using def to create aliases for parameterized types, but it can also be used for parameterized functions and variables:

import generic_foo;

// Parameterized function aliases
def int_foo_call = generic_foo::foo_call(<int>);
def double_foo_call = generic_foo::foo_call(<double>);

// Parameterized type aliases
def IntFoo = Foo(<int>);
def DoubleFoo = Foo(<double>);

// Parameterized global aliases
def int_max_foo = generic_foo::max_foo(<int>);
def double_max_foo = generic_foo::max_foo(<double>);

For more information, see the chapter on generics.

Function pointer default arguments and named parameters

It is possible to attach default arguments to function pointer aliases. There is no requirement that the function has the same default arguments. In fact, the function pointer may have default arguments where the function doesn’t have it and vice-versa. Calling the function directly will then use the function’s default arguments, and calling through the function pointer will yield the function pointer alias’ default argument.

Similarly, named parameter arguments follow the alias definition when calling through the function pointer:

def TestFn = fn void(int y = 123);

fn void test(int x = 5)
{
    io::printfn("X = %d", x);
}

fn void main()
{
    TestFn test2 = &test;
    test();         // Prints X = 5
    test2();        // Prints X = 123
    test(x: 3);     // Prints X = 3 
    test2(y: 4);    // Prints X = 4
}

title: Strings description: Strings sidebar:

order: 62

In C3, multiple string types are available, each suited to different use cases.

String


distinct String = inline char[];

\ Strings are usually the typical type to use, they can be sliced , compared etc … \ It is possible to access the length of a String instance through the .len operator.

ZString


distinct ZString = inline char*;

\ ZString is used when working with C code, which expects null-terminated C-style strings of type char*. it is a distinct type so converting to a ZString requires an explicit cast. This helps to remind the user to check there is appropriate \0 termination of the string data.

The ZString methods are outlined below.

:::caution
Ensure the terminal \0 when converting from String to ZString. :::

WString


distinct WString = inline Char16*;

\ The WString type is similar to ZString but uses Char16*, typically for UTF-16 encoded strings. This type is useful for applications where 16-bit character encoding is required.

DString


distinct DString (OutStream) = void*;

\ DString is a dynamic string builder that supports various string operations at runtime, allowing for flexible manipulation without the need for manual memory allocation.

Member functions:

String Member Functions

fn Char16[]! String.to_new_utf16(s, Allocator allocator = allocator::heap())
fn Char16[]! String.to_temp_utf16(s);
fn WString! String.to_wstring(s, Allocator allocator)

```c3 implementation fn String String.free(&s, Allocator allocator = allocator::heap())


```c3 implementation
fn String String.tcopy(s) => s.copy(allocator::temp()) @inline;

```c3 implementation fn String String.copy(s, Allocator allocator = allocator::heap())


```c3 implementation
fn String String.strip_end(string, String needle);

```c3 implementation fn String String.strip(string, String needle);


```c3 implementation
fn String String.trim(string, String to_trim);

```c3 implementation fn bool String.contains(string, String needle);


```c3 implementation
fn bool String.starts_with(string, String needle);

```c3 implementation fn bool String.ends_with(string, String needle);

```c3 implementation
fn usz! String.index_of_char(s, char needle);

```c3 implementation fn usz! String.index_of_char_from(s, char needle, usz start_index);


```c3 implementation
fn usz! String.index_of(s, String needle)
fn usz! String.rindex_of(s, String needle)

```c3 implementation fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = allocator::heap());


```c3 implementation
fn String String.new_split(s, String needle, usz max = 0) => s.split(needle, max, allocator::heap()) @inline;

```c3 implementation // temporary String split fn String String.tsplit(s, String needle, usz max = 0) => s.split(needle, max, allocator::temp());


```c3 implementation
fn String String.tconcat(s1, String s2);

```c3 implementation fn String String.tconcat(s1, String s2) => s1.concat(s2, allocator::temp());

```c3 implementation
fn WString! String.to_temp_wstring(s) => s.to_wstring(allocator::temp());

```c3 implementation fn WString! String.to_new_wstring(s) => s.to_wstring(allocator::heap());

```c3 implementation
fn int128! String.to_int128(s, int base = 10) => s.to_integer(int128, base);

```c3 implementation fn long! String.to_long(s, int base = 10) => s.to_integer(long, base);

```c3 implementation
fn int! String.to_int(s, int base = 10) => s.to_integer(int, base);

```c3 implementation fn short! String.to_short(s, int base = 10) => s.to_integer(short, base);

```c3 implementation
fn ichar! String.to_ichar(s, int base = 10) => s.to_integer(ichar, base);

```c3 implementation fn uint128! String.to_uint128(s, int base = 10) => s.to_integer(uint128, base);

```c3 implementation
fn ulong! String.to_ulong(s, int base = 10) => s.to_integer(ulong, base);

```c3 implementation fn uint! String.to_uint(s, int base = 10) => s.to_integer(uint, base);

```c3 implementation
fn ushort! String.to_ushort(s, int base = 10) => s.to_integer(ushort, base);

```c3 implementation fn char! String.to_uchar(s, int base = 10) => s.to_integer(char, base);

```c3 implementation
fn double! String.to_double(s) => s.to_real(double);

```c3 implementation fn float! String.to_float(s) => s.to_real(float);

```c3 implementation
fn String String.new_ascii_to_upper(s, Allocator allocator = allocator::heap());

```c3 implementation fn Char16[]! String.to_new_utf16(s, Allocator allocator = allocator::heap());


```c3 implementation
fn Char16[]! String.to_temp_utf16(s);

```c3 implementation fn Char32[]! String.to_utf32(s, Allocator allocator);


```c3 implementation
fn Char32[]! String.to_new_utf32(s) => s.to_utf32(allocator::heap()) @inline;

```c3 implementation fn Char32[]! String.to_temp_utf32(s) => s.to_utf32(allocator::temp()) @inline;


```c3 implementation
fn WString! String.to_wstring(s, Allocator allocator);
fn WString! String.to_new_wstring(s) => s.to_wstring(allocator::heap());
fn WString! String.to_temp_wstring(s) => s.to_wstring(allocator::temp());
fn StringIterator String.iterator(s);

ZString Member Functions

```c3 implementation fn String ZString.str_view(str);


```c3 implementation
fn usz ZString.char_len(str);

```c3 implementation fn usz ZString.len(str);

```c3 implementation
fn ZString String.zstr_copy(s, Allocator allocator = allocator::heap())

```c3 implementation fn ZString String.zstr_tcopy(s) => s.zstr_copy(allocator::temp()) @inline;



---
title: Vectors
description: Vectors
sidebar:
    order: 63
---

Vectors - where possible - based on underlying hardware vector implementations. A vector is similar to an array, but 
with additional functionality. The restriction is that a vector may only consist of elements that are numerical
types, boolean or pointers.

A vector is declared similar to an array but uses `[<>]` rather than `[]`, e.g. `int[<4>]`.

(If you are searching for the counterpart of C++'s `std::vector`, look instead at the standard
library [`List` type](/language-common/arrays/#dynamic-arrays-and-lists).)

## Arithmetics on vectors

Vectors support all arithmetics and other operations supported by its underlying type. The operations are
always performed elementwise.

```c3
int[<2>] a = { 23, 11 };
int[<2>] b = { 2, 1 };
int[<2>] c = a * b;     // c = { 46, 11 }

For integer and boolean types, bit operations such as ^ | & << >> are available, and for pointers, pointer arithmetic is supported.

Scalar values

Scalar values will implicitly widen to vectors when used with vectors:

int[<2>] d = { 21, 14 };
int[<2>] e = d / 7;      // e = { 3, 2 }
int[<2>] f = 4;          // f = { 4, 4 }

Additional operations

The std::math module contains a wealth of additional operations available on vectors using dot-method syntax.

Dot methods available for scalar values, such as ceil, fma etc are in general also available for vectors.

Swizzling

Swizzling using dot notation is supported, using x, y, z, w or r, g, b, a:

int[<3>] a = { 11, 22, 33 };
int[<4>] b = a.xxzx;                         // b = { 11, 11, 33, 11 }
int c = b.w;                                 // c = 11;
char[<4>] color = { 0x11, 0x22, 0x33, 0xFF };
char red = color.r;                          // red = 0x11

Array-like operations

Like arrays, it’s possible to make slices and iterate over vectors. It should be noted that the storage alignment of vectors are often different from arrays, which should be taken into account when storing vectors.


title: Essential Error Handling description: Essential Error Handling sidebar:

order: 64

In this section we will go over the essential information about Optionals and safe methods for working with them, for example if (catch optional_value) and the Rethrow operator !.

In the advanced section there are other nice to have features. Like an alternative to safely unwrap a result from an Optionals using if (try optional_value) and an unsafe method to force unwrap !! a result from an Optional, return default values for optionals ?? if they are empty and other more specialised concepts.

What is an Optional?

Optionals are a safer alternative to returning -1 or null from a function, when a valid value can’t be returned. An Optional has either a result or is empty. When an Optional is empty it has an Excuse explaining what happened.

🎁 Unwrapping an Optional

:::note

Unwrapping an Optional is safe because it checks it has a result present before trying to use it.

After unwrapping, the variable then behaves like a normal variable, a non-Optional. :::

Checking if an Optional is empty

import std::io;

fn void! test()
{
    // Return an Excuse by adding '?' after the fault.
    return IoError.FILE_NOT_FOUND?; 
}

fn void main(String[] args)
{
    // If the Optional is empty, assign the
    // Excuse to a variable: 
    if (catch excuse = test())
    {
        io::printfn("test() gave an Excuse: %s", excuse);
    }
}

Automatically unwrapping an Optional result

If we escape the current scope from an if (catch my_var) using a return, break, continue or Rethrow !, then the variable is automatically unwrapped to a non-Optional:

fn void! test() 
{
    int! foo = unreliable_function();
    if (catch excuse = foo) 
    {
        // Return the excuse with `?` operator
        return excuse?;
    }
    // Because the compiler knows 'foo' cannot
    // be empty here, it is unwrapped to non-Optional
    // 'int foo' in this scope:
    io::printfn("foo: %s", foo); // 7
}

Using the Rethrow operator ! to unwrap an Optional value

import std::io;

// Function returning an Optional
fn int! maybe_func() { /* ... */ }

fn void! test() 
{
    // ❌ This will be a compile error
    // maybe_function() returns an Optional
    // and 'bar' is not declared Optional:
    // int bar = maybe_function();

    int bar = maybe_function()!; 
    // ✅ The above is equivalent to:    
    // int! temp = maybe_function();
    // if (catch excuse = temp) return excuse?

    // Now temp is unwrapped to a non-Optional
    int bar = temp; // ✅ This is OK
}

⚠️ Optionals affect types and control flow

Optionals in expressions produce Optionals

Use an Optional anywhere in an expression the resulting expression will be an Optional too.

import std::io;

fn void main(String[] args)
{
    // Returns Optional with result of type `int` or an Excuse
    int! first_optional = 7;

    // This is Optional too:
    int! second_optional = first_optional + 1;
}

Optionals affect function return types

import std::io;

fn int test(int input) 
{
    io::printn("test(): inside function body");
    return input;
}

fn void main(String[] args)
{
    int! optional_argument = 7;

    // `optional_argument` makes returned `returned_optional` 
    // Optional too: 
    int! returned_optional = test(optional_argument);
}

Functions conditionally run when called with Optional arguments

When calling a function with an Optionals as arguments, the result will be the first Excuse found looking left-to-right. The function is only executed if all Optional arguments have a result.

import std::io;

fn int test(int input, int input2) 
{
    io::printn("test(): inside function body");
    return input;
}

fn void main(String[] args)
{
    int! first_optional = IoError.FILE_NOT_FOUND?;
    int! second_optional = 7;

    // Return first excuse we find
    int! third_optional = test(first_optional, second_optional);
    if (catch excuse = third_optional) 
    {
        // excuse == IoError.FILE_NOT_FOUND
        io::printfn("third_optional's Excuse: %s", excuse); 
    }
}

Interfacing with C

For C the interface to C3: - The Excuse in the Optional of type anyfault is returned as the regular return. - The result in the Optional is passed by reference.

For example:

// C3 code:
fn int! get_value();
// Corresponding C code:
c3fault_t get_value(int *value_ref);

The c3fault_t is guaranteed to be a pointer sized value.


title: Advanced Error Handling description: Advanced Error Handling sidebar:

order: 65

Optionals are only defined in certain code

✅ Variable declarations

int! example = unreliable_function();

✅ Function return signature

fn int! example() { /* ... */ }

Handling an empty Optional

File reading example

Try running this code below with and without a file called file_to_open.txt in the same directory.

import std::io;

<*
 Function modifies 'buffer'
 Returns an Optional with a 'char[]' result 
 OR an empty Optional with an Excuse
*>
fn char[]! read_file(String filename, char[] buffer)
{
    // Return Excuse if opening a file failed, using Rethrow `!`
    File file = file::open(filename, "r")!; 

    // At scope exit, close the file.
    // Discard the Excuse from file.close() with (void) cast
    defer (void)file.close(); 

    // Return Excuse if reading failed, using Rethrow `!`
    file.read(buffer)!; 
    return buffer; // return a buffer result
}

fn void! test_read()
{
    char[] buffer = mem::new_array(char, 100);
    defer free(buffer); // Free memory on scope exit

    char[]! read_buffer = read_file("file_to_open.txt", buffer);
    // Catch the empty Optional and assign the Excuse 
    // to `excuse`
    if (catch excuse = read_buffer) 
    {
        io::printfn("Excuse found: %s", excuse);
        // Returning Excuse using the `?` suffix
        return excuse?;
    }

    // `read_buffer` behaves like a normal variable here 
    // because the Optional being empty was handled by 'if (catch)'
    // which automatically unwrapped 'read_buffer' at this point.
    io::printfn("read_buffer: %s", read_buffer);
}

fn void main()
{
    test_read()!!; // Panic on failure.
}

Return a default value if Optional is empty

The ?? operator allows us to return a default value if the Optional is empty.

import std::io;

fn void test_bad() 
{
    int regular_value;
    int! optional_value = function_may_error();

    // An empty Optional found in optional_value
    if (catch optional_value) 
    {   
        // Assign default result when empty.
        regular_value = -1;
    }

    // A result was found in optional_value
    if (try optional_value) 
    {
        regular_value = optional_value;
    }
    io::printfn("The value was: %d", regular_value);
}

fn void test_good() 
{
    // Return '-1' when `foo_may_error()` is empty.
    int regular_value = foo_may_error() ?? -1;

    io::printfn("The value was: %d", regular_value);
}

Modifying the returned Excuse

A common use of ?? is to catch an empty Optional and change the Excuse to another more specific Excuse, which allows us to distinguish one failure from the other, even when they had the same Excuse originally.

import std::io;

fault NoHomework
{
    DOG_ATE_MY_HOMEWORK,
    MY_TEXTBOOK_CAUGHT_FIRE,
    DISTRACTED_BY_CAT_PICTURES
}

fn int! test() 
{
    return IoError.FILE_NOT_FOUND?;
}

fn void! examples() 
{
    int! a = test(); // IoError.FILE_NOT_FOUND
    int! b = test(); // IoError.FILE_NOT_FOUND

    // We can tell these apart by default assigning our own unique
    // Excuse. Our custom Excuse is assigned only if an
    // empty Optional is returned.
    int! c = test() ?? NoHomework.DOG_ATE_MY_HOMEWORK?;
    int! d = test() ?? NoHomework.DISTRACTED_BY_CAT_PICTURES?;

    // If you want to immediately return with an Excuse, 
    // use the "?" and "!" operators together, see the code below:
    int! e = test() ?? NoHomework.DOG_ATE_MY_HOMEWORK?!;
    int! f = test() ?? NoHomework.DISTRACTED_BY_CAT_PICTURES?!;
}

Force unwrapping expressions

The force unwrap operator !! will make the program panic and exit if the expression is an empty optional. This is useful when the error should – in normal cases – not happen and you don’t want to write any error handling for it. That said, it should be used with great caution in production code.

fn void find_file_and_test()
{
    find_file()!!;

    // Force unwrap '!!' is roughly equal to:
    // if (catch find_file()) unreachable("Unexpected excuse");
}

Find empty Optional without reading the Excuse

import std::io;
fn void test() 
{
    int! optional_value = IoError.FILE_NOT_FOUND?;

    // Find empty Optional, then handle inside scope
    if (catch optional_value) 
    {
        io::printn("Found empty Optional, the Excuse was not read");
    } 
}

Find empty Optional and switch on Excuse

if (catch) can also immediately switch on the Excuse value:

fn void! test() 
{
    if (catch excuse = optional_value)
    {
        case NoHomework.DOG_ATE_MY_HOMEWORK:
            io::printn("Dog ate your file");
        case IoError.FILE_NOT_FOUND:
            io::printn("File not found");
        default:
            io::printfn("Unexpected Excuse: %s", excuse);
            return excuse?;
    }
}

Which is shorthand for:

fn void! test() 
{
    if (catch excuse = optional_value)
    {
        switch (excuse)
        {
            case NoHomework.DOG_ATE_MY_HOMEWORK:
                io::printn("Dog ate your file");
            case IoError.FILE_NOT_FOUND:
                io::printn("File not found");
            default:
                io::printfn("Unexpected Excuse: %s", excuse);
                return excuse?;
        }
    }
}

Run code if the Optional has a result

This is a convenience method, the logical inverse of if (catch) and is helpful when you don’t care about the empty branch of the code or you wish to perform an early return.

fn void test()
{
    // 'optional_value' is a non-Optional variable inside the scope
    if (try optional_value) 
    {
        io::printfn("Result found: %s", optional_value);    
    } 

    // The Optional result is assigned to 'unwrapped_value' inside the scope
    if (try unwrapped_value = optional_value)
    {
        io::printfn("Result found: %s", unwrapped_value);    
    }  
}

Another example:

import std::io;

// Returns Optional result with `int` type or empty with an Excuse
fn int! reliable_function()
{
    return 7; // Return a result
}

fn void main(String[] args)
{
    int! reliable_result = reliable_function();

    // Unwrap the result from reliable_result
    if (try reliable_result)
    {
        // reliable_result is unwrapped in this scope, can be used as normal
        io::printfn("reliable_result: %s", reliable_result);
    }
}

It is possible to add conditions to an if (try) but they must be joined with &&. However you cannot use logical OR (||) conditions:

import std::io;

// Returns Optional with an 'int' result or empty with an Excuse
fn int! reliable_function()
{
    return 7; // Return an Optional result
}

fn void main(String[] args)
{
    int! reliable_result1 = reliable_function();
    int! reliable_result2 = reliable_function();

    // Unwrap the result from reliable_result1 and reliable_result2
    if (try reliable_result1 && try reliable_result2 && 5 > 2)
    {
        // `reliable_result1` is can be used as a normal variable here
        io::printfn("reliable_result1: %s", reliable_result1);

        // `reliable_result2` is can be used as a normal variable here
        io::printfn("reliable_result2: %s", reliable_result2);
    }

    // ERROR cannot use logical OR `||`
    // if (try reliable_result1 || try reliable_result2)
    // {
    //     io::printn("this can never happen);
    // }
}

Shorthands to work with Optionals

Getting the Excuse

Retrieving the Excuse with if (catch excuse = optional_value) {...} is not the only way to get the Excuse from an Optional, we can use the macro @catch instead. Unlike if (catch) this will never cause automatic unwrapping.

fn void main(String[] args)
{
    int! optional_value = IoError.FILE_NOT_FOUND?;

    anyfault excuse = @catch(optional_value);
    if (excuse)
    {
        io::printfn("Excuse found: %s", excuse);
    }
}

Checking if an Optional has a result without unwrapping

The @ok macro will return true if an Optional result is present and false if the Optional is empty. Functionally this is equivalent to !@catch, meaning no Excuse was found, for example:

fn void main(String[] args)
{
    int! optional_value = 7;

    bool result_found = @ok(optional_value);
    assert(result_found == !@catch(optional_value));
}

No void! variables

The void! type has no possible representation as a variable, and may only be a function return type.

To store the Excuse returned from a void! function without if (catch foo = optional_value), use the @catch macro to convert the Optional to an anyfault:

fn void! test() 
{
    return IoError.FILE_NOT_FOUND?;
}

anyfault excuse = @catch(test());

title: Defer and Cleanup description: Defer and Cleanup sidebar:

order: 66

Defer

A defer always runs at the end of a scope at any point after it is declared, defer is commonly used to simplify code that needs clean-up; like closing unix file descriptors, freeing dynamically allocated memory or closing database connections.

End of a scope

The end of a scope also includes return, break, continue or rethrow !.

fn void test() 
{
    io::printn("print first");
    defer io::printn("print third, on function return");
    io::printn("print second");
    return;
}

The defer runs after the other print statements, at the function return.

:::note Rethrow ! unwraps the Optional result if present, afterwards the previously Optional variable is a normal variable again, if the Optional result is empty then the Excuse is returned from the function back to the caller. :::

Defer Execution order

When there are multiple defer statements they are executed in reverse order of their declaration, last-to-first declared.

fn void test() 
{
    io::printn("print first");
    defer io::printn("print third, defers execute in reverse order");
    defer io::printn("print second, defers execute in reverse order");
    return;
}

Example defer

import std::io;

fn char[]! file_read(String filename, char[] buffer)
{   
    // return Excuse if failed to open file
    File file = file::open(filename, "r")!; 

    defer { 
        io::printn("File was found, close the file"); 
        if (catch excuse = file.close()) 
        {
            io::printfn("Fault closing file: %s", excuse); 
        }
    }

    // return if fault reading the file into the buffer
    file.read(buffer)!; 
    return buffer;
}

If the file named filename is found the function will read the content into a buffer, defer will then make sure that any open File handlers are closed. Note that if a scope exit happens before the defer declaration, the defer will not run, this a useful property because if the file failed to open, we don’t need to close it.

Defer try

A defer try is called at end of a scope when the returned Optional contained a result value.

Examples

fn void! test() 
{
    defer try io::printn("✅ defer try run"); 
    // Returned an Optional result
    return;
}

fn void main(String[] args) 
{
    (void)test();
}

Function returns an Optional result value, this means defer try runs on scope exit.

fn void! test() 
{
    defer try io::printn("❌ defer try not run");
    // Returned an Optional Excuse
    return IoError.FILE_NOT_FOUND?;
}

fn void main(String[] args) 
{
    if (catch err = test()) 
    {
        io::printfn("test() returned a fault: %s", err);
    }
}

Function returns an Optional Excuse, this means the defer try does not run on scope exit.

Defer catch

A defer catch is called at end of a scope when exiting exiting with an Optional Excuse, and is helpful for logging, cleanup and freeing resources.

defer catch { ... }
defer (catch err) { ... };

When the fault is captured this is convenient for logging the fault:

defer (catch err) io::printfn("fault found: %s", err)

Memory allocation example

import std::core::mem;

fn char[]! test()
{
    char[] data = mem::new_array(char, 12)!;

    defer (catch err) 
    {
        io::printfn("Excuse found: %s", err)
        (void)free(data);
    }

    // Returns Excuse, memory gets freed
    return IoError.FILE_NOT_FOUND?; 
}

Pitfalls with defer and defer catch

If cleaning up memory allocations or resources make sure the defer or defer catch are declared as close to the resource declaration as possible. This helps to avoid unwanted memory leaks or unwanted resource usage from other code rethrowing ! before the defer catch was even declared.

fn void! function_throws() 
{
    return IoError.FILE_NOT_FOUND?;
}

fn String! test()
{
    char[] data = mem::new_array(char, 12)!;

    // ❌ Before the defer catch declaration
    // memory was NOT freed
    // function_throws()!;  

    defer (catch err) 
    {
        io::printn("freeing memory");
        (void)free(data);
    }

    // ✅ After the defer catch declaration
    // memory freed correctly
    function_throws()!;     

    return (String)data; 
}

title: Contracts description: Contracts sidebar:

order: 67

Contracts are optional pre- and post-conditions checks that the compiler may use for optimization and runtime checks. Note that compilers are not obliged to process pre- and post-conditions at all. However, violating either pre- or post-conditions is considered undefined behaviour, so a compiler may optimize as if they always hold – even if a potential bug may cause them to be violated.

Pre-conditions

Pre-conditions are usually used to validate incoming arguments. Each condition must be an expression that can be evaluated to a boolean. Pre-conditions use the @require annotation, and optionally can have an error message to display after them.

<*
 @require foo > 0, foo < 1000, "optional error msg"
*>
fn int testFoo(int foo)
{
    return foo * 10;
}

Post conditions

Post conditions are evaluated to make checks on the resulting state after passing through the function. The post condition uses the @ensure annotation. Where return is used to represent the return value from the function.

<*
 @require foo != null
 @ensure return > foo.x
*>
fn uint checkFoo(Foo* foo)
{
    uint y = abs(foo.x) + 1;
    // If we had row: foo.x = 0, then this would be a compile time error.
    return y * abs(foo.x);
}

Parameter annotations

@param supports [in] [out] and [inout]. These are only applicable for pointer arguments. [in] disallows writing to the variable, [out] disallows reading from the variable. Without an annotation, pointers may both be read from and written to without checks. If an & is placed in front of the annotation (e.g. [&in]), then this means the pointer must be non-null and is checked for null.

Type readable? writable? use as “in”? use as “out”? use as “inout”
no annotation Yes Yes Yes Yes Yes
in Yes No Yes No No
out No Yes No Yes No
inout Yes Yes Yes Yes Yes

However, it should be noted that the compiler might not detect whether the annotation is correct or not! This program might compile, but will behave strangely:

fn void bad_func(int* i)
{
    *i = 2;
}

<*
 @param [&in] i
*>
fn void lying_func(int* i)
{
    bad_func(i); // The compiler might not check this!
}

fn void test()
{
    int a = 1;
    lying_func(&a);
    io::printf("%d", a); // Might print 1!
}

However, compilers will usually detect this:

<*
 @param [&in] i
*>
fn void bad_func(int* i)
{
    *i = 2; // <- Compiler error: cannot write to "in" parameter
}

Pure in detail

The pure annotation allows a program to make assumptions in regard to how the function treats global variables. Unlike for const, a pure function is not allowed to call a function which is known to be impure.

However, just like for const the compiler might not detect whether the annotation is correct or not! This program might compile, but will behave strangely:

int i = 0;

def SecretFn = fn void();

fn void bad_func()
{
    i = 2;
}

<*
 @pure
*>
fn void lying_func(SecretFn f)
{
    f(); // The compiler cannot reason about this!
}

fn void main()
{
    i = 1;
    lying_func(&bad_func);
    io::printf("%d", i); // Might print 1!
}

However, compilers will usually detect this:

int i = 0;

def SecretFn = fn void();

fn void bad_func()
{
    i = 2;
}

<*
 @pure
*>
fn void lying_func(SecretFn f)
{
    f(); // <- ERROR: Only '@pure' functions may be called. 
}

fn void main()
{
    i = 1;
    lying_func(&bad_func);
    io::printf("%d", i); // Might print 1!
}

Consequently, circumventing “pure” annotations is undefined behaviour.

Pre conditions for macros

In order to check macros, it’s often useful to use the builtin $defined function which returns true if the code inside would pass semantic checking.

<*
 @require $defined(resource.open, resource.open()), `Expected resource to have an "open" function`
 @require resource != nil
 @require $assignable(resource.open(), void*)
*>
macro open_resource(resource)
{
    return resource.open();
}

title: Attributes description: Attributes sidebar:

order: 68

Attributes are compile-time annotations on functions, types, global constants and variables. Similar to Java annotations, a decoration may also take arguments. A attribute can also represent a bundle of attributes.

Built in attributes

@adhoc

Used for: type parameterized generic modules

Normally a parameterized generic module needs to be defined before it is used like:

module my_lib(<Type>);

struct MyType
{
      Type value;
}

module my_code;

// Definition here
def MyType(<int>) = MyTypeInt; 

fn void main()
{
    MyType(<int>) x;
}

A type with @adhoc can be declared parameterized, without any warning being issued, for example:

module my_lib(<Type>);

struct MyType @adhoc
{
      Type value;
}

module my_code;

fn void main()
{
    MyType(<int>) x;
}

@align(alignment)

Used for: struct, bitstructs, union, var, function

This attribute sets the minimum alignment for a field or a variable, for example:

struct Foo @align(32)
{
    int a;
    int b @align(16);
}

@benchmark

Used for: function

Marks the function as a benchmark function. Will be added to the list of benchmark functions when the benchmarks are run, otherwise the function will not be included in the compilation.

@bigendian

Used for: bitstruct

Lays out the bits as if the data was stored in a big endian type, regardless of host system endianness.

@builtin

Used for: function, macro, global, const

Allows a macro, function, global or constant be used from another module without the module path prefixed. Should be used sparingly.

@callc

Used for: function

Sets the call convention, which may be ignored if the convention is not supported on the target. Valid arguments are veccall, ccall, stdcall.

@deprecated

Used for: types, function, macro, global, const, member

Marks the particular type, global, const or member as deprecated, making use trigger a warning.

@export

Used for: function, global, const, enum, union, struct, fault

Marks this declaration as an export, this ensures it is never removed and exposes it as public when linking. The attribute takes an optional string value, which is the external name. This acts as if @extern had been added with that name.

@extern

Used for: function, global, const, enum, union, struct, fault

Sets the external (linkage) name of this declaration.

@finalizer

Used for: function

Make this function run at shutdown. See @init for the optional priority. Note that running a finalizer is a “best effort” attempt by the OS. During abnormal termination it is not guaranteed to run.

The function must be a void function taking no arguments.

@if

Used for: all declarations

Conditionally includes the declaration in the compilation. It takes a constant compile time value argument, if this value is true then the declaration is retained, on false it is removed.

@init

Used for: function

Make this function run at startup before main. It has an optional priority 1 - 65535, with lower being executed earlier. It is not recommended to use values less than 128 as they are generally reserved and using them may interfere with standard program initialization.

The function must be a void function taking no arguments.

@inline

Used for: function, call

Declares a function to always be inlined or if placed on a call, that the call should be inlined.

@littleendian

Used for: bitstruct

Lays out the bits as if the data was stored in a little endian type, regardless of host system endianness.

@local

Used for: any declaration

Sets the visibility to “local”, which means it’s only visible in the current module section.

@maydiscard

Used for: function, macro

Allows the return value of the function or macro to be discarded even if it is an optional. Should be used sparingly.

@naked

Used for: function

This attribute disables prologue / epilogue emission for the function.

@nodiscard

Used for: function, macro

The return value may not be discarded.

@noinit

Used for: global, local variable

Prevents the compiler from zero initializing the variable.

@norecurse

Used for: import @norecurse

Import the module but not sub-modules or parent-modules, see Modules Section.

@noreturn

Used for: function

Declares that the function will never return.

@nostrip

Used for: any declaration

This causes the declaration never to be stripped from the executable, even if it’s not used. This also transitively applies to any dependencies the declaration might have.

@obfuscate

Used for: any declaration

Removes any string values that would identify the declaration in some way. Mostly this is used on faults and enums to remove the stored names.

@operator

Used for: method, macro method

This attribute has arguments [] []= &[] and len allowing operator overloading for [] and foreach. By implementing [] and len, foreach and foreach_r is enabled. In order to do foreach by reference, &[] must be implemented as well.

@overlap

Used for: bitstruct

Allows bitstruct fields to have overlapping bit ranges.

@packed

Used for: struct, union

Causes all members to be packed as if they had alignment 1. The alignment of the struct/union is set to 1. This alignment can be overridden with @align.

@private

Used for: any declaration

Sets the visibility to “private”, which means it is visible in the same module, but not from other modules.

@pure

Used for: call

Used to annotate a non pure function as “pure” when checking for conformance to @pure on functions.

@packed

Used for: struct, union, enum

If used on a struct or enum: packs the type, including any components to minimum size. On an enum, it uses the smallest representation containing all its values.

@reflect

Used for: any declaration

Adds additional reflection information. Has no effect currently.

@section(name)

Used for: function, const, global

Declares that a global variable or function should appear in a specific section.

@test

Used for: function

Marks the function as a test function. Will be added to the list of test functions when the tests are run, otherwise the function will not be included in the compilation.

@unused

Used for: any declaration

Marks the declaration as possibly unused (but should not emit a warning).

@used

Used for: any declaration

Marks a parameter, value etc. as must being used.

@weak

Used for: function, const, global

Emits a weak symbol rather than a global.

User defined attributes

User defined attributes are intended for conditional application of built-in attributes.

def @MyAttribute = { @noreturn @inline };

// The following two are equivalent:
fn void foo() @MyAttribute { ... }
fn void foo() @noreturn @inline { ... }

A user defined attribute may also be completely empty:

def @MyAttributeEmpty = {};

title: C Interoperability description: C Interoperability sidebar:

order: 69

C3 is C ABI compatible. That means you can call C from C3, and call C3 from C without having to do anything special. As a quick way to call C, you can simply declare the function as a C3 function but with extern in front of it. As long as the function is linked, it will work:

extern fn void puts(char*); // C "puts"

fn void main()
{
    // This will call the "puts"
    // function in the standard c lib.
    puts("Hello, world!"); 
}

To use a different identifier inside of your C3 code compared to the function or variable’s external name, use the @extern attribute:

extern fn void foo_puts(char*) @extern("puts"); // C "puts"

fn void main()
{
    foo_puts("Hello, world!"); // Still calls C "puts"
}

While C3 functions are available from C using their external name, it’s often useful to define an external name using @extern or @export with a name to match C usage.

module foo;
fn int square(int x) @export // @export ensures external visibility
{
    return x * x;
}

fn int square2(int x) @export("square")
{
    return x * x;
}

Calling from C:

extern int square(int);
int foo_square(int) __attribute__ ((weak, alias ("foo.square")));

void test()
{
    // This would call square2
    printf("%d\n", square(11));

    // This would call square
    printf("%d\n", foo_square(11));
}

Linking static and dynamic libraries

If you have a library foo.a or foo.so or foo.obj (depending on type and OS), just add -l foo on the command line, or in the project file add it to the linked-libraries value, e.g. "linked-libraries" = ["foo"].

To add library search paths, use -L <directory> from the command line and linker-search-paths the project file (e.g. "linker-search-paths" = ["../mylibs/", "/extra-libs/"])

Gotchas


title: Interfaces and Any Type description: Interfaces and Any Type sidebar:

order: 80

Working with the type of any at runtime.

The any type is recommended for writing code that is polymorphic at runtime where macros are not appropriate. It can be thought of as a typed void*.

An any can be created by assigning any pointer to it. You can then query the any type for the typeid of the enclosed type (the type the pointer points to) using the type field.

This allows switching over the typeid, either using a normal switch:

switch (my_any.type)
{
    case Foo.typeid:
        ...
    case Bar.typeid:
        ...
}

Or the special any-version of the switch:

switch (my_any)
{
    case Foo:
        // my_any can be used as if it was Foo* here
    case Bar:
        // my_any can be used as if it was Bar* here
}

Sometimes one needs to manually construct an any-pointer, which is typically done using the any_make function: any_make(ptr, type) will create an any pointing to ptr and with typeid type.

Since the runtime typeid is available, we can query for any runtime typeid property available at runtime, for example the size, e.g. my_any.type.sizeof. This allows us to do a lot of work on with the enclosed data without knowing the details of its type.

For example, this would make a copy of the data and place it in the variable any_copy:

void* data = malloc(a.type.sizeof);
mem::copy(data, a.ptr, a.type.sizeof);
any any_copy = any_make(data, a.type);

Variable argument functions with implicit any

Regular typed varargs are of a single type, e.g. fn void abc(int x, double... args). In order to take variable functions that are of multiple types, any may be used. There are two variants:

Explicit any vararg functions

This type of function has a format like fn void vaargfn(int x, any... args). Because only pointers may be passed to an any, the arguments must explicitly be pointers (e.g. vaargfn(2, &b, &&3.0)).

While explicit, this may be somewhat less user-friendly than implicit vararg functions:

Implicit any vararg functions

The implicit any vararg function has instead a format like fn void vaanyfn(int x, args...). Calling this function will implicitly cause taking the pointer of the values (so for example in the call vaanyfn(2, b, 3.0), what is actually passed are &b and &&3.0).

Because this passes values implicitly by reference, care must be taken not to mutate any values passed in this manner. Doing so would very likely break user expectations.

Interfaces

Most statically typed object-oriented languages implements extensibility using vtables. In C, and by extension C3, this is possible to emulate by passing around structs containing list of function pointers in addition to the data.

While this is efficient and often the best solution, but it puts certain assumptions on the code and makes interfaces more challenging to evolve over time.

As an alternative there are languages (such as Objective-C) which instead use message passing to dynamically typed objects, where the availability of a certain functionality may be queried at runtime.

C3 provides this latter functionality over the any type using interfaces.

Defining an interface

The first step is to define an interface:

interface MyName
{
    fn String myname();
}

While myname will behave as a method, we declare it without type. Note here that unlike normal methods we leave out the first “self”, argument.

Implementing the interface

To declare that a type implements an interface, add it after the type name:

struct Baz (MyName) 
{ 
    int x; 
}

// Note how the first argument differs from the interface.
fn String Baz.myname(Baz* self) @dynamic 
{ 
    return "I am Baz!"; 
}

If a type declares an interface but does not implement its methods, then that is compile time error. A type may implement multiple interfaces, by placing them all inside of () e.g. struct Foo (VeryOptional, MyName) { ... }

A limitation is that only user-defined types may declare they are implementing interfaces. To make existing types implement interfaces is possible but does not provide compile time checks.

One of the interfaces available in the standard library is Printable, which contains to_format and to_new_string. If we implemented it for our struct above it might look like this:

fn String Baz.to_new_string(Baz baz, Allocator allocator) @dynamic
{
    return string::printf("Baz(%d)", baz.x, allocator: allocator);
}

“@dynamic” methods

A method must be declared @dynamic to implement an interface, but a method may also be declared @dynamic without the type declaring it implements a particular interface. For example, this allows us to write:

// This will make "int" satisfy the MyName interface
fn String int.myname(int*) @dynamic
{ 
    return "I am int!"; 
}

@dynamic methods have their reference retained in the runtime code and can also be searched for at runtime and invoked from the any type.

Referring to an interface by pointer

An interface e.g. MyName, can be cast back and forth to any, but only types which implement the interface completely may implicitly be cast to the interface.

So for example:

Bob b = { 1 };
double d = 0.5;
int i = 3;
MyName a = &b;          // Valid, Bob implements MyName.
// MyName c = &d;       // Error, double does not implement MyName.
MyName c = (MyName)&d;  // Would break at runtime as double doesn't implement MyName
// MyName z = &i;       // Error, implicit conversion because int doesn't explicitly implement it.
MyName* z = (MyName)&i; // Explicit conversion works and is safe at runtime if int implements "myname"

Calling dynamic methods

Methods implementing interfaces are like normal methods, and if called directly, they are just normal function calls. The difference is that they may be invoked through the interface:

fn void whoareyou(MyName a)
{
    io::printn(a.myname());
}

If we have an optional method we should first check that it is implemented:

fn void do_something(VeryOptional z)
{
    if (&z.do_something)
    {
        z.do_something(1, null);
    }
}

We first query if the method exists on the value. If it does we actually run it.

Here is another example, showing how the correct function will be called depending on type, checking for methods on an any:

fn void whoareyou2(any a)
{
    // Query if the function exists
    if (!&a.myname)
    {
        io::printn("I don't know who I am.");
        return;
    }
    // Dynamically call the function
    io::printn(((MyName)a).myname());
}

fn void main()
{
    int i;
    double d;
    Bob bob;

    any a = &i; 
    whoareyou2(a); // Prints "I am int!"
    a = &d;
    whoareyou2(a); // Prints "I don't know who I am."
    a = &bob;
    whoareyou2(a); // Prints "I am Bob!"
}

Reflection invocation

This functionality is not yet implemented and may see syntax changes

It is possible to retrieve any @dynamic function by name and invoke it:

def VoidMethodFn = fn void(void*);

fn void* int.test_something(&self) @dynamic
{
    io::printfn("Testing: %d", *self);
}

fn void main()
{
    int z = 321;
    any a = &z;
    VoidMethodFn test_func = a.reflect("test_something");
    test_func(a); // Will print "Testing: 321"
}

This feature allows methods to be linked up at runtime.


title: Operator Overloading description: Operator Overloading sidebar:

order: 81

C3 allows some limited operator overloading for working with containers.

“Element at” operator []

Implementing [] allows a type to use the my_type[<value>] syntax:

struct Foo
{
    double[] x;
}

fn double Foo.get(&self, usz i) @operator([])
{
    return self.x[i];
}

It’s possible to use any type as argument, such as a string:

fn double Bar.get(&self, String str) @operator([])
{
    return self.get_val_by_key(str);
}

Only a single [] overload is allowed.

“Element ref” operator &[]

Similar to [], the operator returns a value for &my_type[<value>], which may be retrieved in a different way. If this overload isn’t defined, then &my_type[<value>] would be a syntax error.

fn double* Foo.get_ref(&self, usz i) @operator(&[])
{
    return &self.x[i];
}

“Element set” operator []=

The counterpart of [] allows setting an element using my_type[<index>] = <value>.

fn void Foo.set(&self, usz i, double new_val) @operator([]=)
{
    return self.x[i] = new_val;
}

“len” operator

Unlike the previous operator overloads, the “len” operator simply enables functionality which augments the []-family of operators: you can use the “from end” syntax e.g my_type[^1] to get the last element assuming the indexing uses integers.

Enabling ‘foreach’

In order to use a type with foreach, e.g. foreach(d : foo), at a minimum methods with overloads for [] (@operator([])) and len (@operator(len)) need to be added. If &[] is implemented, foreach by reference is enabled (e.g. foreach(double* &d : foo))

fn double Foo.get(&self, usz i) @operator([])
{
    return self.x[i];
}

fn usz Foo.len(&self) @operator(len)
{
    return self.x.len;
}

fn void test(Foo f)
{
    // Print all elements in f
    foreach (d : f)
    {
        io::printfn("%f", d);
    }
}

:::note

Operator overloading is limited, by design, as these features delivered the most value while still keeping the language as simple as possible.

:::


title: Generics description: Generics sidebar:

order: 82

Generic modules are parameterized modules that allow functionality for arbitrary types.

For generic modules, the generic parameters follows the module name:

// TypeA, TypeB, TypeC are generic parameters.
module vector(<TypeA, TypeB, TypeC>);

It is also possible to parameterize by an int or bool constant, for example:

// module custom_type<Type, VALUE>  
module custom_type<float, 3>;

Code inside a generic module may use the generic parameters as if they were well-defined symbols:

module foo_test(<Type1, Type2>);

struct Foo 
{
   Type1 a;
}

fn Type2 test(Type2 b, Foo *foo) 
{
   return foo.a + b;
}

Including a generic module works as usual:

import foo_test;

def FooFloat = Foo(<float, double>);
def test_float = foo_test::test(<float, double>);

...

FooFloat f;
Foo(<int, double>) g;

...

test_float(1.0, &f);
foo_test::test(<int, double>)(1.0, &g);

Just like for macros, optional constraints may be added to improve compile errors:

<*
 @require $assignable(1, TypeB) && $assignable(1, TypeC)
 @require $assignable((TypeB)1, TypeA) && $assignable((TypeC)1, TypeA)
*> 
module vector(<TypeA, TypeB, TypeC>);

/* .. code * ../
def testFunction = vector::testFunc(<Bar, float, int>);

// This would give the error 
// --> Parameter(s) failed validation: 
//     @require "$assignable((TypeB)1, TypeA) && $assignable((TypeC)1, TypeA)" violated.

title: Macros description: Macros sidebar:

order: 83

The macro capabilities of C3 reaches across several constructs: macros, generic functions, generic modules, and compile time variables (prefixed with $), macro compile time execution (using $if, $for, $foreach, $switch) and attributes.

A quick comparison of C and C3 macros

Conditional compilation

// C Macro
#if defined(x) && Y > 3
int z;
#endif
// C3 Macro
$if $defined(x) && Y > 3:
    int z;
$endif

// or
int z @if($defined(x) && Y > 3);

Macros

// C Macro
#define M(x) ((x) + 2)
#define UInt32 unsigned int

// Use:
int y = M(foo() + 2);
UInt32 b = y;
// C3 Macro
macro m(x)
{
    return x + 2;
}
def UInt32 = uint;

// Use:
int y = m(foo() + 2);
UInt32 b = y;

Dynamic scoping

// C Macro
#define Z() ptr->x->y->z
int x = Z();
// C3 Macro
... currently no corresponding functionality ...

Expression arguments

// C Macro
#define M(x, y) x = 2 * (y);
...
M(x, 3);
// C3 Macro
macro @m(#x, y)
{
    #x = 2 * y;
}
...
@m(x, 3);

First class types

// C Macro
#define SIZE(T) (sizeof(T) + sizeof(int))
// C3 Macro
macro size($Type)
{
    return $Type.sizeof + int.sizeof;
}

Trailing blocks for macros

// C Macro
#define FOR_EACH(x, list) \
for (x = (list); x; x = x->next)

// Use:
Foo *it;
FOR_EACH(it, list) 
{
    if (!process(it)) return;
}
// C3 Macro
macro @for_each(list; @body(it))
{
    for ($typeof(list) x = list; x; x = x.next)
    {
        @body(x);
    }    
}

// Use:
@for_each(list; Foo* x)
{
    if (!process(x)) return;
}

First class names

// C Macro
#define offsetof(T, field) (size_t)(&((T*)0)->field)
// C3 Macro
macro usz @offset($Type, #field)
{
    $Type* t = null;
    return (usz)(uptr)&t.#field;
}

Declaration attributes

// C Macro
#define PURE_INLINE __attribute__((pure)) __attribute__((always_inline))
int foo(int x) PURE_INLINE { ... }
// C3 Macro
def @NoDiscardInline = { @nodiscard @inline };
fn int foo(int) @NoDiscardInline { ... }

Declaration macros

// C Macro
#define DECLARE_LIST(name) List name = { .head = NULL };
// Use:
DECLARE_LIST(hello)
// C3 Macro
... currently no corresponding functionality ...

Stringification

// C Macro
#define CHECK(x) do { if (!x) abort(#x); } while(0)
// C3 Macro
macro @check(#expr)
{
    if (!#expr) abort($stringify(#expr));
}

Top level evaluation

Script languages, and also upcoming languages like Jai, usually have unbounded top level evaluation. The flexibility of this style of meta programming has a trade-off in making the code more challenging to understand.

In C3, top level compile time evaluation is limited to @if attributes to conditionally enable or disable declarations. This makes the code easier to read, but at the cost of expressive power.

Macro declarations

A macro is defined using macro <name>(<parameters>). All user defined macros use the @ symbol if they use the & or # parameters.

The parameters have different sigils: $ means compile time evaluated (constant expression or type). # indicates an expression that is not yet evaluated, but is bound to where it was defined. @ is required on macros that use # parameters or trailing macro bodies.

A basic swap:

<*
 @checked $defined(#a = #b, #b = #a)
*>
macro void @swap(#a, #b)
{
    var temp = #a;
    #a = #b;
    #b = temp;
}

This expands on usage like this:

fn void test()
{
    int a = 10;
    int b = 20;
    @swap(a, b);
}
// Equivalent to:
fn void test()
{
    int a = 10;
    int b = 20;
    {
        int __temp = a;
        a = b;
        b = __temp;
    }
}

Note the necessary #. Here is an incorrect swap and what it would expand to:

macro void badswap(a, b)
{
    var temp = a;
    a = b;
    b = temp;
}

fn void test()
{
    int a = 10;
    int b = 20;
    badswap(a, b);
}
// Equivalent to:
fn void test()
{
    int a = 10;
    int b = 20;
    {
        int __a = a;
        int __b = b;
        int __temp = __a;
        __a = __b;
        __b = __temp;
    }
}

Macro methods

Similar to regular methods a macro may also be associated with a particular type:

struct Foo { ... }

macro Foo.generate(&self) { ... }
Foo f;
f.generate();

See the chapter on functions for more details.

Capturing a trailing block

It is often useful for a macro to take a trailing compound statement as an argument. In C++ this pattern is usually expressed with a lambda, but in C3 this is completely inlined.

To accept a trailing block, ; @name(param1, ...) is placed after declaring the regular macro parameters.

Here’s an example to illustrate its use:

<*
 A macro looping through a list of values, executing the body once
 every pass.

 @require $defined(a.len) && $defined(a[0])
*>
macro @foreach(a; @body(index, value))
{
    for (int i = 0; i < a.len; i++)
    {
        @body(i, a[i]);
    }
}

fn void test()
{
    double[] a = { 1.0, 2.0, 3.0 };
    @foreach(a; int index, double value)
    {
        io::printfn("a[%d] = %f", index, value);
    };
}

// Expands to code similar to:
fn void test()
{
    int[] a = { 1, 2, 3 };
    {
        int[] __a = a;
        for (int __i = 0; i < __a.len; i++)
        {
            io::printfn("Value: %d, x2: %d", __value1, __value2);
        }
    }
}

Macros returning values

A macro may return a value, it is then considered an expression rather than a statement:

macro square(x)
{
    return x * x;
}

fn int getTheSquare(int x)
{
    return square(x);
}

fn double getTheSquare2(double x)
{
    return square(x);
}

Calling macros

It’s perfectly fine for a macro to invoke another macro or itself.

macro square(x) { return x * x; }

macro squarePlusOne(x)
{
    return square(x) + 1; // Expands to "return x * x + 1;"
}

The maximum recursion depth is limited to the macro-recursion-depth build setting.

Macro vaargs

Macros support the typed vaargs used by C3 functions: macro void foo(int... args) and macro void bar(args...) but it also supports a unique set of macro vaargs that look like C style vaargs: macro void baz(...)

To access the arguments there is a family of $va-* built-in functions to retrieve the arguments:

macro compile_time_sum(...)
{
    var $x = 0;
    $for (var $i = 0; $i < $vacount; $i++)
        $x += $vaconst[$i];
    $endfor
    return $x;
}
$if compile_time_sum(1, 3) > 2: // Will compile to $if 4 > 2
    ...
$endif

$vacount

Returns the number of arguments.

$vaarg

Returns the argument as a regular parameter. The argument is guaranteed to be evaluated once, even if the argument is used multiple times.

$vaconst

Returns the argument as a compile time constant, this is suitable for placing in a compile time variable or use for compile time evaluation, e.g. $foo = $vaconst(1). This corresponds to $ parameters.

$vaexpr

Returns the argument as an unevaluated expression. Multiple uses will evaluate the expression multiple times, this corresponds to # parameters.

$vatype

Returns the argument as a type. This corresponds to $Type style parameters, e.g. $vatype(2) a = 2

$vasplat

$vasplat allows you to paste the varargs in the call into another call. For example, if the macro was called with values "foo" and 1, the code foo($vasplat()), would become foo("foo", 1). You can even extract provide a range as the argument: $vasplat(2..4) (in this case, this would past in arguments 2, 3 and 4).

Nor is it limited to function arguments, you can also use it with initializers:

int[*] a = { 5, $vasplat[2..], 77 };

Untyped lists

Compile time variables may hold untyped lists. Such lists may be iterated over or implicitly converted to initializer lists:

var $a = { 1, 2 };
$foreach ($x : $a)
    io::printfn("%d", $x);
$endforeach
int[2] x = $a;
io::printfn("%s", x);
io::printfn("%s", $a[1]);
// Will print
// 1
// 2
// [1, 2]
// 2

title: Compile Time Evaluation description: Compile time introspection and execution sidebar:

order: 84

During compilation, constant expressions will automatically be folded. Together with the compile time conditional statements $if, $switch and the compile time iteration statements $for $foreach it is possible to perform limited compile time execution.

Compile time values

During compilation, global constants are considered compile time values, as are any derived constant values, such as type names and sizes, variable alignments etc.

Inside of a macro or a function, it is possible to define mutable compile time variables. Such local variables are prefixed with $ (e.g. $foo). It is also possible to define local type variables, that are also prefixed using $ (e.g. $MyType $ParamType).

Mutable compile time variables are not allowed in the global scope.

$if and $switch

$if <const expr>: takes a compile time constant value and evaluates it to true or false.

macro @foo($x, #y)
{
    $if $x > 3:
        #y += $x * $x;
    $else
        #y += $x;
    $endif
}

const int FOO = 10;

fn void test()
{
    int a = 5;
    int b = 4;
    @foo(1, a); // Allowed, expands to a += 1;
    // @foo(b, a); // Error: b is not a compile time constant.
    @foo(FOO, a); // Allowed, expands to a += FOO * FOO;
}

For switching between multiple possibilities, use $switch.

macro @foo($x, #y)
{
    $switch ($x)
        $case 1: 
            #y += $x * $x;
        $case 2:
            #y += $x;
        $case 3:
            #y *= $x;
        $default:
            #y -= $x;
    $endswitch
}

Switching without argument is also allowed, which works like an if-else chain:

macro @foo($x, #y)
{
    $switch 
        $case $x > 10: 
            #y += $x * $x;
        $case $x < 0:
            #y += $x;
        $default:
            #y -= $x;
    $endswitch
}

Loops using $foreach and $for

$for$endfor works analogous to for, only it is limited to using compile time variables. $foreach$endforeach similarly matches the behaviour of foreach.

Compile time looping:

macro foo($a)
{
    $for (var $x = 0; $x < $a; $x++)
        io::printfn("%d", $x);     
    $endfor
}

fn void test()
{
    foo(2);
    // Expands to ->
    // io::printfn("%d", 0);     
    // io::printfn("%d", 1);         
}

Looping over enums:

macro foo_enum($SomeEnum)
{
    $foreach ($x : $SomeEnum.values)
        io::printfn("%d", (int)$x);     
    $endforeach
}

enum MyEnum
{
    A,
    B,
}

fn void test()
{
    foo_enum(MyEnum);
    // Expands to ->
    // io::printfn("%d", (int)MyEnum.A);
    // io::printfn("%d", (int)MyEnum.B);    
}

An important thing to note is that the content of the $foreach or $for body must be at least a complete statement. It’s not possible to compile partial statements.

Compile time macro execution

If a macro only takes compile time parameters, that is only $-prefixed parameters, and then does not generate any other statements than returns, then the macro will be completely compile time executed.

macro @test($abc)
{
    return $abc * 2;
}

const int MY_CONST = @test(2); // Will fold to "4"

This constant evaluation allows us to write some limited compile time code. For example, this macro will compute Fibonacci at compile time:

macro long @fib(long $n)
{
    $if $n <= 1:
        return $n;
    $else
        return @fib($n - 1) + @fib($n - 2);
    $endif
}

It is important to remember that if we had replaced $n with n the compiler would have complained. n <= 1 is not be considered to be a constant expression, even if the actual argument to the macro was a constant. This limitation is deliberate, to offer control over what is compiled out and what isn’t.

Conditional compilation at the top level using @if

At the top level, conditional compilation is controlled using with @if attributes on declarations

fn void foo_win32() @if(env::WIN32)
{
    /* .... */
}

struct Foo
{
    int a;
    int b @if(env::NO_LIBC);
}

The argument to @if must be possible to resolve to a constant at compile time. This means that argument may also be a compile time evaluated macro:

macro bool @foo($x) => $x > 2;

int x @if(@foo(5)); // Will be included
int y @if(@foo(0)); // Will not be included

Evaluation order of top level conditional compilation

Conditional compilation at the top level can cause unexpected ordering issues, especially when combined with $defined. At a high level, there are three phases of evaluation:

  1. Non-conditional declarations are registered.
  2. Conditional module sections are either discarded or have all of their non-conditional declarations registered.
  3. Each module in turn will evaluate @if attributes for each module section.

The order of module and module section evaluation in (2) and (3) is not deterministic and any use of $defined should not rely on this ordering.

Compile time introspection

At compile time, full type information is available. This allows for creation of reusable, code generating, macros for things like serialization.

usz foo_alignment = Foo.alignof;
usz foo_member_count = Foo.membersof.len;
String foo_name = Foo.nameof;

To read more about all the fields available at compile time, see the page on reflection.

Compile time functions

A set of compile time functions are available at compile time:

$alignof

Get the alignment of something. See reflection.

$append

Append a compile time constant to a compile time array or untyped list.

$assert

Check a condition at compile time.

$assignable

Check if an expression is assignable to the given type, e.g. Type x = expr; would be valid.

$defined

Returns true if a type or identifier is defined. See reflection.

$echo

Print a message to stdout when compiling the code.

$embed

Embed binary data from a file. See expressions.

$error

When this is compiled, issue a compile time error.

$eval

Converts a compile time string to the corresponding variable or function. See reflection.

$evaltype

Converts a compile time string to the corresponding type. See reflection.

$exec

Execute a script at compile time and include the result in the source code.

$extnameof, $qnameof and $nameof

Get the external name of a symbol. See reflection.

$feature

Check if a given feature is enabled.

$is_const

Check if the expression is constant at compile time.

$nameof

Get the local name of a symbol. See reflection.

$offsetof

Get the offset of a member. See reflection.

$qnameof

Get the qualified name of a symbol. See reflection.

$vacount

Return the number of macro vaarg arguments

$vaconst

Return a vaarg as a $constant parameter.

$vaexpr

Return a vaarg as an #expr parameter.

$vasplat

Expand the vaargs in an initializer list or function call.

$vatype

Get a vaarg as a $Type parameter.

$sizeof

Return the size of an expression.

$stringify

Turn an expression into a string.

$typeof

Get the type of an expression (without evaluating it).

$typefrom

Get a type from a compile time constant typeid.


title: Reflection description: Reflection sidebar:

order: 85

C3 allows both compile time and runtime reflection.

During compile time the type information may be directly used as compile time constants, the same data is then available dynamically at runtime.

Note that not all reflection is implemented in the compiler at this point in time.

Compile time reflection

During compile time there are a number of compile time fields that may be accessed directly.

Type properties

It is possible to access properties on the type itself:

alignof

Returns the alignment in bytes needed for the type.

struct Foo @align(8)
{
    int a;
}

uint a = Foo.alignof; // 8

associated

Only available for enums. Returns an array containing the types of associated values if any.

enum Foo : int (double d, String s)
{
    BAR = { 1.0, "normal" },
    BAZ = { 2.0, "exceptional" }
}
String s = Foo.associated[0].nameof; // "double"

elements

Returns the element count of an enum or fault.

enum FooEnum
{
    BAR,
    BAZ
}
int x = FooEnum.elements; // 2

inf

Only available for floating point types

Returns a representation of floating point “infinity”.

inner

This returns a typeid to an “inner” type. What this means is different for each type:

It is not defined for other types.

kindof

Returns the underlying TypeKind as defined in std::core::types.

TypeKind kind = int.kindof; // TypeKind.SIGNED_INT

len

Returns the length of the array.

usz len = int[4].len; // 4

max

Returns the maximum value of the type (only valid for integer and float types).

ushort max_ushort = ushort.max; // 65535

membersof

Only available for bitstruct, struct and union types.

Returns a compile time list containing the fields in a bitstruct, struct or union. The elements have the compile time only type of member_ref.

Note: As the list is an “untyped” list, you are limited to iterating and accessing it at compile time.

struct Baz
{
    int x;
    Foo* z;
}
String x = Baz.membersof[1].nameof; // "z"

A member_ref has properties alignof, kindof, membersof, nameof, offsetof, sizeof and typeid.

min

Returns the minimum value of the type (only valid for integer and float types).

ichar min_ichar = ichar.min; // -128

nameof

Returns the name of the type.

names

Returns a slice containing the names of an enum or fault.

enum FooEnum
{
    BAR,
    BAZ
}
String[] x = FooEnum.names; // ["BAR", "BAZ"]

paramsof

Only available for function pointer types. Returns a ReflectParam struct for all function pointer parameters.

def TestFunc = fn int(int x, double f);
String s = TestFunc.paramsof[1].name; // "f"
typeid t = TestFunc.paramsof[1].type; // double.typeid

parentof

Only available for bitstruct and struct types. Returns the typeid of the parent type.

struct Foo
{
    int a;
}

struct Bar
{
    inline Foo f;
}

String x = Bar.parentof.nameof; // "Foo"

returns

Only available for function types. Returns the typeid of the return type.

def TestFunc = fn int(int, double);
String s = TestFunc.returns.nameof; // "int"

sizeof

Returns the size in bytes for the given type, like C sizeof.

usz x = Foo.sizeof;

typeid

Returns the typeid for the given type. defs will return the typeid of the underlying type. The typeid size is the same as that of an iptr.

typeid x = Foo.typeid;

values

Returns a slice containing the values of an enum or fault.

enum FooEnum
{
    BAR,
    BAZ
}
String x = FooEnum.values[1].nameof; // "BAR"

Compile time functions

There are several built-in functions to inspect the code during compile time.

$alignof

Returns the alignment in bytes needed for the type or member.

module test::bar;

struct Foo
{
    int x;
    char[] y;
}
int g = 123;

$alignof(Foo.x); // => returns 4
$alignof(Foo.y); // => returns 8 on 64 bit
$alignof(Foo);   // => returns 8 on 64 bit
$alignof(g);     // => returns 4

$defined

Returns true if the expression inside is defined and all sub expressions are valid.

$defined(Foo.x);     // => returns true
$defined(Foo.z);     // => returns false
int[2] abc;
$defined(abc.len);   // => returns true
$defined(abc.len()); // => returns false
$defined((int)abc);  // => returns false
// $defined(abc.len() + 1)  would be an error

$eval

Converts a compile time string with the corresponding variable:

int a = 123;         // => a is now 123
$eval("a") = 222;    // => a is now 222
$eval("mymodule::fooFunc")(a); // => same as mymodule::fooFunc(a)

$eval is limited to a single, optionally path prefixed, identifier. Consequently methods cannot be evaluated directly:

struct Foo { ... }
fn int Foo.test(Foo* f) { ... }

fn void test()
{
    void* test1 = &$eval("test"); // Works
    void* test2 = &Foo.$eval("test"); // Works
    // void* test3 = &$eval("Foo.test"); // Error
}

$evaltype

Similar to $eval but for types:

$evaltype("float") f = 12.0f;

$extnameof

Returns the external name of a type, variable or function. The external name is the one used by the linker.

fn void testfn(int x) { }
String a = $extnameof(g); // => "test.bar.g";
string b = $extnameof(testfn); // => "test.bar.testfn"

$nameof

Returns the name of a function or variable as a string without module prefixes.

fn void test() { }
int g = 1;

String a = $nameof(g); // => "g"
String b = $nameof(test); // => "test"

$offsetof

Returns the offset of a member in a struct.

Foo z;
$offsetof(z.y); // => returns 8 on 64 bit, 4 on 32 bit

$qnameof

Returns the same as $nameof, but with the full module name prepended.

module abc;
fn void test() { }
int g = 1;

String a = $qnameof(g); // => "abc::g"
String b = $qnameof(test); // => "abc::test"

$sizeof

This is used on a value to determine the allocation size needed. $sizeof(a) is equivalent to doing $typeof(a).sizeof. Note that this is only used on values and not on types.

$typeof(a)* x = allocate_bytes($sizeof(a));
*x = a;

$stringify

Returns the expression as a string. It has a special behaviour for macro expression parameters, where $stringify(#foo) will return the expression contained in #foo rather than simply return “#foo”

$typeof

Returns the type of an expression or variable as a type itself.

Foo f;
$typeof(f) x = f;

title: Standard Library description: Standard Library sidebar:

order: 128

The standard library is currently in development, so frequent changes will occur. Note that all std::core modules and sub modules are implicitly imported.

std::core::builtin

All functions and macros in this library can be used without path qualifiers.

void panic(char message, char file, char *function, uint line)

Default function called when the asserts fails.

void @swap(&a, &b)

Swap values in a and b.

int a = 3;
int b = 5;
@swap(a, b);
io::printfn("%d", a); // Prints 5

anycast(any v, $Type)

Optionally cast the value v to type $Type* on failure returns CastResult.TYPE_MISMATCH.

int b;
any a = &b;
float*! c = anycast(a, float); // Will return TYPE_MISMATCH
int*! d = anycast(a, int);     // Works!

void unreachable($string = “Unreachable statement reached.”)

Mark a code path as unreachable.

switch (x)
{
    case 0:
        foo();
    case 1:
        bar();
    default:
        // Should never happen.
        unreachable("x should have been 0 or 1");    
}

On safe mode this will throw a runtime panic when reached. For release mode the compiler will assume this case never happens.

bitcast(value, $Type)

Do a bitcast of a value to $Type, requires that the types are of the same memory size.

float x = 1.0;
int y = bitcast(x, int); // y = 0x3f800000

enum_by_name($Type, enum_name)

Optionally returns the enum value with the given name. $Type must be an enum. Returns SearchResult.MISSING on failure.

enum Foo { ABC, CDE, EFG }

fn void! test()
{
    Foo f = enum_by_name(Foo, "CDE")!; 
    // same as Foo f = Foo.CDE;
}

void @scope(&variable; @body)

Scopes a variable:

int a = 3;

@scope(a)
{
    a = 4;
    a++;
};

// Prints a = 3
io::printfn("a = %d", a, b);

less, greater, less_eq, greater_eq, equals

All macros take two values and compare them. Any type implementing Type.less or Type.compare_to may be compared (or if the type implements <). Types implementing Type.equals may use equals even if neither less nor compare_to are implemented.

Faults

std::core::env

Constants

std::core::mem

malloc, malloc_checked, malloc_aligned

Allocate the given number of bytes. malloc will panic on out of memory, whereas malloc_checked and malloc_aligned returns an optional value. malloc_aligned adds an alignment, which must be a power of 2. Any pointer allocated using malloc_aligned must be freed using free_aligned rather the normal free or memory corruption may result.

char* data = malloc(8);
char*! data2 = malloc_checked(8);
int[<16>]*! data3 = malloc_aligned(16 * int.sizeof), 128);

new($Type, #initializer), new_aligned($Type, #initializer)

This allocates a single element of $Type, returning the pointer. An optional initializer may be added, which immediately initializes the value to that of the initializer.

If no initializer is provided, it is zero initialized. new_aligned works the same but for overaligned types, such allocations must be freed using free_aligned

int* a = mem::new(int);
Foo* foo = mem::new(Foo, { 1, 2 });

alloc($Type), alloc_aligned($Type)

Allocates a single element of $Type, same as new, but without initializing the data.

new_array($Type, usz elements), new_array_aligned($Type, usz elements)

Allocates a slice of elements number of elements, returning a slice of the given length. Elements are zero initialized. new_array_aligned is used for types that exceed standard alignment.

int[] ints = mem::new_array(int, 100); // Allocated int[100] on the heap, zero initialized.

alloc_array($Type, usz elements), alloc_array_aligned($Type, usz elements)

Same as new_array but without initialization.

calloc, calloc_checked, calloc_aligned

Identical to the malloc variants, except the data is guaranteed to be zeroed out.

relloc, relloc_checked, realloc_aligned

Resizes memory allocated using malloc or calloc. Any extra data is guaranteed to be zeroed out. realloc_aligned can only be used with pointers created using calloc_aligned or alloc_aligned.

free, free_aligned

Frees memory allocated using malloc or calloc. Any memory allocated using “_aligned” variants must be freed using free_aligned.

@scoped(Allocator* allocator; @body())

Swaps the current memory allocator for the duration of the call.

DynamicArenaAllocator dynamic_arena;
dynamic_arena.init(1024);
mem::@scoped(&dynamic_arena) 
{
    // This allocation uses the dynamic arena 
    Foo* f = malloc(Foo);
};
// Release any dynamic arena memory.
dynamic_arena.destroy();

@tscoped(; @body())

Same as @scoped, but uses the temporary allocator rather than any arbitrary allocator.

void* tmalloc(usz size, usz alignment = 0)

Allocates memory using the temporary allocator. Panic on failure. It has type variants similar to malloc, so tmalloc(Type) would create a Type* using the temporary allocator.

void* tcalloc(usz size, usz alignment = 0)

Same as tmalloc but clears the memory.

void trealloc(void ptr, usz size, usz alignment = 0)

realloc but on memory received using tcalloc or tmalloc.

temp_new, temp_alloc, temp_new_array, temp_alloc_array

Same as the new, alloc, new_array and alloc_array respectively.

void @pool(;@body)

Opens a temporary memory scope.

@pool() 
{
    // This allocation uses the dynamic arena 
    Foo* f = tmalloc(Foo);
};

@volatile_load(&x)

Returns the value in x using a volatile load.

// Both loads will always happen:
int y = @volatile_load(my_global);
y = @volatile_load(my_global);

@volatile_store(&x, y)

Store the value y in x using a volatile store.

// Both stores will always happen:
@volatile_store(y, 1);
@volatile_store(y, 1);

usz aligned_offset(usz offset, usz alignment)

Returns an aligned size based on the current offset. The alignment must be a power of two. E.g. mem::aligned_offset(17, 8) would return 24

usz aligned_pointer(void* ptr, usz alignment)

Returns a pointer aligned to the given alignment, using aligned_offset.

bool ptr_is_aligned(void* ptr, usz alignment)

Return true if the pointer is aligned, false otherwise.

void copy(void dst, void src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)

Copies bytes from one pointer to another. It may optionally be set as volatile, in which case the copy may not be optimized away. Furthermore the source and destination alignment may be used.

Foo* f = tmalloc(data_size);
mem::copy(f, slice.ptr, size);

void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false)

Sets bytes to a value. This operation may be aligned and/or volatile. See the copy method.

void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false)

Sets bytes to zero. This operation may be aligned and/or volatile. See the copy method.

@clone(&value)

Makes a shallow copy of a value using the regular allocator.

Foo f = ...

return @clone(f);

@tclone(&value)

Same as @clone but uses the temporary allocator.

std::core::types

bool is_comparable_value($Type)

Return true if the type can be used with comparison operators.

bool is_equatable_value(value)

Return true if the value can be compared using the equals macro.

bool is_equatable_value(value)

Return true if the value can be compared using the comparison macros.

kind_is_int(TypeKind kind)

any_to_int(any* v, $Type)

Returns an optional value of $Type if the any value losslessly may be converted into the given type. Returns a ConversionResult otherwise.

any* v = &&128;
short y = any_to_int(v, short)!!; // Works 
ichar z = any_to_int(v, ichar)!!; // Panics VALUE_OUT_OF_RANGE

std::core::str::conv

usz! char32_to_utf8(Char32 c, char* output, usz available)

Convert a UTF32 codepoint to an UTF8 buffer. size has the number of writable bytes left. It returns the number of bytes used, or UnicodeResult.CONVERSION_FAILED if the buffer is too small.

void char32_to_utf16_unsafe(Char32 c, Char16** output)

Convert a UTF32 codepoint to an UTF16 buffer without bounds checking, moving the output pointer 1 or 2 steps.

std::io

String! readline(stream = io::stdin(), Allocator allocator = allocator::heap())

Read a String! from a file stream, which is standard input (stdin) by default, reads to the next newline character \n or to the end of stream. Readline returns an Optional string.

import std::io;

fn void! hello_name()
{
    String! name = io::readline();
    if (catch excuse = name) 
    {
        return excuse?;
    }

    io::printfn("Name was: %s.", name);
}

:::Note
`\r` will be filtered from the String.
:::

### String! treadline(stream = io::stdin())
Read a `String!` from a file stream which is standard input (stdin) by default, Reads to the next newline character `\n` or to the end of stream. `Treadline` returns an [Optional](/language-common/optionals-essential/#what-is-an-optional) string. The temporary allocator is used by `Treadline`, in contrast the `readline` defaults to the heap allocator, but is configurable to other allocators. 

```c
import std::io;

fn void! hello_name()
{
    String! name = io::treadline();
    if (catch excuse = name) {
        return excuse?;
    }

    io::printfn("Hello %s! Hope you have a great day", name);
}

:::Note \r will be filtered from the String. :::

void print(x), void printn(x = “”)

Print a value to stdout works for the majority of types, including structs, which can be helpful for debugging. The printn variant appends a newline.

import std::io;

enum Heat 
{
    WARM,
    WARMER,
    REALLY_WARM,
}

fn void main() 
{
    int[<2>] vec = { 4, 2 };
    Heat weather = WARM;
    int[5] fib = { 0, 1, 1, 2, 3 };
    String dialogue = "secret";

    io::print("Hello");   // Hello
    io::print(20);        // 20
    io::print(2.2);       // 2.200000
    io::print(vec);       // [<4, 2>]
    io::print(weather);   // WARM
    io::print(fib);       // [0, 1, 1, 2, 3] 
    io::print(dialogue);  // secret
}

### void eprint(x), void eprintn(x)
Print any value to stderr.
The `eprintn` variant appends a newline.

See `print` for usage.

### usz! printf(String format, args...) @maydiscard
Regular printf functionality: `%s`, `%x`, `%d`, `%f` and `%p` are supported.
Will also print enums and vectors. Prints to stdout.

```c3
import std::io;

enum Heat 
{
    WARM,
    WARMER,
    REALLY_WARM,
}

fn void main() 
{
    int[<2>] vec = { 4, 2 };
    Heat weather = REALLY_WARM;
    String dialogue = "Hello";

    io::printfn("%s", dialogue);  // Hello
    io::printfn("%d", 20);        // 20
    io::printfn("%f", 2.2);       // 2.200000
    io::printfn("%s", vec);       // [<4, 2>]
    io::printfn("%s", weather);   // REALLY_WARM
}

Also available as `printfn` which appends a newline.

### usz! eprintf(String format, args...) @maydiscard
Regular printf functionality: `%s`, `%x`, `%d`, `%f` and `%p` are supported.
Will also print enums and vectors. Prints to stderr.

Also available as `eprintfn` which appends a newline.

See `printf` for usage

### char[]! bprintf(char[] buffer, String format, args...) @maydiscard
Prints using a 'printf'-style formatting string, to a string buffer.

Returns a slice of the `buffer` argument with the resulting length.

### usz! fprint(out, x), usz! fprintn(out, x = "")
Print a value to a stream. `out` must implement `OutStream`.
The `fprintn` variant appends a newline.

### usz! fprintf(OutStream out, String format, args...)
Prints to the specified OutStream using a 'printf'-style formatting string.

Returns the number of characters printed.

`fprintfn` appends a newline.

### void putchar(char c) @inline
Libc `putchar`, prints a single character to stdout.

### usz! DString.appendf(DString* str, String format, args...) @maydiscard
Same as printf but on dynamic strings.

### File* stdout(), File* stdin(), File* stderr()
Return stdout, stdin and stderr respectively.

## std::io::file

### File! open(String filename, String mode)
Open a file with the given file name with the given mode (r, w etc)

### File! open_path(Path path, String mode)
Open a file pointed to by a Path struct, with the given mode.

### bool is_file(String path)
See whether the given path is a file.

### usz! get_size(String path)
Get the size of a file.

### void! delete(String filename)
Delete a file.

### void! File.reopen(&self, String filename, String mode)
Reopen a file with a new filename and mode.

### usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET)
Seek in a file. Based on the libc function.

### void! File.write_byte(&self, char c) @dynamic
Write a single byte to a file.

### void! File.close(&self) @inline @dynamic
Close a file, based on the libc function.

### bool File.eof(&self) @inline
True if EOF has been reached. Based on the libc function.

### usz! File.read(&self, char[] buffer)
Read into a buffer, based on the libc function.

### usz! File.write(&self, char[] buffer)
Write to a buffer, based on the libc function.

### char! File.read_byte(&self) @dynamic
Read a single byte from a file.

### char[]! load_buffer(String filename, char[] buffer)
Load up to buffer.len characters into the buffer.

Returns IoError.OVERFLOW if the file is longer than the buffer.

### char[]! load_new(String filename, Allocator allocator = allocator::heap())
Load the entire file into a new buffer.

### char[]! load_temp(String filename)
Load the entire file into a buffer allocated using the temporary allocator.

### void! File.flush(&self) @dynamic
Flush a file, based on the libc function.

## std::collections::list(\<Type\>)

Generic list module, elements are of `Type`.

```c
import std::collections::list;
def MyIntList = List(<int>);

...

MyIntList list;
list.push(123);
list.free();

List.push(List *list, Type element), append(…)

Append a single value to the list.

Type List.pop(List* list)

Removes and returns the last entry in the list.

Type List.pop_first(List *list)

Removes the first entry in the list.

void List.remove_at(List *list, usz index)

Removes the entry at index.

void List.insert_at(List *list, usz index, Type type)

Inserts a value at index.

void List.push_front(List *list, Type type)

Inserts a value to the front of the list.

void List.remove_last(List* list)

Remove the last value of the list.

void List.remove_first(List *list)

Remove the first element in the list.

Type List.first(List list)

Return the first element in the list if available.

Type List.last(List list)

Return the last element in the list if available.

List.is_empty(List *list)

Return true if the list is empty.

usz List.len(List *list)

Return the number of elements in the list.

Type List.get(List *list, usz index)

Return the value at index.

void List.free(List *list)

Free all memory associated with this list.

void List.swap(List *list, usz i, usz j)

Swap two elements in the list.


title: Standard Library Reference description: Standard Library Reference sidebar:

order: 141

libc

distinct Errno = inline CInt;
struct DivResult
struct LongDivResult
struct TimeSpec
struct Timespec
struct Tm
fn TimeSpec Duration.to_timespec(self) @inline
fn TimeSpec NanoDuration.to_timespec(self) @inline
fn Errno errno()
fn void errno_set(Errno e)

libc @if(!env::LIBC)

fn void* calloc(usz count, usz size) @weak @extern("calloc") @nostrip
fn CFile fclose(CFile) @weak @extern("fclose") @nostrip
fn int feof(CFile stream) @weak @extern("feof") @nostrip
fn int fflush(CFile stream) @weak @extern("fflush") @nostrip
fn int fgetc(CFile stream) @weak @extern("fgetc") @nostrip
fn char* fgets(ZString str, int n, CFile stream) @weak @extern("fgets") @nostrip
fn CFile fopen(ZString filename, ZString mode) @weak @extern("fopen") @nostrip
fn int fputc(int c, CFile stream) @weak @extern("fputc") @nostrip
fn usz fread(void* ptr, usz size, usz nmemb, CFile stream) @weak @extern("fread") @nostrip
fn void* free(void*) @weak @extern("free")
fn CFile freopen(ZString filename, ZString mode, CFile stream) @weak @extern("fopen") @nostrip
fn int fseek(CFile stream, SeekIndex offset, int whence) @weak @extern("fseek") @nostrip
fn usz fwrite(void* ptr, usz size, usz nmemb, CFile stream) @weak @extern("fwrite") @nostrip
fn void longjmp(JmpBuf* buffer, CInt value) @weak @extern("longjmp") @nostrip
fn void* malloc(usz size) @weak @extern("malloc") @nostrip
fn void* memcpy(void* dest, void* src, usz n) @weak @extern("memcpy") @nostrip
fn void* memmove(void* dest, void* src, usz n) @weak @extern("memmove") @nostrip
fn void* memset(void* dest, CInt value, usz n) @weak @extern("memset") @nostrip
fn int putc(int c, CFile stream) @weak @extern("putc") @nostrip
fn int putchar(int c) @weak @extern("putchar") @nostrip
fn int puts(ZString str) @weak @extern("puts") @nostrip
fn void* realloc(void* ptr, usz size) @weak @extern("realloc") @nostrip
fn CInt setjmp(JmpBuf* buffer) @weak @extern("setjmp") @nostrip

libc @if(env::DARWIN)

struct Stat
macro CFile stderr()
macro CFile stdin()
macro CFile stdout()

libc @if(env::LIBC && !env::WIN32 && !env::LINUX && !env::DARWIN)

macro CFile stderr() { return (CFile*)(uptr)STDERR_FD; }
macro CFile stdin() { return (CFile*)(uptr)STDIN_FD; }
macro CFile stdout() { return (CFile*)(uptr)STDOUT_FD; }

libc @if(env::LINUX)

struct Stat @if(!env::X86_64)
struct Stat @if(env::X86_64)
macro usz malloc_size(void* ptr)
macro CFile stderr()
macro CFile stdin()
macro CFile stdout()

libc @if(env::POSIX)

struct Sigaction
struct Stack_t

libc @if(env::WIN32)

struct SystemInfo
macro Tm* gmtime_r(Time_t* timer, Tm* buf)
macro Tm* localtime_r(Time_t* timer, Tm* buf)
macro usz malloc_size(void* ptr)
macro isz read(Fd fd, void* buffer, usz buffer_size)
macro CInt setjmp(JmpBuf* buffer)
macro CFile stderr()
macro CFile stdin()
macro CFile stdout()
macro isz write(Fd fd, void* buffer, usz count)

libc::os

fn int errno() @if(ERRNO_DEFAULT)
macro int errno() @if(env::DARWIN)
macro int errno() @if(env::LINUX)
macro int errno() @if(env::WIN32)
fn void errno_set(int err) @if(ERRNO_DEFAULT)
macro void errno_set(int err) @if(env::DARWIN)
macro void errno_set(int err) @if(env::LINUX)
macro void errno_set(int err) @if(env::WIN32)

std::ascii

fn char char.from_hex(char c)
fn bool char.in_range(char c, char start, char len)
fn bool char.is_alnum(char c)
fn bool char.is_alpha(char c)
fn bool char.is_bdigit(char c)
fn bool char.is_blank(char c)
fn bool char.is_cntrl(char c)
fn bool char.is_digit(char c)
fn bool char.is_graph(char c)
fn bool char.is_lower(char c)
fn bool char.is_odigit(char c)
fn bool char.is_print(char c)
fn bool char.is_punct(char c)
fn bool char.is_space(char c)
fn bool char.is_upper(char c)
fn bool char.is_xdigit(char c)
fn char char.to_lower(char c)
fn char char.to_upper(char c)
fn bool in_range(char c, char start, char len)
macro bool in_range_m(c, start, len)
fn bool is_alnum(char c)
macro bool is_alnum_m(c)
fn bool is_alpha(char c)
macro bool is_alpha_m(c)
fn bool is_bdigit(char c)
macro bool is_bdigit_m(c)
fn bool is_blank(char c)
macro bool is_blank_m(c)
fn bool is_cntrl(char c)
macro bool is_cntrl_m(c)
fn bool is_digit(char c)
macro bool is_digit_m(c)
fn bool is_graph(char c)
macro bool is_graph_m(c)
fn bool is_lower(char c)
macro bool is_lower_m(c)
fn bool is_odigit(char c)
macro bool is_odigit_m(c)
fn bool is_print(char c)
macro bool is_print_m(c)
fn bool is_punct(char c)
macro bool is_punct_m(c)
fn bool is_space(char c)
macro bool is_space_m(c)
fn bool is_upper(char c)
macro bool is_upper_m(c)
fn bool is_xdigit(char c)
macro bool is_xdigit_m(c)
fn char to_lower(char c)
macro to_lower_m(c)
fn char to_upper(char c)
macro to_upper_m(c)
fn bool uint.in_range(uint c, uint start, uint len)
fn bool uint.is_alnum(uint c)
fn bool uint.is_alpha(uint c)
fn bool uint.is_bdigit(uint c)
fn bool uint.is_blank(uint c)
fn bool uint.is_cntrl(uint c)
fn bool uint.is_digit(uint c)
fn bool uint.is_graph(uint c)
fn bool uint.is_lower(uint c)
fn bool uint.is_odigit(uint c)
fn bool uint.is_print(uint c)
fn bool uint.is_punct(uint c)
fn bool uint.is_space(uint c)
fn bool uint.is_upper(uint c)
fn bool uint.is_xdigit(uint c)
fn uint uint.to_lower(uint c)
fn uint uint.to_upper(uint c)

std::atomic

macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment)
macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, success, failure, $alignment)
fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired, CInt success, CInt failure) @extern("__atomic_compare_exchange") @export
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)

std::atomic::types(<Type>)

struct Atomic
macro Type Atomic.add(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.and(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.mul(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.or(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
macro Type Atomic.shift_left(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
macro Type Atomic.shift_right(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
macro void Atomic.store(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.sub(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
fn Type Atomic.xor(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))

std::bits

macro bswap(i) @builtin
macro char.clz(self)
macro char.ctz(self)
macro char char.fshl(hi, char lo, char shift)
macro char char.fshr(hi, char lo, char shift)
macro char.popcount(self)
macro char char.rotl(self, char shift)
macro char char.rotr(self, char shift)
macro char[<*>].clz(self)
macro char[<*>].ctz(self)
macro char[<*>] char[<*>].fshl(hi, char[<*>] lo, char[<*>] shift)
macro char[<*>] char[<*>].fshr(hi, char[<*>] lo, char[<*>] shift)
macro char[<*>].popcount(self)
macro char[<*>] char[<*>].rotl(self, char[<*>] shift)
macro char[<*>] char[<*>].rotr(self, char[<*>] shift)
macro ichar.clz(self)
macro ichar.ctz(self)
macro ichar ichar.fshl(hi, ichar lo, ichar shift)
macro ichar ichar.fshr(hi, ichar lo, ichar shift)
macro ichar.popcount(self)
macro ichar ichar.rotl(self, ichar shift)
macro ichar ichar.rotr(self, ichar shift)
macro ichar[<*>].clz(self)
macro ichar[<*>].ctz(self)
macro ichar[<*>] ichar[<*>].fshl(hi, ichar[<*>] lo, ichar[<*>] shift)
macro ichar[<*>] ichar[<*>].fshr(hi, ichar[<*>] lo, ichar[<*>] shift)
macro ichar[<*>].popcount(self)
macro ichar[<*>] ichar[<*>].rotl(self, ichar[<*>] shift)
macro ichar[<*>] ichar[<*>].rotr(self, ichar[<*>] shift)
macro int.clz(self)
macro int.ctz(self)
macro int int.fshl(hi, int lo, int shift)
macro int int.fshr(hi, int lo, int shift)
macro int.popcount(self)
macro int int.rotl(self, int shift)
macro int int.rotr(self, int shift)
macro int128.clz(self)
macro int128.ctz(self)
macro int128 int128.fshl(hi, int128 lo, int128 shift)
macro int128 int128.fshr(hi, int128 lo, int128 shift)
macro int128.popcount(self)
macro int128 int128.rotl(self, int128 shift)
macro int128 int128.rotr(self, int128 shift)
macro int128[<*>].clz(self)
macro int128[<*>].ctz(self)
macro int128[<*>] int128[<*>].fshl(hi, int128[<*>] lo, int128[<*>] shift)
macro int128[<*>] int128[<*>].fshr(hi, int128[<*>] lo, int128[<*>] shift)
macro int128[<*>].popcount(self)
macro int128[<*>] int128[<*>].rotl(self, int128[<*>] shift)
macro int128[<*>] int128[<*>].rotr(self, int128[<*>] shift)
macro int[<*>].clz(self)
macro int[<*>].ctz(self)
macro int[<*>] int[<*>].fshl(hi, int[<*>] lo, int[<*>] shift)
macro int[<*>] int[<*>].fshr(hi, int[<*>] lo, int[<*>] shift)
macro int[<*>].popcount(self)
macro int[<*>] int[<*>].rotl(self, int[<*>] shift)
macro int[<*>] int[<*>].rotr(self, int[<*>] shift)
macro long.clz(self)
macro long.ctz(self)
macro long long.fshl(hi, long lo, long shift)
macro long long.fshr(hi, long lo, long shift)
macro long.popcount(self)
macro long long.rotl(self, long shift)
macro long long.rotr(self, long shift)
macro long[<*>].clz(self)
macro long[<*>].ctz(self)
macro long[<*>] long[<*>].fshl(hi, long[<*>] lo, long[<*>] shift)
macro long[<*>] long[<*>].fshr(hi, long[<*>] lo, long[<*>] shift)
macro long[<*>].popcount(self)
macro long[<*>] long[<*>].rotl(self, long[<*>] shift)
macro long[<*>] long[<*>].rotr(self, long[<*>] shift)
macro reverse(i)
macro short.clz(self)
macro short.ctz(self)
macro short short.fshl(hi, short lo, short shift)
macro short short.fshr(hi, short lo, short shift)
macro short.popcount(self)
macro short short.rotl(self, short shift)
macro short short.rotr(self, short shift)
macro short[<*>].clz(self)
macro short[<*>].ctz(self)
macro short[<*>] short[<*>].fshl(hi, short[<*>] lo, short[<*>] shift)
macro short[<*>] short[<*>].fshr(hi, short[<*>] lo, short[<*>] shift)
macro short[<*>].popcount(self)
macro short[<*>] short[<*>].rotl(self, short[<*>] shift)
macro short[<*>] short[<*>].rotr(self, short[<*>] shift)
macro uint.clz(self)
macro uint.ctz(self)
macro uint uint.fshl(hi, uint lo, uint shift)
macro uint uint.fshr(hi, uint lo, uint shift)
macro uint.popcount(self)
macro uint uint.rotl(self, uint shift)
macro uint uint.rotr(self, uint shift)
macro uint128.clz(self)
macro uint128.ctz(self)
macro uint128 uint128.fshl(hi, uint128 lo, uint128 shift)
macro uint128 uint128.fshr(hi, uint128 lo, uint128 shift)
macro uint128.popcount(self)
macro uint128 uint128.rotl(self, uint128 shift)
macro uint128 uint128.rotr(self, uint128 shift)
macro uint128[<*>].clz(self)
macro uint128[<*>].ctz(self)
macro uint128[<*>] uint128[<*>].fshl(hi, uint128[<*>] lo, uint128[<*>] shift)
macro uint128[<*>] uint128[<*>].fshr(hi, uint128[<*>] lo, uint128[<*>] shift)
macro uint128[<*>].popcount(self)
macro uint128[<*>] uint128[<*>].rotl(self, uint128[<*>] shift)
macro uint128[<*>] uint128[<*>].rotr(self, uint128[<*>] shift)
macro uint[<*>].clz(self)
macro uint[<*>].ctz(self)
macro uint[<*>] uint[<*>].fshl(hi, uint[<*>] lo, uint[<*>] shift)
macro uint[<*>] uint[<*>].fshr(hi, uint[<*>] lo, uint[<*>] shift)
macro uint[<*>].popcount(self)
macro uint[<*>] uint[<*>].rotl(self, uint[<*>] shift)
macro uint[<*>] uint[<*>].rotr(self, uint[<*>] shift)
macro ulong.clz(self)
macro ulong.ctz(self)
macro ulong ulong.fshl(hi, ulong lo, ulong shift)
macro ulong ulong.fshr(hi, ulong lo, ulong shift)
macro ulong.popcount(self)
macro ulong ulong.rotl(self, ulong shift)
macro ulong ulong.rotr(self, ulong shift)
macro ulong[<*>].clz(self)
macro ulong[<*>].ctz(self)
macro ulong[<*>] ulong[<*>].fshl(hi, ulong[<*>] lo, ulong[<*>] shift)
macro ulong[<*>] ulong[<*>].fshr(hi, ulong[<*>] lo, ulong[<*>] shift)
macro ulong[<*>].popcount(self)
macro ulong[<*>] ulong[<*>].rotl(self, ulong[<*>] shift)
macro ulong[<*>] ulong[<*>].rotr(self, ulong[<*>] shift)
macro ushort.clz(self)
macro ushort.ctz(self)
macro ushort ushort.fshl(hi, ushort lo, ushort shift)
macro ushort ushort.fshr(hi, ushort lo, ushort shift)
macro ushort.popcount(self)
macro ushort ushort.rotl(self, ushort shift)
macro ushort ushort.rotr(self, ushort shift)
macro ushort[<*>].clz(self)
macro ushort[<*>].ctz(self)
macro ushort[<*>] ushort[<*>].fshl(hi, ushort[<*>] lo, ushort[<*>] shift)
macro ushort[<*>] ushort[<*>].fshr(hi, ushort[<*>] lo, ushort[<*>] shift)
macro ushort[<*>].popcount(self)
macro ushort[<*>] ushort[<*>].rotl(self, ushort[<*>] shift)
macro ushort[<*>] ushort[<*>].rotr(self, ushort[<*>] shift)

std::collections::anylist

struct AnyList (Printable)
macro any AnyList.@item_at(&self, usz index) @operator([])
fn void AnyList.add_all(&self, AnyList* other_list)
fn any[] AnyList.array_view(&self)
fn void AnyList.clear(&self)
macro AnyList.first(&self, $Type)
fn any! AnyList.first_any(&self) @inline
fn void AnyList.free(&self)
fn void AnyList.free_element(&self, any element) @inline
macro AnyList.get(&self, usz index, $Type)
fn any AnyList.get_any(&self, usz index) @inline
fn bool AnyList.is_empty(&self) @inline
macro AnyList.last(&self, $Type)
fn any! AnyList.last_any(&self) @inline
fn usz AnyList.len(&self) @operator(len) @inline
fn AnyList* AnyList.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
fn any! AnyList.new_pop(&self, Allocator allocator = allocator::heap())
fn any! AnyList.new_pop_first(&self, Allocator allocator = allocator::heap())
macro AnyList.pop(&self, $Type)
macro AnyList.pop_first(&self, $Type)
fn any! AnyList.pop_first_retained(&self)
fn any! AnyList.pop_retained(&self)
macro void AnyList.push(&self, element)
macro void AnyList.push_front(&self, type)
fn void AnyList.remove_at(&self, usz index)
fn void AnyList.remove_first(&self)
fn usz AnyList.remove_if(&self, AnyPredicate filter)
fn void AnyList.remove_last(&self)
fn usz AnyList.remove_using_test(&self, AnyTest filter, any context)
fn void AnyList.reserve(&self, usz min_capacity)
fn usz AnyList.retain_if(&self, AnyPredicate selection)
fn usz AnyList.retain_using_test(&self, AnyTest filter, any context)
fn void AnyList.reverse(&self)
macro void AnyList.set(&self, usz index, value)
fn void AnyList.swap(&self, usz i, usz j)
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16)
fn any! AnyList.temp_pop(&self)
fn any! AnyList.temp_pop_first(&self)
fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
fn String AnyList.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String AnyList.to_tstring(&self)

std::collections::bitset(<SIZE>)

struct BitSet
fn usz BitSet.cardinality(&self)
fn bool BitSet.get(&self, usz i) @operator([]) @inline
fn usz BitSet.len(&self) @operator(len) @inline
fn void BitSet.set(&self, usz i)
fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
fn void BitSet.unset(&self, usz i)

std::collections::enummap(<Enum, ValueType>)

struct EnumMap (Printable)
fn ValueType EnumMap.get(&self, Enum key) @operator([]) @inline
fn ValueType* EnumMap.get_ref(&self, Enum key) @operator(&[]) @inline
fn void EnumMap.init(&self, ValueType init_value)
fn usz EnumMap.len(&self) @operator(len) @inline
fn void EnumMap.set(&self, Enum key, ValueType value) @operator([]=) @inline
fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic
fn String EnumMap.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String EnumMap.to_tstring(&self) @dynamic

std::collections::enumset(<Enum>)

distinct EnumSet (Printable) = EnumSetType;
fn void EnumSet.add(&self, Enum v)
fn void EnumSet.add_all(&self, EnumSet s)
fn EnumSet EnumSet.and_of(&self, EnumSet s)
fn void EnumSet.clear(&self)
fn EnumSet EnumSet.diff_of(&self, EnumSet s)
fn bool EnumSet.has(&self, Enum v)
fn EnumSet EnumSet.or_of(&self, EnumSet s)
fn bool EnumSet.remove(&self, Enum v)
fn void EnumSet.remove_all(&self, EnumSet s)
fn void EnumSet.retain_all(&self, EnumSet s)
fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
fn String EnumSet.to_new_string(&set, Allocator allocator = allocator::heap()) @dynamic
fn String EnumSet.to_tstring(&set) @dynamic
fn EnumSet EnumSet.xor_of(&self, EnumSet s)

std::collections::enumset::private

macro typeid type_for_enum_elements(usz $elements)

std::collections::growablebitset(<Type>)

struct GrowableBitSet
fn usz GrowableBitSet.cardinality(&self)
fn void GrowableBitSet.free(&self)
fn bool GrowableBitSet.get(&self, usz i) @operator([]) @inline
fn usz GrowableBitSet.len(&self) @operator(len)
fn GrowableBitSet* GrowableBitSet.new_init(&self, usz initial_capacity = 1, Allocator allocator = allocator::heap())
fn void GrowableBitSet.set(&self, usz i)
fn void GrowableBitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
fn GrowableBitSet* GrowableBitSet.temp_init(&self, usz initial_capacity = 1)
fn void GrowableBitSet.unset(&self, usz i)

std::collections::linkedlist(<Type>)

struct LinkedList
fn void LinkedList.clear(&self)
fn Type! LinkedList.first(&self)
fn void LinkedList.free(&self)
fn Type LinkedList.get(&self, usz index)
fn void LinkedList.insert_at(&self, usz index, Type element)
fn Type! LinkedList.last(&self)
fn usz LinkedList.len(&self) @inline
fn LinkedList* LinkedList.new_init(&self, Allocator allocator = allocator::heap())
macro Node* LinkedList.node_at_index(&self, usz index)
fn Type! LinkedList.peek(&self)
fn Type! LinkedList.peek_last(&self)
fn Type! LinkedList.pop(&self)
fn Type! LinkedList.pop_front(&self)
fn void LinkedList.push(&self, Type value)
fn void LinkedList.push_front(&self, Type value)
fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
fn void LinkedList.remove_at(&self, usz index)
fn void! LinkedList.remove_first(&self) @maydiscard
fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
fn void! LinkedList.remove_last(&self) @maydiscard
fn bool LinkedList.remove_last_match(&self, Type t)  @if(ELEMENT_IS_EQUATABLE)
fn void LinkedList.set(&self, usz index, Type element)
fn LinkedList* LinkedList.temp_init(&self)

std::collections::list(<Type>)

struct List (Printable)
macro Type List.@item_at(&self, usz index) @operator([])
fn void List.add_all(&self, List* other_list)
fn void List.add_array(&self, Type[] array)
fn Type[] List.array_view(&self)
fn usz List.byte_size(&self) @inline
fn void List.clear(&self)
fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
fn Type! List.first(&self)
fn void List.free(&self)
fn Type List.get(&self, usz index) @inline
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
fn usz! List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allocator::heap())
fn void List.insert_at(&self, usz index, Type type)
fn bool List.is_empty(&self) @inline
fn Type! List.last(&self)
fn usz List.len(&self) @operator(len) @inline
fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
fn Type! List.pop(&self)
fn Type! List.pop_first(&self)
fn void List.push(&self, Type element) @inline
fn void List.push_front(&self, Type type) @inline
fn void List.remove_all_from(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE)
fn usz List.remove_all_matches(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
fn void List.remove_at(&self, usz index)
fn void! List.remove_first(&self) @maydiscard
fn bool List.remove_first_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
fn usz List.remove_if(&self, ElementPredicate filter)
fn void! List.remove_last(&self) @maydiscard
fn bool List.remove_last_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
fn usz List.remove_using_test(&self, ElementTest filter, any context)
fn void List.reserve(&self, usz min_capacity)
fn usz List.retain_if(&self, ElementPredicate selection)
fn usz List.retain_using_test(&self, ElementTest filter, any context)
fn void List.reverse(&self)
fn usz! List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
fn void List.set(&self, usz index, Type value) @operator([]=)
fn void List.set_at(&self, usz index, Type type)
fn void List.swap(&self, usz i, usz j)
fn List* List.temp_init(&self, usz initial_capacity = 16)
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
fn Type[] List.to_new_array(&self, Allocator allocator = allocator::heap())
fn String List.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn Type[] List.to_tarray(&self)
fn String List.to_tstring(&self)

std::collections::map(<Key, Value>)

struct Entry
struct HashMap
macro HashMap.@each(map; @body(key, value))
macro HashMap.@each_entry(map; @body(entry))
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
fn void HashMap.clear(&map)
fn void HashMap.free(&map)
fn Value! HashMap.get(&map, Key key) @operator([])
fn Entry*! HashMap.get_entry(&map, Key key)
fn Value*! HashMap.get_ref(&map, Key key)
fn bool HashMap.has_key(&map, Key key)
fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
fn bool HashMap.is_empty(&map) @inline
fn bool HashMap.is_initialized(&map)
fn Key[] HashMap.key_new_list(&map, Allocator allocator = allocator::heap())
fn Key[] HashMap.key_tlist(&map)
fn usz HashMap.len(&map) @inline
fn HashMap* HashMap.new_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
fn HashMap* HashMap.new_init_from_map(&self, HashMap* other_map, Allocator allocator = allocator::heap())
fn void! HashMap.remove(&map, Key key) @maydiscard
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
fn HashMap* HashMap.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
fn HashMap* HashMap.temp_init_from_map(&map, HashMap* other_map)
fn Value[] HashMap.value_new_list(&map, Allocator allocator = allocator::heap())
fn Value[] HashMap.value_tlist(&map)

std::collections::maybe(<Type>)

struct Maybe
macro Type! Maybe.get(self)
fn Maybe value(Type val)

std::collections::object

struct Object (Printable)
fn void Object.free(&self)
fn Object*! Object.get(&self, String key)
fn Object* Object.get_at(&self, usz index)
fn bool! Object.get_bool(&self, String key)
fn bool! Object.get_bool_at(&self, usz index)
fn char! Object.get_char(&self, String key)
fn char! Object.get_char_at(&self, usz index)
macro String! Object.get_enum(&self, $EnumType, String key)
macro String! Object.get_enum_at(&self, $EnumType, usz index)
fn double! Object.get_float(&self, String key)
fn double! Object.get_float_at(&self, usz index)
fn ichar! Object.get_ichar(&self, String key)
fn ichar! Object.get_ichar_at(&self, usz index)
fn int! Object.get_int(&self, String key)
fn int128! Object.get_int128(&self, String key)
fn int128! Object.get_int128_at(&self, usz index)
fn int! Object.get_int_at(&self, usz index)
fn usz Object.get_len(&self)
fn long! Object.get_long(&self, String key)
fn long! Object.get_long_at(&self, usz index)
fn Object* Object.get_or_create_obj(&self, String key)
fn short! Object.get_short(&self, String key)
fn short! Object.get_short_at(&self, usz index)
fn String! Object.get_string(&self, String key)
fn String! Object.get_string_at(&self, usz index)
fn uint! Object.get_uint(&self, String key)
fn uint128! Object.get_uint128(&self, String key)
fn uint128! Object.get_uint128_at(&self, usz index)
fn uint! Object.get_uint_at(&self, usz index)
fn ulong! Object.get_ulong(&self, String key)
fn ulong! Object.get_ulong_at(&self, usz index)
fn short! Object.get_ushort(&self, String key)
fn ushort! Object.get_ushort_at(&self, usz index)
fn bool Object.has_key(&self, String key)
fn bool Object.is_array(&self) @inline
fn bool Object.is_bool(&self) @inline
fn bool Object.is_empty(&self) @inline
fn bool Object.is_float(&self) @inline
fn bool Object.is_indexable(&self)
fn bool Object.is_int(&self) @inline
fn bool Object.is_keyable(&self)
fn bool Object.is_map(&self) @inline
fn bool Object.is_null(&self) @inline
fn bool Object.is_string(&self) @inline
macro Object* Object.push(&self, value)
fn void Object.push_object(&self, Object* to_append)
macro Object* Object.set(&self, String key, value)
macro Object* Object.set_at(&self, usz index, String key, value)
fn void Object.set_object_at(&self, usz index, Object* to_set)
fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
macro get_integer_value(Object* value, $Type)
fn Object* new_bool(bool b)
macro Object* new_enum(e, Allocator allocator)
fn Object* new_float(double f, Allocator allocator)
fn Object* new_int(int128 i, Allocator allocator)
fn Object* new_null()
fn Object* new_obj(Allocator allocator)
fn Object* new_string(String s, Allocator allocator)

std::collections::priorityqueue(<Type>)

distinct PriorityQueue = inline PrivatePriorityQueue(<Type, false>);
distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>);

std::collections::priorityqueue::private(<Type, MAX>)

struct PrivatePriorityQueue (Printable)
fn Type! PrivatePriorityQueue.first(&self)
fn void PrivatePriorityQueue.free(&self)
fn Type PrivatePriorityQueue.get(&self, usz index) @operator([])
fn bool PrivatePriorityQueue.is_empty(&self)
fn usz PrivatePriorityQueue.len(&self) @operator(len)
fn void PrivatePriorityQueue.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @inline
fn Type! PrivatePriorityQueue.pop(&self)
fn void PrivatePriorityQueue.push(&self, Type element)
fn void PrivatePriorityQueue.temp_init(&self, usz initial_capacity = 16) @inline
fn usz! PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
fn String PrivatePriorityQueue.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic

std::collections::range(<Type>)

struct ExclusiveRange (Printable)
struct Range (Printable)
fn bool ExclusiveRange.contains(&self, Type value) @inline
fn Type ExclusiveRange.get(&self, usz index) @operator([])
fn usz ExclusiveRange.len(&self) @operator(len)
fn usz! ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
fn String ExclusiveRange.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String ExclusiveRange.to_tstring(&self)
fn bool Range.contains(&self, Type value) @inline
fn Type Range.get(&self, usz index) @operator([])
fn usz Range.len(&self) @operator(len)
fn usz! Range.to_format(&self, Formatter* formatter) @dynamic
fn String Range.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String Range.to_tstring(&self)

std::collections::ringbuffer(<Type, SIZE>)

struct RingBuffer
fn Type RingBuffer.get(&self, usz index) @operator([])
fn void RingBuffer.init(&self) @inline
fn Type! RingBuffer.pop(&self)
fn void RingBuffer.push(&self, Type c)
fn usz RingBuffer.read(&self, usz index, Type[] buffer)
fn void RingBuffer.write(&self, Type[] buffer)

std::collections::triple(<Type1, Type2, Type3>)

struct Triple

std::collections::tuple(<Type1, Type2>)

struct Tuple

std::core::array

macro concat_new(arr1, arr2, Allocator allocator = allocator::heap())
macro index_of(array, element)
macro rindex_of(array, element)
macro slice2d(array, x = 0, xlen = 0, y = 0, ylen = 0)
macro tconcat(arr1, arr2)

std::core::array::slice(<Type>)

struct Slice2d
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
fn usz Slice2d.count(&self)
macro Type[] Slice2d.get(self, usz idy) @operator([])
fn usz Slice2d.len(&self) @operator(len)
fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0)

std::core::bitorder

macro bool is_array_or_slice_of_char(bytes)
macro bool is_arrayptr_or_slice_of_char(bytes)
macro is_bitorder($Type)
macro read(bytes, $Type)
macro write(x, bytes, $Type)

std::core::builtin

enum PrefetchLocality
fault CastResult
fault IteratorResult
fault SearchResult
macro char[] @as_char_view(&value) @builtin
macro anyfault @catch(#expr) @builtin
macro @expect(#value, expected, $probability = 1.0) @builtin
macro bool @likely(bool #value, $probability = 1.0) @builtin
macro bool @ok(#expr) @builtin
macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
macro void @scope(&variable; @body) @builtin
macro void @swap(&a, &b) @builtin
macro bool @unlikely(bool #value, $probability = 1.0) @builtin
macro uint String.hash(String c)
macro any.as_inner(&self)
macro any.retype_to(&self, typeid type)
macro any_make(void* ptr, typeid type) @builtin
macro anycast(any v, $Type) @builtin
macro bitcast(expr, $Type) @builtin
macro uint bool.hash(bool b)
macro uint char.hash(char c)
macro uint char[].hash(char[] c)
macro int compare_to(a, b) @builtin
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
macro enum_by_name($Type, String enum_name) @builtin
macro bool equals(a, b) @builtin
macro void* get_frameaddress(int n)
macro void* get_returnaddress(int n)
macro greater(a, b) @builtin
macro greater_eq(a, b) @builtin
macro uint ichar.hash(ichar c)
macro uint int.hash(int i)
macro uint int128.hash(int128 i)
macro less(a, b) @builtin
macro less_eq(a, b) @builtin
macro uint long.hash(long i)
macro max(x, ...) @builtin
macro min(x, ...) @builtin
fn void panicf(String fmt, String file, String function, uint line, args...)
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE)
macro uint short.hash(short s)
macro swizzle(v, ...) @builtin
macro swizzle2(v, v2, ...) @builtin
macro uint typeid.hash(typeid t)
macro uint uint.hash(uint i)
macro uint uint128.hash(uint128 i)
macro uint ulong.hash(ulong i)
macro void unreachable(String string = "Unreachable statement reached.", ...) @builtin @noreturn
macro void unsupported(String string = "Unsupported function invoked") @builtin @noreturn
macro uint ushort.hash(ushort s)
macro uint void*.hash(void* ptr)

std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS)

fn void sig_bus_error(CInt i)
fn void sig_panic(String message)
fn void sig_segmentation_fault(CInt i)

std::core::dstring

distinct DString (OutStream) = void*;
macro void DString.append(&self, value)
fn void DString.append_char(&self, char c)
fn void DString.append_char32(&self, Char32 c)
fn void DString.append_chars(&self, String str)
fn void DString.append_repeat(&self, char c, usz times)
fn void DString.append_string(&self, DString str)
fn void DString.append_utf32(&self, Char32[] chars)
fn usz! DString.appendf(&self, String format, args...) @maydiscard
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
fn usz DString.capacity(self)
fn void DString.chop(self, usz new_size)
fn void DString.clear(self)
fn DString DString.copy(self, Allocator allocator = null)
fn String DString.copy_str(self, Allocator allocator = allocator::heap())
fn Char32[] DString.copy_utf32(&self, Allocator allocator = allocator::heap())
fn ZString DString.copy_zstr(self, Allocator allocator = allocator::heap())
fn void DString.delete(&self, usz start, usz len = 1)
fn void DString.delete_range(&self, usz start, usz end)
fn bool DString.equals(self, DString other_string)
fn void DString.free(&self)
fn void DString.insert_at(&self, usz index, String s)
fn usz DString.len(&self) @dynamic
fn bool DString.less(self, DString other_string)
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap())
fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator allocator = allocator::heap())
fn usz! DString.read_from_stream(&self, InStream reader)
fn void DString.reserve(&self, usz addition)
fn void DString.set(self, usz index, char c)
fn String DString.str_view(self)
fn DString DString.tcopy(&self)
fn String DString.tcopy_str(self)
fn DString DString.temp_concat(self, DString b)
fn DString DString.temp_init(&self, usz capacity = MIN_CAPACITY)
fn usz! DString.write(&self, char[] buffer) @dynamic
fn void! DString.write_byte(&self, char c) @dynamic
fn ZString DString.zstr_view(&self)
fn DString new(String c = "", Allocator allocator = allocator::heap())
fn DString new_join(String[] s, String joiner, Allocator allocator = allocator::heap())
fn DString new_with_capacity(usz capacity, Allocator allocator = allocator::heap())
fn DString temp_new(String s = "")
fn DString temp_with_capacity(usz capacity)

std::core::env

enum ArchType
enum CompilerOptLevel
enum MemoryEnvironment
enum OsType
macro bool os_is_darwin()
macro bool os_is_posix()

std::core::mem

enum AtomicOrdering : int
struct TempState
macro @atomic_load(&x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
macro void @atomic_store(&x, value, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
macro @clone(value) @builtin @nodiscard
macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
macro void @pool(TempAllocator* #other_temp = null; @body) @builtin
macro void @report_heap_allocs_in_scope(;@body())
macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment)
macro void @scoped(Allocator allocator; @body())
macro void @stack_mem(usz $size; @body(Allocator mem)) @builtin
macro void @stack_pool(usz $size; @body) @builtin
macro @tclone(value) @builtin @nodiscard
macro @volatile_load(&x) @builtin
macro @volatile_store(&x, y) @builtin
fn usz aligned_offset(usz offset, usz alignment)
macro void* aligned_pointer(void* ptr, usz alignment)
macro alloc($Type) @nodiscard
macro alloc_aligned($Type) @nodiscard
macro alloc_array($Type, usz elements) @nodiscard
macro alloc_array_aligned($Type, usz elements) @nodiscard
fn void* calloc(usz size) @builtin @inline @nodiscard
fn void* calloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard
macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false, bool $inlined = false)
macro void clear_inline(void* dst, usz $len, usz $dst_align = 0, bool $is_volatile = false)
macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT, bool $volatile = true, bool $weak = false, usz $alignment = 0)
macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT)
macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false, bool $inlined = false)
macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
macro bool equals(a, b, isz len = -1, usz $align = 0)
fn void free(void* ptr) @builtin @inline
fn void free_aligned(void* ptr) @builtin @inline
macro gather(ptrvec, bool[<*>] mask, passthru)
macro TrackingEnv* get_tracking_env()
fn void* malloc(usz size) @builtin @inline @nodiscard
macro masked_load(ptr, bool[<*>] mask, passthru)
macro masked_store(ptr, value, bool[<*>] mask)
macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
macro new($Type, ...) @nodiscard
macro new_aligned($Type, ...) @nodiscard
macro new_array($Type, usz elements) @nodiscard
macro new_array_aligned($Type, usz elements) @nodiscard
fn bool ptr_is_aligned(void* ptr, usz alignment) @inline
fn void* realloc(void *ptr, usz new_size) @builtin @inline @nodiscard
fn void* realloc_aligned(void *ptr, usz new_size, usz alignment) @builtin @inline @nodiscard
macro scatter(ptrvec, value, bool[<*>] mask)
macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false)
macro void set_inline(void* dst, char val, usz $len, usz $dst_align = 0, bool $is_volatile = false)
fn void* tcalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
macro temp_alloc($Type) @nodiscard
macro temp_alloc_array($Type, usz elements) @nodiscard
macro temp_new($Type, ...) @nodiscard
macro temp_new_array($Type, usz elements) @nodiscard
fn void temp_pop(TempState old_state)
fn TempState temp_push(TempAllocator* other = null)
fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline @nodiscard
macro type_alloc_must_be_aligned($Type)

std::core::mem::allocator

distinct LibcAllocator (Allocator) = uptr;
enum AllocInitType
fault AllocationFailure
interface Allocator
struct AlignedBlock
struct Allocation
struct ArenaAllocator (Allocator)
struct DynamicArenaAllocator (Allocator)
struct OnStackAllocator (Allocator)
struct OnStackAllocatorHeader
struct SimpleHeapAllocator (Allocator)
struct TempAllocator (Allocator)
struct TempAllocatorPage
struct TrackingAllocator (Allocator)
struct TrackingEnv
struct WasmMemory
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
macro void! @aligned_free(#free_fn, void* old_pointer)
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
fn void*! ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void ArenaAllocator.clear(&self)
fn void ArenaAllocator.init(&self, char[] data)
fn usz ArenaAllocator.mark(&self) @dynamic
fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
fn void ArenaAllocator.reset(&self, usz mark) @dynamic
fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void DynamicArenaAllocator.free(&self)
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator allocator)
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*! OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void OnStackAllocator.free(&self)
fn void OnStackAllocator.init(&self, char[] data, Allocator allocator)
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*! SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn usz TempAllocator.mark(&self) @dynamic
fn void! TempAllocator.print_pages(&self, File* f)
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
fn void TempAllocator.reset(&self, usz mark) @dynamic
fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
macro bool TempAllocatorPage.is_aligned(&self)
macro usz TempAllocatorPage.pagesize(&self)
fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn usz TrackingAllocator.allocated(&self)
fn usz TrackingAllocator.allocation_count(&self)
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
fn void TrackingAllocator.clear(&self)
fn void! TrackingAllocator.fprint_report(&self, OutStream out)
fn void TrackingAllocator.free(&self)
fn void TrackingAllocator.init(&self, Allocator allocator)
fn void TrackingAllocator.print_report(&self)
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn usz TrackingAllocator.total_allocated(&self)
fn usz TrackingAllocator.total_allocation_count(&self)
fn char[]! WasmMemory.allocate_block(&self, usz bytes)
macro alloc(Allocator allocator, $Type) @nodiscard
macro alloc_array(Allocator allocator, $Type, usz elements) @nodiscard
macro alloc_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
macro alloc_try(Allocator allocator, $Type) @nodiscard
macro alloc_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
macro void* calloc(Allocator allocator, usz size) @nodiscard
macro void*! calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
macro void*! calloc_try(Allocator allocator, usz size) @nodiscard
macro clone(Allocator allocator, value) @nodiscard
fn any clone_any(Allocator allocator, any value) @nodiscard
macro void free(Allocator allocator, void* ptr)
macro void free_aligned(Allocator allocator, void* ptr)
macro Allocator heap()
macro void* malloc(Allocator allocator, usz size) @nodiscard
macro void*! malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
macro void*! malloc_try(Allocator allocator, usz size) @nodiscard
macro new(Allocator allocator, $Type, ...) @nodiscard
macro new_array(Allocator allocator, $Type, usz elements) @nodiscard
macro new_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
macro new_array_try(Allocator allocator, $Type, usz elements) @nodiscard
fn TempAllocator*! new_temp_allocator(usz size, Allocator allocator)
macro new_try(Allocator allocator, $Type, ...) @nodiscard
macro new_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
macro void* realloc(Allocator allocator, void* ptr, usz new_size) @nodiscard
macro void*! realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
macro void*! realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
macro TempAllocator* temp()

std::core::mem::allocator @if(!env::WIN32 && !env::POSIX)

fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic

std::core::mem::allocator @if(env::POSIX)

fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic

std::core::mem::allocator @if(env::WIN32)

fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic

std::core::runtime

struct AnyRaw
struct BenchmarkUnit
struct SliceRaw
struct TestContext
struct TestUnit
fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator = allocator::heap())
fn int cmp_test_unit(TestUnit a, TestUnit b)
fn bool default_benchmark_runner()
fn bool default_test_runner()
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
fn bool run_tests(TestUnit[] tests)
fn void set_benchmark_max_iterations(uint value) @builtin
fn void set_benchmark_warmup_iterations(uint value) @builtin
fn TestUnit[] test_collection_create(Allocator allocator = allocator::heap())
fn void test_panic(String message, String file, String function, uint line)

std::core::runtime @if(WASM_NOLIBC)

fn void wasm_initialize() @extern("_initialize") @wasm

std::core::string

distinct WString = inline Char16*;
distinct ZString = inline char*;
fault NumberConversion
fault UnicodeResult
struct Splitter
fn String! Splitter.next(&self)
fn void Splitter.reset(&self)
fn String String.concat(s1, String s2, Allocator allocator = allocator::heap())
fn bool String.contains(s, String needle)
fn void String.convert_ascii_to_lower(s)
fn void String.convert_ascii_to_upper(s)
fn String String.copy(s, Allocator allocator = allocator::heap())
fn bool String.ends_with(string, String needle)
fn void String.free(&s, Allocator allocator = allocator::heap())
fn usz! String.index_of(s, String needle)
fn usz! String.index_of_char(s, char needle)
fn StringIterator String.iterator(s)
fn String String.new_ascii_to_lower(s, Allocator allocator = allocator::heap())
fn String String.new_ascii_to_upper(s, Allocator allocator = allocator::heap())
fn usz! String.rindex_of(s, String needle)
fn usz! String.rindex_of_char(s, char needle)
fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = allocator::heap())
fn Splitter String.splitter(self, String split)
fn bool String.starts_with(string, String needle)
fn String String.strip(string, String needle)
fn String String.strip_end(string, String needle)
fn String String.tconcat(s1, String s2)
fn String String.tcopy(s)
fn String String.temp_ascii_to_lower(s, Allocator allocator = allocator::heap())
fn String String.temp_ascii_to_upper(s)
fn double! String.to_double(s)
fn float! String.to_float(s)
fn ichar! String.to_ichar(s, int base = 10)
fn int! String.to_int(s, int base = 10)
fn int128! String.to_int128(s, int base = 10)
macro String.to_integer(string, $Type, int base = 10)
fn long! String.to_long(s, int base = 10)
fn Char16[]! String.to_new_utf16(s, Allocator allocator = allocator::heap())
fn Char32[]! String.to_new_utf32(s, Allocator allocator = allocator::heap())
fn WString! String.to_new_wstring(s, Allocator allocator = allocator::heap())
fn short! String.to_short(s, int base = 10)
fn Char16[]! String.to_temp_utf16(s)
fn Char32[]! String.to_temp_utf32(s)
fn WString! String.to_temp_wstring(s)
fn char! String.to_uchar(s, int base = 10)
fn uint! String.to_uint(s, int base = 10)
fn uint128! String.to_uint128(s, int base = 10)
fn ulong! String.to_ulong(s, int base = 10)
fn ushort! String.to_ushort(s, int base = 10)
fn String String.trim(string, String to_trim = "\t\n\r ")
fn String[] String.tsplit(s, String needle, usz max = 0)
fn usz String.utf8_codepoints(s)
fn ZString String.zstr_copy(s, Allocator allocator = allocator::heap())
fn ZString String.zstr_tcopy(s)
fn usz ZString.char_len(str)
fn String ZString.copy(z, Allocator allocator = allocator::temp())
fn usz ZString.len(str)
fn String ZString.str_view(str)
fn String ZString.tcopy(z)
macro bool char_in_set(char c, String set)
macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
fn String join_new(String[] s, String joiner, Allocator allocator = allocator::heap())
macro String new_format(String fmt, ..., Allocator allocator = allocator::heap())
fn String! new_from_utf16(Char16[] utf16, Allocator allocator = allocator::heap())
fn String! new_from_utf32(Char32[] utf32, Allocator allocator = allocator::heap())
fn String! new_from_wstring(WString wstring, Allocator allocator = allocator::heap())
fn String! temp_from_utf16(Char16[] utf16)
fn String! temp_from_wstring(WString wstring)
macro String tformat(String fmt, ...)

std::core::string::conv

fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
fn usz! char32_to_utf8(Char32 c, char[] output)
fn usz char32_to_utf8_unsafe(Char32 c, char** output)
fn usz utf16len_for_utf32(Char32[] utf32)
fn usz utf16len_for_utf8(String utf8)
fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
fn usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
fn void utf32to8_unsafe(Char32[] utf32, char* utf8_buffer)
fn usz utf8_codepoints(String utf8)
fn Char32! utf8_to_char32(char* ptr, usz* size)
fn usz utf8len_for_utf16(Char16[] utf16)
fn usz utf8len_for_utf32(Char32[] utf32)
fn void! utf8to16_unsafe(String utf8, Char16* utf16_buffer)
fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)

std::core::string::iterator

struct StringIterator
fn Char32! StringIterator.next(&self)
fn void StringIterator.reset(&self)

std::core::types

enum TypeKind : char
fault ConversionResult
struct TypeEnum
macro bool @has_same(#a, #b, ...)
fn bool TypeKind.is_int(kind) @inline
macro any_to_int(any v, $Type)
macro bool implements_copy($Type)
macro TypeKind inner_kind($Type)
macro bool is_bool($Type)
macro bool is_comparable_value(value)
macro bool is_equatable_type($Type)
macro bool is_equatable_value(value)
macro bool is_float($Type)
macro bool is_floatlike($Type)
macro bool is_int($Type)
macro bool is_intlike($Type)
macro bool is_numerical($Type)
macro bool is_promotable_to_float($Type)
macro bool is_promotable_to_floatlike($Type)
macro bool is_same($TypeA, $TypeB)
macro bool is_same_vector_type($Type1, $Type2)
macro bool is_slice_convertable($Type)
macro bool is_subtype_of($Type, $OtherType)
macro bool is_underlying_int($Type)
macro bool is_vector($Type)
macro lower_to_atomic_compatible_type($Type)
macro bool may_load_atomic($Type)
fn bool typeid.is_subtype_of(self, typeid other)

std::core::values

macro bool @assign_to(#value1, #value2)
macro TypeKind @inner_kind(#value)
macro bool @is_bool(#value)
macro bool @is_float(#value)
macro bool @is_floatlike(#value)
macro bool @is_int(#value)
macro bool @is_promotable_to_float(#value)
macro bool @is_promotable_to_floatlike(#value)
macro bool @is_same_type(#value1, #value2)
macro bool @is_same_vector_type(#value1, #value2)
macro bool @is_vector(#value)
macro typeid @typeid(#value) @builtin
macro bool @typeis(#value, $Type) @builtin
macro TypeKind @typekind(#value) @builtin
macro promote_int(x)

std::crypto::rc4

struct Rc4
fn void Rc4.crypt(&self, char[] in, char[] out)
fn void Rc4.destroy(&self)
fn void Rc4.init(&self, char[] key)

std::encoding::base64

fault Base64Error
struct Base64Decoder
struct Base64Encoder
fn usz! Base64Decoder.decode(&self, char[] src, char[] dst)
fn usz! Base64Decoder.decode_len(&self, usz n)
fn void! Base64Decoder.init(&self, String alphabet, int padding = '=')
fn usz! Base64Encoder.encode(&self, char[] src, char[] dst)
fn usz Base64Encoder.encode_len(&self, usz n)
fn void! Base64Encoder.init(&self, String alphabet, int padding = '=')

std::encoding::csv

struct CsvReader
macro CsvReader.@each_row(self, int rows = int.max; @body(String[] row))
fn void CsvReader.init(&self, InStream stream, String separator = ",")
fn String[]! CsvReader.read_new_row(self, Allocator allocator = allocator::heap())
fn String[]! CsvReader.read_new_row_with_allocator(self, Allocator allocator = allocator::heap())
fn String[]! CsvReader.read_temp_row(self)
fn void! CsvReader.skip_row(self) @maydiscard

std::encoding::json

fault JsonParsingError
fn JsonTokenType! lex_string(JsonContext* context)
fn Object*! parse(InStream s, Allocator allocator = allocator::heap())

std::hash::adler32

struct Adler32
fn uint Adler32.final(&self)
fn void Adler32.init(&self)
fn void Adler32.update(&self, char[] data)
fn void Adler32.updatec(&self, char c)
fn uint encode(char[] data)

std::hash::crc32

struct Crc32
fn uint Crc32.final(&self)
fn void Crc32.init(&self, uint seed = 0)
fn void Crc32.update(&self, char[] data)
fn void Crc32.updatec(&self, char c)
fn uint encode(char[] data)

std::hash::crc64

struct Crc64
fn ulong Crc64.final(&self)
fn void Crc64.init(&self, uint seed = 0)
fn void Crc64.update(&self, char[] data)
fn void Crc64.updatec(&self, char c)
fn ulong encode(char[] data)

std::hash::fnv32a

distinct Fnv32a = uint;
fn void Fnv32a.init(&self)
fn void Fnv32a.update(&self, char[] data)
macro void Fnv32a.update_char(&self, char c)
fn uint encode(char[] data)

std::hash::fnv64a

distinct Fnv64a = ulong;
fn void Fnv64a.init(&self)
fn void Fnv64a.update(&self, char[] data)
macro void Fnv64a.update_char(&self, char c)
fn ulong encode(char[] data)

std::hash::sha1

struct Sha1
fn char[20] Sha1.final(&self)
fn void Sha1.init(&self)
fn void Sha1.update(&self, char[] data)

std::io

enum Seek
fault FormattingFault
fault IoError
fault PrintFault
interface InStream
interface OutStream
interface Printable
struct BitReader
struct BitWriter
struct ByteBuffer (InStream, OutStream)
struct ByteReader (InStream)
struct ByteWriter (OutStream)
struct File (InStream, OutStream)
struct Formatter
struct LimitReader (InStream)
struct ReadBuffer (InStream)
struct Scanner (InStream)
struct WriteBuffer (OutStream)
macro bool @is_instream(#expr)
macro bool @is_outstream(#expr)
macro void! @pushback_using_seek(&s)
macro char! @read_byte_using_read(&s)
macro usz! @read_using_read_byte(&s, char[] buffer)
macro void! @write_byte_using_write(&s, char c)
macro usz! @write_using_write_byte(&s, char[] bytes)
fn void BitReader.clear(&self) @inline
fn void BitReader.init(&self, InStream byte_reader)
fn char! BitReader.read_bits(&self, uint nbits)
fn void! BitWriter.flush(&self)
fn void BitWriter.init(&self, OutStream byte_writer)
fn void! BitWriter.write_bits(&self, uint bits, uint nbits)
fn usz! ByteBuffer.available(&self) @inline @dynamic
fn void ByteBuffer.free(&self)
fn void! ByteBuffer.grow(&self, usz n)
fn ByteBuffer*! ByteBuffer.init_with_buffer(&self, char[] buf)
fn ByteBuffer*! ByteBuffer.new_init(&self, usz max_read, usz initial_capacity = 16, Allocator allocator = allocator::heap())
fn void! ByteBuffer.pushback_byte(&self) @dynamic
fn usz! ByteBuffer.read(&self, char[] bytes) @dynamic
fn char! ByteBuffer.read_byte(&self) @dynamic
fn usz! ByteBuffer.seek(&self, isz offset, Seek seek) @dynamic
macro ByteBuffer.shrink(&self)
fn ByteBuffer*! ByteBuffer.temp_init(&self, usz max_read, usz initial_capacity = 16)
fn usz! ByteBuffer.write(&self, char[] bytes) @dynamic
fn void! ByteBuffer.write_byte(&self, char c) @dynamic
fn usz! ByteReader.available(&self) @inline @dynamic
fn ByteReader* ByteReader.init(&self, char[] bytes)
fn usz ByteReader.len(&self) @dynamic
fn void! ByteReader.pushback_byte(&self) @dynamic
fn usz! ByteReader.read(&self, char[] bytes) @dynamic
fn char! ByteReader.read_byte(&self) @dynamic
fn usz! ByteReader.seek(&self, isz offset, Seek seek) @dynamic
fn usz! ByteReader.write_to(&self, OutStream writer) @dynamic
fn void! ByteWriter.destroy(&self) @dynamic
fn void! ByteWriter.ensure_capacity(&self, usz len) @inline
fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
fn ByteWriter* ByteWriter.new_init(&self, Allocator allocator = allocator::heap())
fn usz! ByteWriter.read_from(&self, InStream reader) @dynamic
fn String ByteWriter.str_view(&self) @inline
fn ByteWriter* ByteWriter.temp_init(&self)
fn usz! ByteWriter.write(&self, char[] bytes) @dynamic
fn void! ByteWriter.write_byte(&self, char c) @dynamic
fn void Formatter.init(&self, OutputFn out_fn, void* data = null)
fn usz! Formatter.print(&self, String str)
fn usz! Formatter.print_with_function(&self, Printable arg)
fn usz! Formatter.printf(&self, String format, args...)
fn usz! Formatter.vprintf(&self, String format, any[] anys)
fn usz! LimitReader.available(&self) @inline @dynamic
fn void! LimitReader.close(&self) @dynamic
fn LimitReader* LimitReader.init(&self, InStream wrapped_stream, usz limit)
fn usz! LimitReader.read(&self, char[] bytes) @dynamic
fn char! LimitReader.read_byte(&self) @dynamic
fn void! ReadBuffer.close(&self) @dynamic
fn ReadBuffer* ReadBuffer.init(&self, InStream wrapped_stream, char[] bytes)
fn usz! ReadBuffer.read(&self, char[] bytes) @dynamic
fn char! ReadBuffer.read_byte(&self) @dynamic
fn String ReadBuffer.str_view(&self) @inline
fn void! Scanner.close(&self) @dynamic
fn char[] Scanner.flush(&self) @dynamic
fn void Scanner.init(&self, InStream stream, char[] buffer)
fn usz! Scanner.read(&self, char[] bytes) @dynamic
fn char! Scanner.read_byte(&self) @dynamic
fn char[]! Scanner.scan(&self, String pattern = "\n")
fn void! WriteBuffer.close(&self) @dynamic
fn void! WriteBuffer.flush(&self) @dynamic
fn WriteBuffer* WriteBuffer.init(&self, OutStream wrapped_stream, char[] bytes)
fn String WriteBuffer.str_view(&self) @inline
fn usz! WriteBuffer.write(&self, char[] bytes) @dynamic
fn void! WriteBuffer.write_byte(&self, char c) @dynamic
fn usz! available(InStream s)
fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
fn usz! copy_to(InStream in, OutStream dst, char[] buffer = {})
macro void eprint(x)
fn usz! eprintf(String format, args...) @maydiscard
fn usz! eprintfn(String format, args...) @maydiscard
macro void eprintn(x)
macro usz! fprint(out, x)
fn usz! fprintf(OutStream out, String format, args...)
fn usz! fprintfn(OutStream out, String format, args...) @maydiscard
macro usz! fprintn(out, x = "")
macro void print(x)
fn usz! printf(String format, args...) @maydiscard
fn usz! printfn(String format, args...) @maydiscard
macro void printn(x = "")
macro usz! read_all(stream, char[] buffer)
macro usz! read_any(stream, any ref)
macro usz! read_varint(stream, x_ptr)
macro String! readline(stream = io::stdin(), Allocator allocator = allocator::heap())
macro String! treadline(stream = io::stdin())
macro usz! write_all(stream, char[] buffer)
macro usz! write_any(stream, any ref)
macro usz! write_varint(stream, x)

std::io @if (env::LIBC)

fn void putchar(char c) @inline
fn File* stderr()
fn File* stdin()
fn File* stdout()

std::io @if(!env::LIBC)

fn void putchar(char c) @inline
fn File* stderr()
fn File* stdin()
fn File* stdout()

std::io::file

fn void! File.close(&self) @inline @dynamic
fn bool File.eof(&self) @inline
fn void! File.flush(&self) @dynamic
fn void! File.memopen(File* file, char[] data, String mode)
fn usz! File.read(&self, char[] buffer) @dynamic
fn char! File.read_byte(&self) @dynamic
fn void! File.reopen(&self, String filename, String mode)
fn usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
fn usz! File.write(&self, char[] buffer) @dynamic
fn void! File.write_byte(&self, char c) @dynamic
fn void! delete(String filename)
fn File from_handle(CFile file)
fn usz! get_size(String path)
fn bool is_file(String path)
fn char[]! load_buffer(String filename, char[] buffer)
fn char[]! load_new(String filename, Allocator allocator = allocator::heap())
fn char[]! load_temp(String filename)
fn File! open(String filename, String mode)
fn File! open_path(Path path, String mode)

std::io::os

macro String! getcwd(Allocator allocator = allocator::heap())
macro void! native_chdir(Path path)
fn bool native_file_or_dir_exists(String path)
fn usz! native_file_size(String path) @if(!env::WIN32 && !env::DARWIN)
fn usz! native_file_size(String path) @if(env::DARWIN)
fn usz! native_file_size(String path) @if(env::WIN32)
fn bool native_is_dir(String path)
fn bool native_is_file(String path)
macro bool! native_mkdir(Path path, MkdirPermissions permissions)
macro bool! native_rmdir(Path path)
fn void! native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX)

std::io::os @if(env::LIBC)

fn void*! native_fopen(String filename, String mode) @inline
fn usz! native_fread(CFile file, char[] buffer) @inline
fn void*! native_freopen(void* file, String filename, String mode) @inline
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
fn usz! native_ftell(CFile file) @inline
fn usz! native_fwrite(CFile file, char[] buffer) @inline
fn void! native_remove(String filename)
fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(!env::WIN32)
fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(env::WIN32)

std::io::os @if(env::NO_LIBC)

fn void*! native_fopen(String filename, String mode) @inline
fn usz! native_fread(CFile file, char[] buffer) @inline
fn void*! native_freopen(void* file, String filename, String mode) @inline
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
fn usz! native_ftell(CFile file) @inline
fn usz! native_fwrite(CFile file, char[] buffer) @inline
fn void! native_remove(String filename) @inline
macro Path! native_temp_directory(Allocator allocator = allocator::heap())

std::io::os @if(env::POSIX)

fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
fn void! native_rmtree(Path dir)

std::io::os @if(env::WIN32)

fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
fn void! native_rmtree(Path path)

std::io::path

enum MkdirPermissions
enum PathEnv
fault PathResult
struct Path (Printable)
fn Path! Path.absolute(self, Allocator allocator = allocator::heap())
fn Path! Path.append(self, String filename, Allocator allocator = allocator::heap())
fn ZString Path.as_zstr(self)
fn String Path.basename(self)
fn String Path.dirname(self)
fn bool Path.equals(self, Path p2)
fn String! Path.extension(self)
fn void Path.free(self)
fn bool Path.has_suffix(self, String str)
fn bool! Path.is_absolute(self)
fn Path! Path.parent(self)
fn String Path.root_directory(self)
fn String Path.str_view(self) @inline
fn Path! Path.tappend(self, String filename)
fn usz! Path.to_format(&self, Formatter* formatter) @dynamic
fn String Path.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String Path.volume_name(self)
fn bool! Path.walk(self, PathWalker w, void* data)
fn void! chdir(Path path)
fn void! delete(Path path)
fn bool exists(Path path)
fn usz! file_size(Path path)
fn Path! getcwd(Allocator allocator = allocator::heap())
fn bool is_dir(Path path)
fn bool is_file(Path path)
macro bool is_posix_separator(char c)
macro bool is_reserved_path_char(char c, PathEnv path_env = DEFAULT_PATH_ENV)
macro bool is_reserved_win32_path_char(char c)
macro bool is_separator(char c, PathEnv path_env = DEFAULT_PATH_ENV)
macro bool is_win32_separator(char c)
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap())
fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
fn Path! new(String path, Allocator allocator = allocator::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
fn Path! new_posix(String path, Allocator allocator = allocator::heap())
fn Path! new_win32_wstring(WString path, Allocator allocator = allocator::heap())
fn Path! new_windows(String path, Allocator allocator = allocator::heap())
fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
fn bool! rmdir(Path path)
fn void! rmtree(Path path)
fn Path! temp_directory(Allocator allocator = allocator::heap())
fn Path! temp_new(String path, PathEnv path_env = DEFAULT_PATH_ENV)
fn Path! tgetcwd()

std::math

enum RoundingMode : int
fault MathError
fault MatrixError
fn int128 __ashlti3(int128 a, uint b) @extern("__ashlti3") @weak @nostrip
fn int128 __ashrti3(int128 a, uint b) @extern("__ashrti3") @weak @nostrip
fn int128 __divti3(int128 a, int128 b) @extern("__divti3") @weak @nostrip
fn int128 __fixdfti(double a) @weak @extern("__fixdfti") @nostrip
fn int128 __fixsfti(float a) @weak @extern("__fixsfti") @nostrip
fn uint128 __fixunsdfti(double a) @weak @extern("__fixunsdfti") @nostrip
fn uint128 __fixunssfti(float a) @weak @extern("__fixunssfti") @nostrip
fn double __floattidf(int128 a) @extern("__floattidf") @weak @nostrip
fn float __floattisf(int128 a) @extern("__floattisf") @weak @nostrip
fn double __floatuntidf(uint128 a) @extern("__floatuntidf") @weak @nostrip
fn float __floatuntisf(uint128 a) @extern("__floatuntisf") @weak @nostrip
fn uint128 __lshrti3(uint128 a, uint b) @extern("__lshrti3") @weak @nostrip
fn int128 __modti3(int128 a, int128 b) @extern("__modti3") @weak @nostrip
fn int128 __multi3(int128 a, int128 b) @extern("__multi3") @weak @nostrip
fn double __roundeven(double d) @extern("roundeven") @weak @nostrip
fn float __roundevenf(float f) @extern("roundevenf") @weak @nostrip
fn uint128 __udivti3(uint128 n, uint128 d) @extern("__udivti3") @weak @nostrip
fn uint128 __umodti3(uint128 n, uint128 d) @extern("__umodti3") @weak @nostrip
fn double _frexp(double x, int* e)
fn float _frexpf(float x, int* e)
macro abs(x)
macro acos(x)
macro acosh(x)
macro asin(x)
macro asinh(x)
macro atan(x)
macro atan2(x, y)
macro atanh(x)
macro bool bool[<*>].and(bool[<*>] x)
macro bool[<*>] bool[<*>].comp_eq(bool[<*>] x, bool[<*>] y)
macro bool[<*>] bool[<*>].comp_ge(bool[<*>] x, bool[<*>] y)
macro bool[<*>] bool[<*>].comp_gt(bool[<*>] x, bool[<*>] y)
macro bool[<*>] bool[<*>].comp_le(bool[<*>] x, bool[<*>] y)
macro bool[<*>] bool[<*>].comp_lt(bool[<*>] x, bool[<*>] y)
macro bool[<*>] bool[<*>].comp_ne(bool[<*>] x, bool[<*>] y)
macro bool bool[<*>].max(bool[<*>] x)
macro bool bool[<*>].min(bool[<*>] x)
macro bool bool[<*>].or(bool[<*>] x)
macro bool bool[<*>].product(bool[<*>] x)
macro bool bool[<*>].sum(bool[<*>] x)
macro bool bool[<*>].xor(bool[<*>] x)
macro ceil(x)
macro bool char.is_even(char x)
macro bool char.is_odd(char x)
macro char! char.overflow_add(char x, char y)
macro char! char.overflow_mul(char x, char y)
macro char! char.overflow_sub(char x, char y)
macro char char.sat_add(char x, char y)
macro char char.sat_mul(char x, char y)
macro char char.sat_shl(char x, char y)
macro char char.sat_sub(char x, char y)
macro char char[<*>].and(char[<*>] x)
macro bool[<*>] char[<*>].comp_eq(char[<*>] x, char[<*>] y)
macro bool[<*>] char[<*>].comp_ge(char[<*>] x, char[<*>] y)
macro bool[<*>] char[<*>].comp_gt(char[<*>] x, char[<*>] y)
macro bool[<*>] char[<*>].comp_le(char[<*>] x, char[<*>] y)
macro bool[<*>] char[<*>].comp_lt(char[<*>] x, char[<*>] y)
macro bool[<*>] char[<*>].comp_ne(char[<*>] x, char[<*>] y)
macro char char[<*>].max(char[<*>] x)
macro char char[<*>].min(char[<*>] x)
macro char char[<*>].or(char[<*>] x)
macro char char[<*>].product(char[<*>] x)
macro char char[<*>].sum(char[<*>] x)
macro char char[<*>].xor(char[<*>] x)
macro clamp(x, lower, upper)
macro copysign(mag, sgn)
macro cos(x)
macro cosec(x)
macro cosech(x)
macro cosh(x)
macro cotan(x)
macro cotanh(x)
macro deg_to_rad(x)
macro double double.ceil(double x)
macro double double.clamp(double x, double lower, double upper)
macro double double.copysign(double mag, double sgn)
macro double double.floor(double x)
macro double double.fma(double a, double b, double c)
macro uint double.high_word(double d)
macro uint double.low_word(double d)
macro double double.muladd(double a, double b, double c)
macro double double.nearbyint(double x)
macro double double.pow(double x, exp)
macro double double.rint(double x)
macro double double.round(double x)
macro double double.roundeven(double x)
macro double double.trunc(double x)
macro double[<*>] double[<*>].ceil(double[<*>] x)
macro double[<*>] double[<*>].clamp(double[<*>] x, double[<*>] lower, double[<*>] upper)
macro bool[<*>] double[<*>].comp_eq(double[<*>] x, double[<*>] y)
macro bool[<*>] double[<*>].comp_ge(double[<*>] x, double[<*>] y)
macro bool[<*>] double[<*>].comp_gt(double[<*>] x, double[<*>] y)
macro bool[<*>] double[<*>].comp_le(double[<*>] x, double[<*>] y)
macro bool[<*>] double[<*>].comp_lt(double[<*>] x, double[<*>] y)
macro bool[<*>] double[<*>].comp_ne(double[<*>] x, double[<*>] y)
macro double[<*>] double[<*>].copysign(double[<*>] mag, double[<*>] sgn)
macro double double[<*>].distance(double[<*>] x, double[<*>] y)
macro double double[<*>].dot(double[<*>] x, double[<*>] y)
macro bool double[<*>].equals(double[<*>] x, double[<*>] y)
macro double[<*>] double[<*>].floor(double[<*>] x)
macro double[<*>] double[<*>].fma(double[<*>] a, double[<*>] b, double[<*>] c)
macro double double[<*>].length(double[<*>] x)
macro double[<*>] double[<*>].lerp(double[<*>] x, double[<*>] y, double amount)
macro double double[<*>].max(double[<*>] x)
macro double double[<*>].min(double[<*>] x)
macro double[<*>] double[<*>].nearbyint(double[<*>] x)
macro double[<*>] double[<*>].normalize(double[<*>] x)
macro double[<*>] double[<*>].pow(double[<*>] x, exp)
macro double double[<*>].product(double[<*>] x, double start = 1.0)
macro double[<*>] double[<*>].reflect(double[<*>] x, double[<*>] y)
macro double[<*>] double[<*>].rint(double[<*>] x)
macro double[<*>] double[<*>].round(double[<*>] x)
macro double[<*>] double[<*>].roundeven(double[<*>] x)
macro double double[<*>].sum(double[<*>] x, double start = 0.0)
macro double[<*>] double[<*>].trunc(double[<*>] x)
macro exp(x)
macro exp2(x)
macro float float.ceil(float x)
macro float float.clamp(float x, float lower, float upper)
macro float float.copysign(float mag, float sgn)
macro float float.floor(float x)
macro float float.fma(float a, float b, float c)
macro float float.muladd(float a, float b, float c)
macro float float.nearbyint(float x)
macro float float.pow(float x, exp)
macro float float.rint(float x)
macro float float.round(float x)
macro float float.roundeven(float x)
macro float float.trunc(float x)
macro uint float.word(float d)
macro float[<*>] float[<*>].ceil(float[<*>] x)
macro float[<*>] float[<*>].clamp(float[<*>] x, float[<*>] lower, float[<*>] upper)
macro bool[<*>] float[<*>].comp_eq(float[<*>] x, float[<*>] y)
macro bool[<*>] float[<*>].comp_ge(float[<*>] x, float[<*>] y)
macro bool[<*>] float[<*>].comp_gt(float[<*>] x, float[<*>] y)
macro bool[<*>] float[<*>].comp_le(float[<*>] x, float[<*>] y)
macro bool[<*>] float[<*>].comp_lt(float[<*>] x, float[<*>] y)
macro bool[<*>] float[<*>].comp_ne(float[<*>] x, float[<*>] y)
macro float[<*>] float[<*>].copysign(float[<*>] mag, float[<*>] sgn)
macro float float[<*>].distance(float[<*>] x, float[<*>] y)
macro float float[<*>].dot(float[<*>] x, float[<*>] y)
macro bool float[<*>].equals(float[<*>] x, float[<*>] y)
macro float[<*>] float[<*>].floor(float[<*>] x)
macro float[<*>] float[<*>].fma(float[<*>] a, float[<*>] b, float[<*>] c)
macro float float[<*>].length(float[<*>] x)
macro float[<*>] float[<*>].lerp(float[<*>] x, float[<*>] y, float amount)
macro float float[<*>].max(float[<*>] x)
macro float float[<*>].min(float[<*>] x)
macro float[<*>] float[<*>].nearbyint(float[<*>] x)
macro float[<*>] float[<*>].normalize(float[<*>] x)
macro float[<*>] float[<*>].pow(float[<*>] x, exp)
macro float float[<*>].product(float[<*>] x, float start = 1.0)
macro float[<*>] float[<*>].reflect(float[<*>] x, float[<*>] y)
macro float[<*>] float[<*>].rint(float[<*>] x)
macro float[<*>] float[<*>].round(float[<*>] x)
macro float[<*>] float[<*>].roundeven(float[<*>] x)
macro float float[<*>].sum(float[<*>] x, float start = 0.0)
macro float[<*>] float[<*>].trunc(float[<*>] x)
macro floor(x)
macro fma(a, b, c)
macro frexp(x, int* e)
macro hypot(x, y)
macro bool ichar.is_even(ichar x)
macro bool ichar.is_odd(ichar x)
macro ichar! ichar.overflow_add(ichar x, ichar y)
macro ichar! ichar.overflow_mul(ichar x, ichar y)
macro ichar! ichar.overflow_sub(ichar x, ichar y)
macro ichar ichar.sat_add(ichar x, ichar y)
macro ichar ichar.sat_mul(ichar x, ichar y)
macro ichar ichar.sat_shl(ichar x, ichar y)
macro ichar ichar.sat_sub(ichar x, ichar y)
macro ichar ichar[<*>].and(ichar[<*>] x)
macro bool[<*>] ichar[<*>].comp_eq(ichar[<*>] x, ichar[<*>] y)
macro bool[<*>] ichar[<*>].comp_ge(ichar[<*>] x, ichar[<*>] y)
macro bool[<*>] ichar[<*>].comp_gt(ichar[<*>] x, ichar[<*>] y)
macro bool[<*>] ichar[<*>].comp_le(ichar[<*>] x, ichar[<*>] y)
macro bool[<*>] ichar[<*>].comp_lt(ichar[<*>] x, ichar[<*>] y)
macro bool[<*>] ichar[<*>].comp_ne(ichar[<*>] x, ichar[<*>] y)
macro ichar ichar[<*>].max(ichar[<*>] x)
macro ichar ichar[<*>].min(ichar[<*>] x)
macro ichar ichar[<*>].or(ichar[<*>] x)
macro ichar ichar[<*>].product(ichar[<*>] x)
macro ichar ichar[<*>].sum(ichar[<*>] x)
macro ichar ichar[<*>].xor(ichar[<*>] x)
macro bool int.is_even(int x)
macro bool int.is_odd(int x)
macro int! int.overflow_add(int x, int y)
macro int! int.overflow_mul(int x, int y)
macro int! int.overflow_sub(int x, int y)
macro int int.sat_add(int x, int y)
macro int int.sat_mul(int x, int y)
macro int int.sat_shl(int x, int y)
macro int int.sat_sub(int x, int y)
macro bool int128.is_even(int128 x)
macro bool int128.is_odd(int128 x)
macro int128! int128.overflow_add(int128 x, int128 y)
macro int128! int128.overflow_mul(int128 x, int128 y)
macro int128! int128.overflow_sub(int128 x, int128 y)
macro int128 int128.sat_add(int128 x, int128 y)
macro int128 int128.sat_mul(int128 x, int128 y)
macro int128 int128.sat_shl(int128 x, int128 y)
macro int128 int128.sat_sub(int128 x, int128 y)
macro int128 int128[<*>].and(int128[<*>] x)
macro bool[<*>] int128[<*>].comp_eq(int128[<*>] x, int128[<*>] y)
macro bool[<*>] int128[<*>].comp_ge(int128[<*>] x, int128[<*>] y)
macro bool[<*>] int128[<*>].comp_gt(int128[<*>] x, int128[<*>] y)
macro bool[<*>] int128[<*>].comp_le(int128[<*>] x, int128[<*>] y)
macro bool[<*>] int128[<*>].comp_lt(int128[<*>] x, int128[<*>] y)
macro bool[<*>] int128[<*>].comp_ne(int128[<*>] x, int128[<*>] y)
macro int128 int128[<*>].max(int128[<*>] x)
macro int128 int128[<*>].min(int128[<*>] x)
macro int128 int128[<*>].or(int128[<*>] x)
macro int128 int128[<*>].product(int128[<*>] x)
macro int128 int128[<*>].sum(int128[<*>] x)
macro int128 int128[<*>].xor(int128[<*>] x)
macro int int[<*>].and(int[<*>] x)
macro bool[<*>] int[<*>].comp_eq(int[<*>] x, int[<*>] y)
macro bool[<*>] int[<*>].comp_ge(int[<*>] x, int[<*>] y)
macro bool[<*>] int[<*>].comp_gt(int[<*>] x, int[<*>] y)
macro bool[<*>] int[<*>].comp_le(int[<*>] x, int[<*>] y)
macro bool[<*>] int[<*>].comp_lt(int[<*>] x, int[<*>] y)
macro bool[<*>] int[<*>].comp_ne(int[<*>] x, int[<*>] y)
macro int int[<*>].max(int[<*>] x)
macro int int[<*>].min(int[<*>] x)
macro int int[<*>].or(int[<*>] x)
macro int int[<*>].product(int[<*>] x)
macro int int[<*>].sum(int[<*>] x)
macro int int[<*>].xor(int[<*>] x)
macro bool is_even(x)
macro bool is_finite(x)
macro is_inf(x)
macro is_nan(x)
macro bool is_odd(x)
macro bool is_power_of_2(x)
macro ln(x)
macro log(x, base)
macro log10(x)
macro log2(x)
macro bool long.is_even(long x)
macro bool long.is_odd(long x)
macro long! long.overflow_add(long x, long y)
macro long! long.overflow_mul(long x, long y)
macro long! long.overflow_sub(long x, long y)
macro long long.sat_add(long x, long y)
macro long long.sat_mul(long x, long y)
macro long long.sat_shl(long x, long y)
macro long long.sat_sub(long x, long y)
macro long long[<*>].and(long[<*>] x)
macro bool[<*>] long[<*>].comp_eq(long[<*>] x, long[<*>] y)
macro bool[<*>] long[<*>].comp_ge(long[<*>] x, long[<*>] y)
macro bool[<*>] long[<*>].comp_gt(long[<*>] x, long[<*>] y)
macro bool[<*>] long[<*>].comp_le(long[<*>] x, long[<*>] y)
macro bool[<*>] long[<*>].comp_lt(long[<*>] x, long[<*>] y)
macro bool[<*>] long[<*>].comp_ne(long[<*>] x, long[<*>] y)
macro long long[<*>].max(long[<*>] x)
macro long long[<*>].min(long[<*>] x)
macro long long[<*>].or(long[<*>] x)
macro long long[<*>].product(long[<*>] x)
macro long long[<*>].sum(long[<*>] x)
macro long long[<*>].xor(long[<*>] x)
macro max(x, y, ...)
macro min(x, y, ...)
macro muladd(a, b, c)
macro nearbyint(x)
macro next_power_of_2(x)
macro pow(x, exp)
macro rint(x)
macro round(x)
macro round_to_decimals(x, int decimal_places)
macro roundeven(x)
macro double scalbn(double x, int n)
macro sec(x)
macro sech(x)
macro select(bool[<*>] mask, then_value, else_value)
macro bool short.is_even(short x)
macro bool short.is_odd(short x)
macro short! short.overflow_add(short x, short y)
macro short! short.overflow_mul(short x, short y)
macro short! short.overflow_sub(short x, short y)
macro short short.sat_add(short x, short y)
macro short short.sat_mul(short x, short y)
macro short short.sat_shl(short x, short y)
macro short short.sat_sub(short x, short y)
macro short short[<*>].and(short[<*>] x)
macro bool[<*>] short[<*>].comp_eq(short[<*>] x, short[<*>] y)
macro bool[<*>] short[<*>].comp_ge(short[<*>] x, short[<*>] y)
macro bool[<*>] short[<*>].comp_gt(short[<*>] x, short[<*>] y)
macro bool[<*>] short[<*>].comp_le(short[<*>] x, short[<*>] y)
macro bool[<*>] short[<*>].comp_lt(short[<*>] x, short[<*>] y)
macro bool[<*>] short[<*>].comp_ne(short[<*>] x, short[<*>] y)
macro short short[<*>].max(short[<*>] x)
macro short short[<*>].min(short[<*>] x)
macro short short[<*>].or(short[<*>] x)
macro short short[<*>].product(short[<*>] x)
macro short short[<*>].sum(short[<*>] x)
macro short short[<*>].xor(short[<*>] x)
macro sign(x)
macro int signbit(x)
macro sin(x)
macro sincos(x, y)
macro sinh(x)
macro sqr(x)
macro sqrt(x)
macro tan(x)
macro tanh(x)
macro trunc(x)
macro bool uint.is_even(uint x)
macro bool uint.is_odd(uint x)
macro uint! uint.overflow_add(uint x, uint y)
macro uint! uint.overflow_mul(uint x, uint y)
macro uint! uint.overflow_sub(uint x, uint y)
macro uint uint.sat_add(uint x, uint y)
macro uint uint.sat_mul(uint x, uint y)
macro uint uint.sat_shl(uint x, uint y)
macro uint uint.sat_sub(uint x, uint y)
macro bool uint128.is_even(uint128 x)
macro bool uint128.is_odd(uint128 x)
macro uint128! uint128.overflow_add(uint128 x, uint128 y)
macro uint128! uint128.overflow_mul(uint128 x, uint128 y)
macro uint128! uint128.overflow_sub(uint128 x, uint128 y)
macro uint128 uint128.sat_add(uint128 x, uint128 y)
macro uint128 uint128.sat_mul(uint128 x, uint128 y)
macro uint128 uint128.sat_shl(uint128 x, uint128 y)
macro uint128 uint128.sat_sub(uint128 x, uint128 y)
macro uint128 uint128[<*>].and(uint128[<*>] x)
macro bool[<*>] uint128[<*>].comp_eq(uint128[<*>] x, uint128[<*>] y)
macro bool[<*>] uint128[<*>].comp_ge(uint128[<*>] x, uint128[<*>] y)
macro bool[<*>] uint128[<*>].comp_gt(uint128[<*>] x, uint128[<*>] y)
macro bool[<*>] uint128[<*>].comp_le(uint128[<*>] x, uint128[<*>] y)
macro bool[<*>] uint128[<*>].comp_lt(uint128[<*>] x, uint128[<*>] y)
macro bool[<*>] uint128[<*>].comp_ne(uint128[<*>] x, uint128[<*>] y)
macro uint128 uint128[<*>].max(uint128[<*>] x)
macro uint128 uint128[<*>].min(uint128[<*>] x)
macro uint128 uint128[<*>].or(uint128[<*>] x)
macro uint128 uint128[<*>].product(uint128[<*>] x)
macro uint128 uint128[<*>].sum(uint128[<*>] x)
macro uint128 uint128[<*>].xor(uint128[<*>] x)
macro uint uint[<*>].and(uint[<*>] x)
macro bool[<*>] uint[<*>].comp_eq(uint[<*>] x, uint[<*>] y)
macro bool[<*>] uint[<*>].comp_ge(uint[<*>] x, uint[<*>] y)
macro bool[<*>] uint[<*>].comp_gt(uint[<*>] x, uint[<*>] y)
macro bool[<*>] uint[<*>].comp_le(uint[<*>] x, uint[<*>] y)
macro bool[<*>] uint[<*>].comp_lt(uint[<*>] x, uint[<*>] y)
macro bool[<*>] uint[<*>].comp_ne(uint[<*>] x, uint[<*>] y)
macro uint uint[<*>].max(uint[<*>] x)
macro uint uint[<*>].min(uint[<*>] x)
macro uint uint[<*>].or(uint[<*>] x)
macro uint uint[<*>].product(uint[<*>] x)
macro uint uint[<*>].sum(uint[<*>] x)
macro uint uint[<*>].xor(uint[<*>] x)
macro bool ulong.is_even(ulong x)
macro bool ulong.is_odd(ulong x)
macro ulong! ulong.overflow_add(ulong x, ulong y)
macro ulong! ulong.overflow_mul(ulong x, ulong y)
macro ulong! ulong.overflow_sub(ulong x, ulong y)
macro ulong ulong.sat_add(ulong x, ulong y)
macro ulong ulong.sat_mul(ulong x, ulong y)
macro ulong ulong.sat_shl(ulong x, ulong y)
macro ulong ulong.sat_sub(ulong x, ulong y)
macro ulong ulong[<*>].and(ulong[<*>] x)
macro bool[<*>] ulong[<*>].comp_eq(ulong[<*>] x, ulong[<*>] y)
macro bool[<*>] ulong[<*>].comp_ge(ulong[<*>] x, ulong[<*>] y)
macro bool[<*>] ulong[<*>].comp_gt(ulong[<*>] x, ulong[<*>] y)
macro bool[<*>] ulong[<*>].comp_le(ulong[<*>] x, ulong[<*>] y)
macro bool[<*>] ulong[<*>].comp_lt(ulong[<*>] x, ulong[<*>] y)
macro bool[<*>] ulong[<*>].comp_ne(ulong[<*>] x, ulong[<*>] y)
macro ulong ulong[<*>].max(ulong[<*>] x)
macro ulong ulong[<*>].min(ulong[<*>] x)
macro ulong ulong[<*>].or(ulong[<*>] x)
macro ulong ulong[<*>].product(ulong[<*>] x)
macro ulong ulong[<*>].sum(ulong[<*>] x)
macro ulong ulong[<*>].xor(ulong[<*>] x)
macro bool ushort.is_even(ushort x)
macro bool ushort.is_odd(ushort x)
macro ushort! ushort.overflow_add(ushort x, ushort y)
macro ushort! ushort.overflow_mul(ushort x, ushort y)
macro ushort! ushort.overflow_sub(ushort x, ushort y)
macro ushort ushort.sat_add(ushort x, ushort y)
macro ushort ushort.sat_mul(ushort x, ushort y)
macro ushort ushort.sat_shl(ushort x, ushort y)
macro ushort ushort.sat_sub(ushort x, ushort y)
macro ushort ushort[<*>].and(ushort[<*>] x)
macro bool[<*>] ushort[<*>].comp_eq(ushort[<*>] x, ushort[<*>] y)
macro bool[<*>] ushort[<*>].comp_ge(ushort[<*>] x, ushort[<*>] y)
macro bool[<*>] ushort[<*>].comp_gt(ushort[<*>] x, ushort[<*>] y)
macro bool[<*>] ushort[<*>].comp_le(ushort[<*>] x, ushort[<*>] y)
macro bool[<*>] ushort[<*>].comp_lt(ushort[<*>] x, ushort[<*>] y)
macro bool[<*>] ushort[<*>].comp_ne(ushort[<*>] x, ushort[<*>] y)
macro ushort ushort[<*>].max(ushort[<*>] x)
macro ushort ushort[<*>].min(ushort[<*>] x)
macro ushort ushort[<*>].or(ushort[<*>] x)
macro ushort ushort[<*>].product(ushort[<*>] x)
macro ushort ushort[<*>].sum(ushort[<*>] x)
macro ushort ushort[<*>].xor(ushort[<*>] x)

std::math::complex(<Real>)

macro Complex Complex.add(self, Complex b)
macro Complex Complex.add_each(self, Real b)
macro Complex Complex.div(self, Complex b)
macro Complex Complex.mul(self, Complex b)
macro Complex Complex.scale(self, Real s)
macro Complex Complex.sub(self, Complex b)
macro Complex Complex.sub_each(self, Real b)

std::math::easing

fn float back_in(float t, float b, float c, float d, float s = 1.70158f) @inline
fn float back_inout(float t, float b, float c, float d, float s = 1.70158f) @inline
fn float back_out(float t, float b, float c, float d, float s = 1.70158f) @inline
fn float bounce_in(float t, float b, float c, float d) @inline
fn float bounce_inout(float t, float b, float c, float d) @inline
fn float bounce_out(float t, float b, float c, float d) @inline
fn float circ_in(float t, float b, float c, float d) @inline
fn float circ_inout(float t, float b, float c, float d) @inline
fn float circ_out(float t, float b, float c, float d) @inline
fn float cubic_in(float t, float b, float c, float d) @inline
fn float cubic_inout(float t, float b, float c, float d) @inline
fn float cubic_out(float t, float b, float c, float d) @inline
fn float elastic_in(float t, float b, float c, float d) @inline
fn float elastic_inout(float t, float b, float c, float d) @inline
fn float elastic_out(float t, float b, float c, float d) @inline
fn float expo_in(float t, float b, float c, float d) @inline
fn float expo_inout(float t, float b, float c, float d) @inline
fn float expo_out(float t, float b, float c, float d) @inline
fn float linear_in(float t, float b, float c, float d) @inline
fn float linear_inout(float t, float b, float c, float d) @inline
fn float linear_none(float t, float b, float c, float d) @inline
fn float linear_out(float t, float b, float c, float d) @inline
fn float quad_in(float t, float b, float c, float d) @inline
fn float quad_inout(float t, float b, float c, float d) @inline
fn float quad_out(float t, float b, float c, float d) @inline
fn float sine_in(float t, float b, float c, float d) @inline
fn float sine_inout(float t, float b, float c, float d) @inline
fn float sine_out(float t, float b, float c, float d) @inline

std::math::matrix(<Real>)

struct Matrix2x2
struct Matrix3x3
struct Matrix4x4
fn Matrix2x2 Matrix2x2.add(&self, Matrix2x2 mat2)
fn Matrix2x2 Matrix2x2.adjoint(&self)
fn Real[<2>] Matrix2x2.apply(&self, Real[<2>] vec)
fn Matrix2x2 Matrix2x2.component_mul(&self, Real s)
fn Real Matrix2x2.determinant(&self)
fn Matrix2x2! Matrix2x2.inverse(&self)
fn Matrix2x2 Matrix2x2.mul(&self, Matrix2x2 b)
fn Matrix2x2 Matrix2x2.sub(&self, Matrix2x2 mat2)
fn Real Matrix2x2.trace(&self)
fn Matrix2x2 Matrix2x2.transpose(&self)
fn Matrix3x3 Matrix3x3.add(&self, Matrix3x3 mat2)
fn Matrix3x3 Matrix3x3.adjoint(&self)
fn Real[<3>] Matrix3x3.apply(&self, Real[<3>] vec)
fn Matrix3x3 Matrix3x3.component_mul(&self, Real s)
fn Real Matrix3x3.determinant(&self)
fn Matrix3x3! Matrix3x3.inverse(&self)
fn Matrix3x3 Matrix3x3.mul(&self, Matrix3x3 b)
fn Matrix3x3 Matrix3x3.rotate(&self, Real r)
fn Matrix3x3 Matrix3x3.scale(&self, Real[<2>] v)
fn Matrix3x3 Matrix3x3.sub(&self, Matrix3x3 mat2)
fn Real Matrix3x3.trace(&self)
fn Matrix3x3 Matrix3x3.translate(&self, Real[<2>] v)
fn Matrix3x3 Matrix3x3.transpose(&self)
fn Matrix4x4 Matrix4x4.add(&self, Matrix4x4 mat2)
fn Matrix4x4 Matrix4x4.adjoint(&self)
fn Real[<4>] Matrix4x4.apply(&self, Real[<4>] vec)
fn Matrix4x4 Matrix4x4.component_mul(&self, Real s)
fn Real Matrix4x4.determinant(&self)
fn Matrix4x4! Matrix4x4.inverse(&self)
fn Matrix4x4 Matrix4x4.mul(Matrix4x4* a, Matrix4x4 b)
fn Matrix4x4 Matrix4x4.rotate_x(&self, Real r)
fn Matrix4x4 Matrix4x4.rotate_y(&self, Real r)
fn Matrix4x4 Matrix4x4.rotate_z(&self, Real r)
fn Matrix4x4 Matrix4x4.scale(&self, Real[<3>] v)
fn Matrix4x4 Matrix4x4.sub(&self, Matrix4x4 mat2)
fn Real Matrix4x4.trace(&self)
fn Matrix4x4 Matrix4x4.translate(&self, Real[<3>] v)
fn Matrix4x4 Matrix4x4.transpose(&self)
fn Matrix4x4 ortho(Real left, Real right, Real top, Real bottom, Real near, Real far)
fn Matrix4x4 perspective(Real fov, Real aspect_ratio, Real near, Real far)

std::math::nolibc

macro double __math_oflow(ulong sign)
macro float __math_oflowf(uint sign)
macro double __math_uflow(ulong sign)
macro float __math_uflowf(uint sign)
macro __math_xflow(sign, v)
macro force_eval_add(x, v)

std::math::nolibc @if(env::NO_LIBC)

fn double __cos(double x, double y) @extern("__cos") @weak @nostrip
fn float __cosdf(double x) @extern("__cosdf") @weak @nostrip
fn int __rem_pio2(double x, double *y)
fn int __rem_pio2_large(double* x, double* y, int e0, int nx, int prec)
fn int __rem_pio2f(float x, double *y)
fn double __sin(double x, double y, int iy) @extern("__sin") @weak @nostrip
fn float __sindf(double x) @extern("__sindf") @weak @nostrip
fn double __tan(double x, double y, int odd) @extern("__tan") @weak @nostrip
fn float __tandf(double x, int odd) @extern("__tandf") @weak @nostrip
fn double _atan(double x) @weak @extern("atan") @nostrip
fn double _atan2(double y, double x) @weak @extern("atan2") @nostrip
fn float _atan2f(float y, float x) @weak @extern("atan2f") @nostrip
fn float _atanf(float x) @weak @extern("atanf") @nostrip
fn double _ceil(double x) @weak @extern("ceil") @nostrip
fn float _ceilf(float x) @weak @extern("ceilf") @nostrip
fn double _cos(double x) @weak @nostrip
fn float _cosf(float x) @extern("cosf") @weak @nostrip
fn double _exp2(double x) @extern("exp2") @weak @nostrip
fn float _exp2f(float x) @extern("exp2f") @weak @nostrip
fn double _floor(double x) @weak @extern("floor") @nostrip
fn float _floorf(float x) @weak @extern("floorf") @nostrip
fn double _round(double x) @extern("round") @weak @nostrip
fn float _roundf(float x) @extern("roundf") @weak @nostrip
fn double _scalbn(double x, int n) @weak @extern("scalbn") @nostrip
fn float _sinf(float x) @weak @extern("sinf") @nostrip
fn double _trunc(double x) @weak @extern("trunc") @nostrip
fn float _truncf(float x) @weak @extern("truncf") @nostrip
fn double pow_broken(double x, double y) @extern("pow") @weak @nostrip
fn float powf_broken(float x, float f) @extern("powf") @weak @nostrip
fn double sin(double x) @extern("sin") @weak @nostrip
fn void sincos(double x, double *sin, double *cos) @extern("sincos") @weak @nostrip
fn double sincos_broken(double x) @extern("sincos") @weak @nostrip
fn void sincosf(float x, float *sin, float *cos) @extern("sincosf") @weak @nostrip
fn double tan(double x) @extern("tan") @weak @nostrip
fn float tanf(float x) @extern("tanf") @weak @nostrip

std::math::quaternion(<Real>)

macro Quaternion Quaternion.add(Quaternion a, Quaternion b)
macro Quaternion Quaternion.add_each(Quaternion a, Real b)
fn Quaternion Quaternion.invert(q)
macro Real Quaternion.length(Quaternion q)
macro Quaternion Quaternion.lerp(Quaternion q1, Quaternion q2, Real amount)
fn Quaternion Quaternion.mul(a, Quaternion b)
fn Quaternion Quaternion.nlerp(Quaternion q1, Quaternion q2, Real amount)
macro Quaternion Quaternion.normalize(Quaternion q)
macro Quaternion Quaternion.scale(Quaternion a, Real s)
fn Quaternion Quaternion.slerp(q1, Quaternion q2, Real amount)
macro Quaternion Quaternion.sub(Quaternion a, Quaternion b)
macro Quaternion Quaternion.sub_each(Quaternion a, Real b)
macro Matrix4 Quaternion.to_matrix(Quaternion* q)
macro Matrix4f Quaternion.to_matrixf(Quaternion* q)

std::math::random

distinct Lcg128Random (Random) = uint128;
distinct Lcg16Random (Random) = ushort;
distinct Lcg32Random (Random) = uint;
distinct Lcg64Random (Random) = ulong;
distinct Mcg128Random (Random) = uint128;
distinct Mcg16Random (Random) = ushort;
distinct Mcg32Random (Random) = uint;
distinct Mcg64Random (Random) = ulong;
distinct Pcg128Random (Random) = uint128;
distinct Pcg16Random (Random) = ushort;
distinct Pcg32Random (Random) = uint;
distinct Pcg64Random (Random) = ulong;
distinct Sfc128Random (Random) = uint128[4];
distinct Sfc16Random (Random) = ushort[4];
distinct Sfc32Random (Random) = uint[4];
distinct Sfc64Random (Random) = ulong[4];
distinct Sfc8Random (Random) = char[4];
distinct SimpleRandom (Random) = ulong;
interface Random
struct Msws128Random (Random)
struct Msws16Random (Random)
struct Msws32Random (Random)
struct Msws64Random (Random)
struct Msws8Random (Random)
fn void  Msws16Random.set_seed(&self, char[] input) @dynamic
fn void  Msws32Random.set_seed(&self, char[] input) @dynamic
fn void  Msws64Random.set_seed(&self, char[] input) @dynamic
fn void  Msws8Random.set_seed(&self, char[] input) @dynamic
fn void  Pcg128Random.set_seed(&self, char[] input) @dynamic
fn void  Sfc128Random.set_seed(&self, char[] input) @dynamic
fn void  Sfc16Random.set_seed(&self, char[] input) @dynamic
fn void  Sfc32Random.set_seed(&self, char[] input) @dynamic
fn void  Sfc64Random.set_seed(&self, char[] input) @dynamic
fn void  Sfc8Random.set_seed(&self, char[] input) @dynamic
fn char[8 * 4] entropy()
macro ushort @char_to_short(#function)
macro ulong @int_to_long(#function)
macro uint128 @long_to_int128(#function)
macro @random_value_to_bytes(#function, char[] bytes)
macro uint @short_to_int(#function)
fn char Lcg128Random.next_byte(&self) @dynamic
fn void Lcg128Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Lcg128Random.next_int(&self) @dynamic
fn uint128 Lcg128Random.next_int128(&self) @dynamic
fn ulong Lcg128Random.next_long(&self) @dynamic
fn ushort Lcg128Random.next_short(&self) @dynamic
fn void Lcg128Random.set_seed(&self, char[] input) @dynamic
fn char Lcg16Random.next_byte(&self) @dynamic
fn void Lcg16Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Lcg16Random.next_int(&self) @dynamic
fn uint128 Lcg16Random.next_int128(&self) @dynamic
fn ulong Lcg16Random.next_long(&self) @dynamic
fn ushort Lcg16Random.next_short(&self) @dynamic
fn void Lcg16Random.set_seed(&self, char[] seed) @dynamic
fn char Lcg32Random.next_byte(&self) @dynamic
fn void Lcg32Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Lcg32Random.next_int(&self) @dynamic
fn uint128 Lcg32Random.next_int128(&self) @dynamic
fn ulong Lcg32Random.next_long(&self) @dynamic
fn ushort Lcg32Random.next_short(&self) @dynamic
fn void Lcg32Random.set_seed(&self, char[] seed) @dynamic
fn char Lcg64Random.next_byte(&self) @dynamic
fn void Lcg64Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Lcg64Random.next_int(&self) @dynamic
fn uint128 Lcg64Random.next_int128(&self) @dynamic
fn ulong Lcg64Random.next_long(&self) @dynamic
fn ushort Lcg64Random.next_short(&self) @dynamic
fn void Lcg64Random.set_seed(&self, char[] seed) @dynamic
fn char Mcg128Random.next_byte(&self) @dynamic
fn void Mcg128Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Mcg128Random.next_int(&self) @dynamic
fn uint128 Mcg128Random.next_int128(&self) @dynamic
fn ulong Mcg128Random.next_long(&self) @dynamic
fn ushort Mcg128Random.next_short(&self) @dynamic
fn void Mcg128Random.set_seed(&self, char[] seed) @dynamic
fn char Mcg16Random.next_byte(&self) @dynamic
fn void Mcg16Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Mcg16Random.next_int(&self) @dynamic
fn uint128 Mcg16Random.next_int128(&self) @dynamic
fn ulong Mcg16Random.next_long(&self) @dynamic
fn ushort Mcg16Random.next_short(&self) @dynamic
fn void Mcg16Random.set_seed(&self, char[] seed) @dynamic
fn char Mcg32Random.next_byte(&self) @dynamic
fn void Mcg32Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Mcg32Random.next_int(&self) @dynamic
fn uint128 Mcg32Random.next_int128(&self) @dynamic
fn ulong Mcg32Random.next_long(&self) @dynamic
fn ushort Mcg32Random.next_short(&self) @dynamic
fn void Mcg32Random.set_seed(&self, char[] seed) @dynamic
fn char Mcg64Random.next_byte(&self) @dynamic
fn void Mcg64Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Mcg64Random.next_int(&self) @dynamic
fn uint128 Mcg64Random.next_int128(&self) @dynamic
fn ulong Mcg64Random.next_long(&self) @dynamic
fn ushort Mcg64Random.next_short(&self) @dynamic
fn void Mcg64Random.set_seed(&self, char[] seed) @dynamic
fn char Msws128Random.next_byte(&self) @dynamic
fn void Msws128Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Msws128Random.next_int(&self) @dynamic
fn uint128 Msws128Random.next_int128(&self) @dynamic
fn ulong Msws128Random.next_long(&self) @dynamic
fn ushort Msws128Random.next_short(&self) @dynamic
fn void Msws128Random.set_seed(&self, char[] input) @dynamic
fn char Msws16Random.next_byte(&self) @dynamic
fn void Msws16Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Msws16Random.next_int(&self) @dynamic
fn uint128 Msws16Random.next_int128(&self) @dynamic
fn ulong Msws16Random.next_long(&self) @dynamic
fn ushort Msws16Random.next_short(&self) @dynamic
fn char Msws32Random.next_byte(&self) @dynamic
fn void Msws32Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Msws32Random.next_int(&self) @dynamic
fn uint128 Msws32Random.next_int128(&self) @dynamic
fn ulong Msws32Random.next_long(&self) @dynamic
fn ushort Msws32Random.next_short(&self) @dynamic
fn char Msws64Random.next_byte(&self) @dynamic
fn void Msws64Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Msws64Random.next_int(&self) @dynamic
fn uint128 Msws64Random.next_int128(&self) @dynamic
fn ulong Msws64Random.next_long(&self) @dynamic
fn ushort Msws64Random.next_short(&self) @dynamic
fn char Msws8Random.next_byte(&self) @dynamic
fn void Msws8Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Msws8Random.next_int(&self) @dynamic
fn uint128 Msws8Random.next_int128(&self) @dynamic
fn ulong Msws8Random.next_long(&self) @dynamic
fn ushort Msws8Random.next_short(&self) @dynamic
fn char Pcg128Random.next_byte(&self) @dynamic
fn void Pcg128Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Pcg128Random.next_int(&self) @dynamic
fn uint128 Pcg128Random.next_int128(&self) @dynamic
fn ulong Pcg128Random.next_long(&self) @dynamic
fn ushort Pcg128Random.next_short(&self) @dynamic
fn char Pcg16Random.next_byte(&self) @dynamic
fn void Pcg16Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Pcg16Random.next_int(&self) @dynamic
fn uint128 Pcg16Random.next_int128(&self) @dynamic
fn ulong Pcg16Random.next_long(&self) @dynamic
fn ushort Pcg16Random.next_short(&self) @dynamic
fn void Pcg16Random.set_seed(&self, char[] input) @dynamic
fn char Pcg32Random.next_byte(&self) @dynamic
fn void Pcg32Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Pcg32Random.next_int(&self) @dynamic
fn uint128 Pcg32Random.next_int128(&self) @dynamic
fn ulong Pcg32Random.next_long(&self) @dynamic
fn ushort Pcg32Random.next_short(&self) @dynamic
fn void Pcg32Random.set_seed(&self, char[] input) @dynamic
fn char Pcg64Random.next_byte(&self) @dynamic
fn void Pcg64Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Pcg64Random.next_int(&self) @dynamic
fn uint128 Pcg64Random.next_int128(&self) @dynamic
fn ulong Pcg64Random.next_long(&self) @dynamic
fn ushort Pcg64Random.next_short(&self) @dynamic
fn void Pcg64Random.set_seed(&self, char[] input) @dynamic
fn char Sfc128Random.next_byte(&self) @dynamic
fn void Sfc128Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Sfc128Random.next_int(&self) @dynamic
fn uint128 Sfc128Random.next_int128(&self) @dynamic
fn ulong Sfc128Random.next_long(&self) @dynamic
fn ushort Sfc128Random.next_short(&self) @dynamic
fn char Sfc16Random.next_byte(&self) @dynamic
fn void Sfc16Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Sfc16Random.next_int(&self) @dynamic
fn uint128 Sfc16Random.next_int128(&self) @dynamic
fn ulong Sfc16Random.next_long(&self) @dynamic
fn ushort Sfc16Random.next_short(&seed) @dynamic
fn char Sfc32Random.next_byte(&self) @dynamic
fn void Sfc32Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Sfc32Random.next_int(&sfc) @dynamic
fn uint128 Sfc32Random.next_int128(&self) @dynamic
fn ulong Sfc32Random.next_long(&self) @dynamic
fn ushort Sfc32Random.next_short(&self) @dynamic
fn char Sfc64Random.next_byte(&self) @dynamic
fn void Sfc64Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Sfc64Random.next_int(&self) @dynamic
fn uint128 Sfc64Random.next_int128(&self) @dynamic
fn ulong Sfc64Random.next_long(&self) @dynamic
fn ushort Sfc64Random.next_short(&self) @dynamic
fn char Sfc8Random.next_byte(&self) @dynamic
fn void Sfc8Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Sfc8Random.next_int(&self) @dynamic
fn uint128 Sfc8Random.next_int128(&self) @dynamic
fn ulong Sfc8Random.next_long(&self) @dynamic
fn ushort Sfc8Random.next_short(&self) @dynamic
fn char SimpleRandom.next_byte(&self) @dynamic
fn void SimpleRandom.next_bytes(&self, char[] bytes) @dynamic
fn uint SimpleRandom.next_int(&self) @dynamic
fn uint128 SimpleRandom.next_int128(&self) @dynamic
fn ulong SimpleRandom.next_long(&self) @dynamic
fn ushort SimpleRandom.next_short(&self) @dynamic
fn void SimpleRandom.set_seed(&self, char[] seed) @dynamic
macro bool is_random(random)
macro make_seed($Type, char[] input)
macro int next(random, int max)
macro void next_bool(random)
macro double next_double(random)
macro float next_float(random)
fn int rand(int max) @builtin
macro void seed(random, seed)
macro void seed_entropy(random)
fn void seeder(char[] input, char[] out_buffer)

std::math::vector

macro Vec2.angle(self, Vec2 v2)
macro Vec2.clamp_mag(self, double min, double max)
macro Vec2.distance_sq(self, Vec2 v2)
macro Vec2.length_sq(self)
macro Vec2.rotate(self, double angle)
fn Vec2 Vec2.towards(self, Vec2 target, double max_distance)
macro Vec2.transform(self, Matrix4 mat)
macro Vec2f.angle(self, Vec2f v2)
macro Vec2f.clamp_mag(self, float min, float max)
macro Vec2f.distance_sq(self, Vec2f v2)
macro Vec2f.length_sq(self)
macro Vec2f.rotate(self, float angle)
fn Vec2f Vec2f.towards(self, Vec2f target, float max_distance)
macro Vec2f.transform(self, Matrix4f mat)
fn double Vec3.angle(self, Vec3 v2)
fn Vec3 Vec3.barycenter(self, Vec3 a, Vec3 b, Vec3 c)
macro Vec3.clamp_mag(self, double min, double max)
fn Vec3 Vec3.cross(self, Vec3 v2)
macro Vec3.distance_sq(self, Vec3 v2)
macro Vec3.length_sq(self)
fn Vec3 Vec3.perpendicular(self)
fn Vec3 Vec3.refract(self, Vec3 n, double r)
fn Vec3 Vec3.rotate_axis(self, Vec3 axis, double angle)
fn Vec3 Vec3.rotate_quat(self, Quaternion q)
fn Vec3 Vec3.towards(self, Vec3 target, double max_distance)
fn Vec3 Vec3.transform(self, Matrix4 mat)
fn Vec3 Vec3.unproject(self, Matrix4 projection, Matrix4 view)
fn float Vec3f.angle(self, Vec3f v2)
fn Vec3f Vec3f.barycenter(self, Vec3f a, Vec3f b, Vec3f c)
macro Vec3f.clamp_mag(self, float min, float max)
fn Vec3f Vec3f.cross(self, Vec3f v2)
macro Vec3f.distance_sq(self, Vec3f v2)
macro Vec3f.length_sq(self)
fn Vec3f Vec3f.perpendicular(self)
fn Vec3f Vec3f.refract(self, Vec3f n, float r)
fn Vec3f Vec3f.rotate_axis(self, Vec3f axis, float angle)
fn Vec3f Vec3f.rotate_quat(self, Quaternionf q)
fn Vec3f Vec3f.towards(self, Vec3f target, float max_distance)
fn Vec3f Vec3f.transform(self, Matrix4f mat)
fn Vec3f Vec3f.unproject(self, Matrix4f projection, Matrix4f view)
macro Vec4.clamp_mag(self, double min, double max)
macro Vec4.distance_sq(self, Vec4 v2)
macro Vec4.length_sq(self)
fn Vec4 Vec4.towards(self, Vec4 target, double max_distance)
macro Vec4f.clamp_mag(self, float min, float max)
macro Vec4f.distance_sq(self, Vec4f v2)
macro Vec4f.length_sq(self)
fn Vec4f Vec4f.towards(self, Vec4f target, float max_distance)
fn Matrix4 matrix4_look_at(Vec3 eye, Vec3 target, Vec3 up)
fn Matrix4f matrix4f_look_at(Vec3f eye, Vec3f target, Vec3f up)
fn void ortho_normalize(Vec3f* v1, Vec3f* v2)
fn void ortho_normalized(Vec3* v1, Vec3* v2)

std::net

enum IpProtocol : char (AIFamily ai_family)
fault NetError
struct InetAddress (Printable)
fn bool InetAddress.is_any_local(InetAddress* addr)
fn bool InetAddress.is_link_local(InetAddress* addr)
fn bool InetAddress.is_loopback(InetAddress* addr)
fn bool InetAddress.is_multicast(InetAddress* addr)
fn bool InetAddress.is_multicast_global(InetAddress* addr)
fn bool InetAddress.is_multicast_link_local(InetAddress* addr)
fn bool InetAddress.is_multicast_node_local(InetAddress* addr)
fn bool InetAddress.is_multicast_org_local(InetAddress* addr)
fn bool InetAddress.is_multicast_site_local(InetAddress* addr)
fn bool InetAddress.is_site_local(InetAddress* addr)
fn usz! InetAddress.to_format(InetAddress* addr, Formatter* formatter) @dynamic
fn String InetAddress.to_new_string(InetAddress* addr, Allocator allocator = allocator::heap()) @dynamic
fn AddrInfo*! addrinfo(String host, uint port, AIFamily ai_family, AISockType ai_socktype) @if(os::SUPPORTS_INET)
fn String! int_to_new_ipv4(uint val, Allocator allocator = allocator::heap())
fn String! int_to_temp_ipv4(uint val)
fn InetAddress! ipv4_from_str(String s)
fn uint! ipv4toint(String s)
fn InetAddress! ipv6_from_str(String s)

std::net @if(os::SUPPORTS_INET)

distinct PollEvents = ushort;
distinct PollSubscribes = ushort;
enum SocketOption : char (CInt value)
struct Poll
struct Socket (InStream, OutStream)
macro void @loop_over_ai(AddrInfo* ai; @body(NativeSocket fd, AddrInfo* ai))
fn void! Socket.close(&self) @inline @dynamic
fn void! Socket.destroy(&self) @dynamic
fn bool! Socket.get_broadcast(&self)
fn bool! Socket.get_dontroute(&self)
fn bool! Socket.get_keepalive(&self)
fn bool! Socket.get_oobinline(&self)
fn bool! Socket.get_option(&self, SocketOption option)
fn bool! Socket.get_reuseaddr(&self)
fn usz! Socket.read(&self, char[] bytes) @dynamic
fn char! Socket.read_byte(&self) @dynamic
fn void! Socket.set_broadcast(&self, bool value)
fn void! Socket.set_dontroute(&self, bool value)
fn void! Socket.set_keepalive(&self, bool value)
fn void! Socket.set_oobinline(&self, bool value)
fn void! Socket.set_option(&self, SocketOption option, bool value)
fn void! Socket.set_reuseaddr(&self, bool value)
fn usz! Socket.write(&self, char[] bytes) @dynamic
fn void! Socket.write_byte(&self, char byte) @dynamic
macro Socket new_socket(fd, ai)
fn ulong! poll(Poll[] polls, Duration timeout)
fn ulong! poll_ms(Poll[] polls, long timeout_ms)

std::net::os

distinct AIFamily = CInt;
distinct AIFlags = CInt;
distinct AIProtocol = CInt;
distinct AISockType = CInt;
distinct SockAddrPtr = void*;
struct AddrInfo

std::net::os @if(env::POSIX && SUPPORTS_INET)

distinct NativeSocket = inline Fd;
struct Posix_pollfd
macro void! NativeSocket.close(self)
macro bool NativeSocket.is_non_blocking(self)
macro void! NativeSocket.set_non_blocking(self, bool non_blocking)
fn anyfault convert_error(Errno error)
fn anyfault socket_error()

std::net::os @if(env::WIN32)

distinct NativeSocket = uptr;
macro void! NativeSocket.close(self)
fn void! NativeSocket.set_non_blocking(self, bool non_blocking)
fn anyfault convert_error(WSAError error)
fn anyfault socket_error()

std::net::tcp @if(os::SUPPORTS_INET)

distinct TcpServerSocket = inline Socket;
distinct TcpSocket = inline Socket;
fn TcpSocket! accept(TcpServerSocket* server_socket)
fn TcpSocket! connect(String host, uint port, Duration timeout = 0, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
fn TcpSocket! connect_async(String host, uint port, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
fn TcpSocket! connect_async_to(AddrInfo* ai, SocketOption... options)
fn TcpSocket! connect_to(AddrInfo* ai, SocketOption... options)
fn TcpServerSocket! listen(String host, uint port, uint backlog, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
fn TcpServerSocket! listen_to(AddrInfo* ai, uint backlog, SocketOption... options)

std::net::udp @if(os::SUPPORTS_INET)

distinct UdpSocket = inline Socket;
fn UdpSocket! connect(String host, uint port, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
fn UdpSocket! connect_async(String host, uint port, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
fn UdpSocket! connect_async_to(AddrInfo* ai, SocketOption... options)
fn UdpSocket! connect_to(AddrInfo* ai, SocketOption... options)

std::os @if(env::DARWIN)

fn uint num_cpu()

std::os @if(env::LINUX)

fn uint num_cpu()

std::os @if(env::WIN32)

fn uint num_cpu()

std::os::backtrace

fault BacktraceFault
struct Backtrace (Printable)
fn void Backtrace.free(&self)
fn bool Backtrace.has_file(&self)
fn Backtrace* Backtrace.init(&self, uptr offset, String function, String object_file, String file = "", uint line = 0, Allocator allocator)
fn bool Backtrace.is_unknown(&self)
fn usz! Backtrace.to_format(&self, Formatter* formatter) @dynamic
fn void*[] capture_current(void*[] buffer)
fn BacktraceList! symbolize_backtrace(void*[] backtrace, Allocator allocator) @if(!env::NATIVE_STACKTRACE)

std::os::darwin @if(env::DARWIN)

struct Darwin_Dl_info
struct Darwin_segment_command_64
fn String! executable_path(Allocator allocator)
fn BacktraceList! symbolize_backtrace(void*[] backtrace, Allocator allocator)

std::os::env

fn bool clear_var(String name)
fn String! executable_path(Allocator allocator = allocator::heap())
fn Path! get_config_dir(Allocator allocator = allocator::heap())
fn String! get_home_dir(Allocator using = allocator::heap())
fn String! get_var(String name, Allocator allocator = allocator::heap())
fn String! get_var_temp(String name)
fn bool set_var(String name, String value, bool overwrite = true)

std::os::linux @if(env::LINUX)

struct Elf32_Ehdr
struct Elf32_Phdr
struct Elf64_Ehdr
struct Elf64_Phdr
struct Linux_Dl_info
fn BacktraceList! symbolize_backtrace(void*[] backtrace, Allocator allocator)

std::os::macos::cf @if(env::DARWIN) @link(env::DARWIN, “CoreFoundation.framework”)

distinct CFAllocatorContextRef = void*;
distinct CFAllocatorRef = void*;
distinct CFArrayCallBacksRef = void*;
distinct CFArrayRef = void*;
distinct CFMutableArrayRef = void*;
distinct CFTypeRef = void*;
struct CFRange
macro void* CFAllocatorRef.alloc(CFAllocatorRef allocator, usz size)
macro void CFAllocatorRef.dealloc(CFAllocatorRef allocator, void* ptr)
macro usz CFAllocatorRef.get_preferred_size(CFAllocatorRef allocator, usz req_size)
macro void CFAllocatorRef.set_default(CFAllocatorRef allocator)
macro CFAllocatorRef default_allocator()

std::os::macos::objc @if(env::DARWIN) @link(env::DARWIN, “CoreFoundation.framework”)

distinct Class = void*;
distinct Ivar = void*;
distinct Method = void*;
distinct Selector = void*;
fault ObjcFailure
macro bool Class.equals(Class a, Class b)
macro Method Class.method(Class cls, Selector name)
macro ZString Class.name(Class cls)
macro bool Class.responds_to(Class cls, Selector sel)
macro Class Class.superclass(Class cls)
macro bool Selector.equals(Selector a, Selector b)
macro Class! class_by_name(ZString c)
macro Class[] class_get_list(Allocator allocator = allocator::heap())

std::os::posix @if(env::POSIX)

distinct DIRPtr = void*;
distinct Pthread_t = void*;
struct Posix_dirent
struct Posix_spawn_file_actions_t
struct Posix_spawnattr_t
fn CInt backtrace(void** buffer, CInt size)
macro CInt wEXITSTATUS(CInt status)
macro bool wIFCONTINUED(CInt status)
macro bool wIFEXITED(CInt status)
macro bool wIFSIGNALED(CInt status)
macro bool wIFSTOPPED(CInt status)
macro CInt wSTOPSIG(CInt status)
macro CInt wTERMSIG(CInt status)
macro CInt wWCOREDUMP(CInt status)
macro CInt w_EXITCODE(CInt ret, CInt sig)
macro CInt w_STOPCODE(CInt sig)

std::os::process @if(env::WIN32 || env::POSIX)

fault SubProcessResult
struct SubProcess
fn bool SubProcess.destroy(&self)
fn bool! SubProcess.is_running(&self)
fn CInt! SubProcess.join(&self) @if(env::POSIX)
fn CInt! SubProcess.join(&self) @if(env::WIN32)
fn usz! SubProcess.read_stderr(&self, char* buffer, usz size)
fn usz! SubProcess.read_stdout(&self, char* buffer, usz size)
fn File SubProcess.stdout(&self)
fn void! SubProcess.terminate(&self)
fn SubProcess! create(String[] command_line, SubProcessOptions options = {}, String[] environment = {}) @if(env::POSIX)
fn SubProcess! create(String[] command_line, SubProcessOptions options = {}, String[] environment = {}) @if(env::WIN32)
fn String! execute_stdout_to_buffer(char[] buffer, String[] command_line, SubProcessOptions options = {}, String[] environment = {})

std::os::win32

distinct Win32_CRITICAL_SECTION = ulong[5];
enum Win32_ADDRESS_MODE
enum Win32_SYM_TYPE
struct Win32_ADDRESS64
struct Win32_AMD64_CONTEXT @align(16)
struct Win32_ARM64_NT_CONTEXT @align(16)
struct Win32_ARM64_NT_NEON128
struct Win32_FILETIME
struct Win32_GUID
struct Win32_IMAGEHLP_LINE64
struct Win32_IMAGEHLP_MODULE64
struct Win32_IMAGE_DATA_DIRECTORY
struct Win32_IMAGE_FILE_HEADER
struct Win32_IMAGE_NT_HEADERS
struct Win32_IMAGE_OPTIONAL_HEADER64
struct Win32_KDHELP64
struct Win32_M128A @align(16)
struct Win32_MODLOAD_DATA
struct Win32_MODULEINFO
struct Win32_OVERLAPPED
struct Win32_PROCESS_INFORMATION
struct Win32_SECURITY_ATTRIBUTES
struct Win32_STACKFRAME64
struct Win32_STARTUPINFOEXW
struct Win32_STARTUPINFOW
struct Win32_SYMBOL_INFO
struct Win32_SYSTEM_INFO
struct Win32_UNICODE_STRING
struct Win32_XMM_SAVE_AREA32

std::os::win32 @if(env::WIN32)

distinct WSAError = int;
enum Win32_GET_FILEEX_INFO_LEVELS
struct Symbol
struct Win32_FILE_ATTRIBUTE_DATA
struct Win32_WIN32_FIND_DATAW
struct Win32_pollfd
fn Win32_DWORD! load_modules()
fn Backtrace! resolve_backtrace(void* addr, Win32_HANDLE process, Allocator allocator)
fn BacktraceList! symbolize_backtrace(void*[] backtrace, Allocator allocator)

std::sort

macro bool @is_comparer(#cmp, #list)
macro usz @len_from_list(&list)
macro usz binarysearch(list, x, cmp = null) @builtin
macro quicksort(list, cmp = null) @builtin

std::sort::qs(<Type, Comparer>)

fn void qsort(Type list, isz low, isz high, Comparer cmp)

std::thread

distinct ConditionVariable = NativeConditionVariable;
distinct Mutex = NativeMutex;
distinct MutexType = int;
distinct OnceFlag = NativeOnceFlag;
distinct RecursiveMutex = inline Mutex;
distinct Thread = NativeThread;
distinct TimedMutex = inline Mutex;
distinct TimedRecursiveMutex = inline Mutex;
fault ThreadFault
macro void! ConditionVariable.broadcast(&cond)
macro void! ConditionVariable.destroy(&cond)
macro void! ConditionVariable.init(&cond)
macro void! ConditionVariable.signal(&cond)
macro void! ConditionVariable.wait(&cond, Mutex* mutex)
macro void! ConditionVariable.wait_timeout(&cond, Mutex* mutex, ulong timeout)
macro void! Mutex.destroy(&mutex)
macro void! Mutex.init(&mutex)
macro void! Mutex.lock(&mutex)
macro bool Mutex.try_lock(&mutex)
macro void! Mutex.unlock(&mutex)
macro void OnceFlag.call(&flag, OnceFn func)
macro void! RecursiveMutex.init(&mutex)
macro void! Thread.create(&thread, ThreadFn thread_fn, void* arg)
macro void! Thread.detach(thread)
macro bool Thread.equals(thread, Thread other)
macro int! Thread.join(thread)
macro void! TimedMutex.init(&mutex)
macro void! TimedMutex.lock_timeout(&mutex, ulong ms)
macro void! TimedRecursiveMutex.init(&mutex)
macro void! TimedRecursiveMutex.lock_timeout(&mutex, ulong ms)
macro Thread current()
macro void exit(int result)
macro void! sleep(Duration d) @maydiscard
macro void! sleep_ms(ulong ms) @maydiscard
macro void! sleep_ns(NanoDuration ns) @maydiscard
macro void yield()

std::thread::cpu @if(env::DARWIN)

fn uint native_cpu()

std::thread::cpu @if(env::LINUX)

fn uint native_cpu()

std::thread::cpu @if(env::WIN32)

fn uint native_cpu()

std::thread::os @if (!env::POSIX && !env::WIN32)

distinct NativeConditionVariable = int;
distinct NativeMutex = int;
distinct NativeOnceFlag = int;
distinct NativeThread = int;

std::thread::os @if(env::LINUX)

distinct Pthread_attr_t = ulong[7]; // 24 on 32bit
distinct Pthread_cond_t = ulong[6];
distinct Pthread_condattr_t = uint;
distinct Pthread_key_t = uint;
distinct Pthread_mutex_t = ulong[5]; // 24 on 32 bit
distinct Pthread_mutexattr_t = uint;
distinct Pthread_once_t = int;
distinct Pthread_rwlock_t = ulong[7]; // 32 on 3bit
distinct Pthread_rwlockattr_t = uint;
distinct Pthread_sched_param = uint;

std::thread::os @if(env::POSIX && !env::LINUX)

distinct Pthread_attr_t = ulong[8];
distinct Pthread_cond_t = ulong[6];
distinct Pthread_condattr_t = ulong[8];
distinct Pthread_key_t = ulong;
distinct Pthread_mutex_t = ulong[8];
distinct Pthread_mutexattr_t = ulong[2];
distinct Pthread_once_t = ulong[2];
distinct Pthread_rwlock_t = ulong[25];
distinct Pthread_rwlockattr_t = ulong[3];
distinct Pthread_sched_param = ulong;

std::thread::os @if(env::POSIX)

struct NativeMutex
fn void! NativeConditionVariable.broadcast(&cond)
fn void! NativeConditionVariable.destroy(&cond)
fn void! NativeConditionVariable.init(&cond)
fn void! NativeConditionVariable.signal(&cond)
fn void! NativeConditionVariable.wait(&cond, NativeMutex* mtx)
fn void! NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms)
fn void! NativeMutex.destroy(&self)
fn void! NativeMutex.init(&self, MutexType type)
fn bool NativeMutex.is_initialized(&self)
fn void! NativeMutex.lock(&self)
fn void! NativeMutex.lock_timeout(&self, ulong ms)
fn bool NativeMutex.try_lock(&self)
fn void! NativeMutex.unlock(&self)
fn void NativeOnceFlag.call_once(&flag, OnceFn func)
fn void! NativeThread.create(&thread, ThreadFn thread_fn, void* arg)
fn void! NativeThread.detach(thread)
fn bool NativeThread.equals(thread, NativeThread other)
fn int! NativeThread.join(thread)
fn void! native_sleep_nano(NanoDuration nano)
fn NativeThread native_thread_current()
fn void native_thread_exit(int result)
fn void native_thread_yield()

std::thread::os @if(env::WIN32)

distinct NativeThread = inline Win32_HANDLE;
struct NativeConditionVariable
struct NativeMutex
struct NativeOnceFlag
fn void! NativeConditionVariable.broadcast(&cond)
fn void! NativeConditionVariable.destroy(&cond) @maydiscard
fn void! NativeConditionVariable.init(&cond)
fn void! NativeConditionVariable.signal(&cond)
fn void! NativeConditionVariable.wait(&cond, NativeMutex* mtx) @inline
fn void! NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, uint time) @inline
fn void! NativeMutex.destroy(&mtx)
fn void! NativeMutex.init(&mtx, MutexType type)
fn void! NativeMutex.lock(&mtx)
fn void! NativeMutex.lock_timeout(&mtx, usz ms)
fn bool NativeMutex.try_lock(&mtx)
fn void! NativeMutex.unlock(&mtx)
fn void NativeOnceFlag.call_once(&flag, OnceFn func)
fn void! NativeThread.create(&thread, ThreadFn func, void* args)
fn void! NativeThread.detach(thread) @inline
fn bool NativeThread.equals(thread, NativeThread other)
fn int! NativeThread.join(thread)
fn void! native_sleep_nano(NanoDuration ns)
fn NativeThread native_thread_current()
fn void native_thread_exit(int result) @inline
fn void native_thread_yield()

std::thread::pool(<SIZE>)

struct QueueItem
struct ThreadPool
fn void! ThreadPool.destroy(&self)
fn void! ThreadPool.init(&self)
fn void! ThreadPool.push(&self, ThreadFn func, void* arg)
fn void! ThreadPool.stop_and_destroy(&self)

std::time

distinct Clock = ulong;
distinct Duration = long;
distinct NanoDuration (Printable) = long;
distinct Time = long;
enum Month : char
enum Weekday : char
struct DateTime
struct TzDateTime
fn long Duration.to_ms(td)
fn NanoDuration Duration.to_nano(td)
fn Duration NanoDuration.to_duration(nd)
fn usz! NanoDuration.to_format(&self, Formatter* formatter) @dynamic
fn long NanoDuration.to_ms(nd)
fn double NanoDuration.to_sec(nd)
fn Time Time.add_days(time, long days)
fn Time Time.add_duration(time, Duration duration)
fn Time Time.add_hours(time, long hours)
fn Time Time.add_minutes(time, long minutes)
fn Time Time.add_seconds(time, long seconds)
fn Time Time.add_weeks(time, long weeks)
fn double Time.diff_days(time, Time other)
fn double Time.diff_hour(time, Time other)
fn double Time.diff_min(time, Time other)
fn double Time.diff_sec(time, Time other)
fn Duration Time.diff_us(time, Time other)
fn double Time.diff_weeks(time, Time other)
fn double Time.to_seconds(time)
fn Duration from_float(double s) @inline
fn Duration hour(long l) @inline
fn Duration min(long l) @inline
fn Duration ms(long l) @inline
fn Time now()
fn Duration sec(long l) @inline

std::time::clock

fn NanoDuration Clock.mark(&self)
fn NanoDuration Clock.to_now(self)
fn Clock now()

std::time::datetime @if(env::LIBC)

fn DateTime DateTime.add_days(&self, int days)
fn DateTime DateTime.add_hours(&self, int hours)
fn DateTime DateTime.add_minutes(&self, int minutes)
fn DateTime DateTime.add_months(&self, int months)
fn DateTime DateTime.add_seconds(&self, int seconds)
fn DateTime DateTime.add_weeks(&self, int weeks)
fn DateTime DateTime.add_years(&self, int years)
fn bool DateTime.after(&self, DateTime compare) @inline
fn bool DateTime.before(&self, DateTime compare) @inline
fn int DateTime.compare_to(&self, DateTime compare)
fn double DateTime.diff_sec(self, DateTime from)
fn Duration DateTime.diff_us(self, DateTime from)
fn int DateTime.diff_years(&self, DateTime from)
fn void DateTime.set_date(&self, int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0)
fn void DateTime.set_time(&self, Time time)
fn TzDateTime DateTime.to_local(&self)
fn Time DateTime.to_time(&self) @inline
fn DateTime from_date(int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0)
fn DateTime from_time(Time time)
fn DateTime now()

std::time::os @if(env::DARWIN)

struct Darwin_mach_timebase_info
fn Clock native_clock()

std::time::os @if(env::POSIX)

fn Clock native_clock() @if(!env::DARWIN)
fn Time native_timestamp()

std::time::os @if(env::WIN32)

fn Clock native_clock()
fn Time native_timestamp()

title: Conversions and Promotions description: Conversions and Promotions sidebar:

order: 210

Conversion Rules For C3

C3 differs in some crucial respects when it comes to number conversions and promotions. These are the rules for C3:

C3 uses two’s complement arithmetic for all integer math.

:::note These abbreviations are used in the text below: - “lhs” meaning “left hand side”. - “rhs” meaning “right hand side”. :::

Target type

The left hand side of an assignment, or the parameter type in a call is known as the target type the target type is used for implicit widening and inferring struct initialization.

Common arithmetic promotion

Like C, C3 uses implicit arithmetic promotion of integer and floating point variables before arithmetic operations:

  1. For any floating point type with a bitwidth smaller than 32 bits, widen to float. E.g. f16 -> float
  2. For an integer type smaller than the minimum arithmetic width promote the value to a same signed integer of the minimum arithmetic width (this usually corresponds to a c int/uint). E.g. ushort -> uint

Implicit narrowing

An expression with an integer type, may implicitly narrow to smaller integer type, and similarly a float type may implicitly narrow to less wide floating point type is determined from the following algorithm.

  1. Shifts and assign look at the lhs expression.
  2. ++, --, ~, -, !!, ! - check the inner type.
  3. +, -, *, /, %, ^, |, &, ??, ?: - check both lhs and rhs.
  4. Narrowing int/float cast, assume the type is the narrowed type.
  5. Widening int/float cast, look at the inner expression, ignoring the cast.
  6. In the case of any other cast, assume it is opaque and the type is that of the cast.
  7. In the case of an integer literal, instead of looking at the type, check that the integer would fit the type to narrow to.
  8. For .len access, allow narrowing to C int width.
  9. For all other expressions, check against the size of the type.

As rough guide: if all the sub expressions originally are small enough it’s ok to implicitly convert the result.

Examples:

float16 h = 12.0;
float f = 13.0;
double d = 22.0;

char x = 1;
short y = -3;
int z = 0xFFFFF;
ulong w = -0xFFFFFFF;

x = x + x; // => calculated as x = (char)((int)x + (int)x);
x = y + x; // => Error, narrowing not allowed as y > char
h = x * h; // => calculated as h = (float16)((float)x * (float)h);
h = f + x; // => Error, narrowing not allowed since f > f16

Implicit widening

Unlike C, implicit widening will only happen on “simple expressions”: if the expression is a primary expression, or a unary operation on a primary expression.

For assignment, special rules hold. For an assignment to a binary expression, if its two subexpressions are “simple expressions” and the binary expression is +, -, /, *, allow an implicit promotion of the two sub expressions.

int a = ...
short b = ...
char c = ...
long d = a; // Valid - simple expression.
int e = (int)(d + (a + b)); // Error
int f = (int)(d + ~b); // Valid
long g = a + b; // Valid

As a rule of thumb, if there are more than one possible conversion an explicit cast is needed.

Example:

long h = a + (b + c);

// Possible intention 1
long h = (long)(a + (b + c));

// Possible intention 2
long h = (long)a + (long)(b + c);

// Possible intention 3
long h = (long)a + ((long)b + (long)c);

Maximum type

The maximum type is a concept used when unifying two or more types. The algorithm follows:

  1. First perform implicit promotion.
  2. If both types are the same, the maximum type is this type.
  3. If one type is a floating point type, and the other is an integer type, the maximum type is the floating point type. E.g. int + float -> float.
  4. If both types are floating point types, the maximum type is the widest floating point type. E.g. float + double -> double.
  5. If both types are integer types with the same signedness, the maximum type is the widest integer type of the two. E.g. uint + ulong -> ulong.
  6. If both types are integer types with different signedness, the maximum type is a signed integer with the same bit width as the maximum integer type. ulong + int -> long
  7. If at least one side is a struct or a pointer to a struct with an inline directive on a member, check recursively check if the type of the inline member can be used to find a maximum type (see below under sub struct conversions)
  8. All other cases are errors.

Substruct conversions

Substructs may be used in place of its parent structs in many cases. The rule is as follows:

  1. A substruct pointer may implicitly convert to a parent struct.
  2. A substruct value may be implicitly assigned to a variable with the parent struct type, This will truncate the value, copying only the parent part of the substruct. However, a substruct value cannot be assigned its parent struct.
  3. Substruct slices and arrays can not be cast (implicitly or explicitly) to an array of the parent struct type.

Pointer conversions

Pointer conversion between types usually need explicit casts. The exception is void * which any type may implicitly convert to or from. Conversion rules from and to arrays are detailed under arrays

Vector conversions

Conversion between underlying vector types need explicit conversions. They work as regular conversions with one notable exception: converting a true boolean vector value into an int will yield a value with all bits set. So bool[<2>] { true, false } converted to for example char[<2>] will yield { 255, 0 }.

Vectors can also be cast to the corresponding array type, for example: char[<2>] <=> char[2].

Binary conversions

1. Multiplication, division, remainder, subtraction / addition with both operands being numbers

These operations are only valid for integer and float types.

  1. Resolve the operands.
  2. Find the maximum type of the two operands.
  3. Promote both operands to the resulting type if both are simple expressions
  4. The resulting type of the expression is the maximum type.

2. Addition with left side being a pointer

  1. Resolve the operands.
  2. If the rhs is not an integer, this is an error.
  3. If the rhs has a bit width that exceeds isz, this is an error.
  4. The result of the expression is the lhs type.

3. Subtraction with lhs pointer and rhs integer

  1. Resolve the operands.
  2. If the right hand type has a bit width that exceeds isz, this is an error.
  3. The result of the expression is the left hand type.

4. Subtraction with both sides pointers

  1. Resolve the operands.
  2. If the either side is a void *, it is cast to the other type.
  3. If the types of the sides are different, this is an error.
  4. The result of the expression is isz.
  5. If this result exceeds the target width, this is an error.

6. Bit operations ^ & |

These operations are only valid for integers and booleans.

  1. Resolve the operands.
  2. Find the maximum type of the two operands.
  3. Promote both operands to the maximum type if they are simple expressions.
  4. The result of the expression is the maximum type.

6. Shift operations << >>

These operations are only valid for integers.

  1. Resolve the operands.
  2. In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side.
  3. The result of the expression is the lhs type.

7. Assignment operations += -= *= *= /= %= ^= |= &=

  1. Resolve the lhs.
  2. Resolve the right operand as an assignment rhs.
  3. The result of the expression is the lhs type.

8. Assignment shift >>= <<=

  1. Resolve both operands
  2. In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side.
  3. The result of the expression is the lhs type.

9. && and ||

  1. Resolve both operands.
  2. Insert bool cast of both operands.
  3. The type is bool.

10. <= == >= !=

  1. Resolve the operands, left to right.
  2. Find the maximum type of the two operands.
  3. Promote both operands to the maximum type.
  4. The type is bool.

Unary conversions

1. Bit negate

  1. Resolve the inner operand.
  2. If the inner type is not an integer this is an error.
  3. The type is the inner type.

2. Boolean not

  1. Resolve the inner operand.
  2. The type is bool.

3. Negation

  1. Resolve the inner operand.
  2. If the type inner type is not a number this is an error.
  3. If the inner type is an unsigned integer, cast it to the same signed type.
  4. The type is the type of the result from (3)

4. & and &&

  1. Resolve the inner operand.
  2. The type is a pointer to the type of the inner operand.

5. *

  1. Resolve the inner operand.
  2. If the operand is not a pointer, or is a void * pointer, this is an error.
  3. The type is the pointee of the inner operand’s type.

Dereferencing 0 is implementation defined.

6. ++ and --

  1. Resolve the inner operand.
  2. If the type is not a number, this is an error.
  3. The type is the same as the inner operand.

Base expressions

1. Typed identifiers

  1. The type is that of the declaration.
  2. If the width of the type is less than that of the target type, widen to the target type.
  3. If the width of the type is greater than that of the target type, it is an error.

2. Constants and literals

  1. If the constant is an integer, it is assumed to be the arithmetic promotion width and signed. If the suffix u is added, it is assumed to be an unsigned number. If a suffix ixx or uxx is given then it is considered a an integer of that type width and signedness. It cannot be implicitly narrowed.
  2. If the constant is a floating point value, it is assumed to be a double unless suffixed with f which is then assumed to be a float. If a bit width is given after f, it is instead a floating point type of that width.

title: Precedence description: Precedence sidebar:

order: 211

Precedence rules in C3 differs from C/C++. Here are all precedence levels in C3, listed from highest (1) to lowest (11):

  1. (), [], ., !! postfix !, ++ and --
  2. @, prefix -, ~, prefix *, &, prefix ++ and --
  3. infix *, /, %
  4. <<, >>
  5. ^, |, infix &
  6. +, infix -, +++
  7. ==, !=, >=, <=, >, <
  8. &&, &&&
  9. ||, |||
  10. ternary ?: ??
  11. =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=

The main difference is that bitwise operations and shift has higher precedence than addition/subtraction and multiplication/division in C3. Bitwise operations also have higher precedence than the relational operators. Also, there is no difference in precedence between && || or between the bitwise operators.

Examples:

a + b >> c + d

(a + b) >> (c + d) // C (+ - are evaluated before >>)
a + (b >> c) + d   // C3 (>> is evaluated before + -)


a & b == c

a & (b == c)       // C  (bitwise operators are evaluated after relational)
(a & b) == c       // C3 (bitwise operators are evaluated before relational)


a > b == c < d

(a > b) == (c < d) // C  (< > binds tighter than ==)
((a > b) == c) < d // C3 Error, requires parenthesis!


a | b ^ c & d

a | ((b ^ c) & d)  // C  (All bitwise operators have different precedence)
((a | b) ^ c) & d  // C3 Error, requires parenthesis!

The change in precedence of the bitwise operators corrects a long standing issue in the C specification. The change in precedence for shift operations goes towards making the precedence less surprising.

Conflating the precedence of relational and equality operations, and all bitwise operations was motivated by simplification: few remember the exact internal differences in precedence between bitwise operators. Parenthesis are required for those conflated levels of precedence.

Left- to-right offers a very simple model to think about the internal order of operations, and encourages use of explicit ordering, as best practice in C is to use parentheses anyway.


title: Undefined Behaviour description: Undefined Behaviour sidebar:

order: 212

Like C, C3 uses undefined behaviour. In contrast, C3 will trap - that is, print an error trace and abort – on undefined behaviour in debug builds. This is similar to using C with a UB sanitizer. It is only during release builds that actual undefined behaviour occurs.

In C3, undefined behaviour means that the compiler is free to interpret undefined behaviour as if behaviour cannot occur.

In the example below:

uint x = foo();
uint z = 255 / x;
return x != 0;

The case of x == 0 would invoke undefined behaviour for 255/x. For that reason, the compiler may assume that x != 0 and compile it into the following code:

foo();
return true;

As a contrast, the safe build will compile code equivalent to the following.

uint x = foo();
if (x == 0) trap("Division by zero")
return true;

List of undefined behaviours

The following operations cause undefined behaviour in release builds of C3:

operation will trap in safe builds
int / 0 Yes
int % 0 Yes
reading explicitly uninitialized memory Possible*
array index out of bounds Yes
dereference null Yes
dereferencing memory not allocated Possible*
dereferencing memory outside of its lifetime Possible*
casting pointer to the incorrect array Possible*
violating pre or post conditions Yes
violating asserts Yes
reaching unreachable() code Yes

* “Possible” indicates trapping is implementation dependent.

List of implementation dependent behaviours

Some behaviour is allowed to differ between implementations and platforms.

operation will trap in safe builds permitted behaviour
comparing pointers of different provenance Optional Any result
subtracting pointers of different provenance Optional Any result
shifting by more or equal to the bit width Yes Any result
shifting by negative amount Yes Any result
conversion floating point <-> integer type is out of range Optional Any result
conversion between pointer types produces one with incorrect alignment Optional Any result / Error
calling a function through a function pointer that does not match the function Optional Any result / Error
attempt to modify a string literal Optional Partial modification / Error
modifying a const variable Optional Partial modification / Error

List of undefined behaviour in C, which is defined in C3

Signed Integer Overflow

Signed integer is always wrapped using 2s complement.

Modifying the intermediate results of an expression

Behaves as if the intermediate result was stored in a variable on the stack.


title: Builtins description: Builtins sidebar:

order: 226

The compiler offers builtin constants and functions. Some are only available on certain targets. All builtins use the $$ name prefix.

Builtin constants

These constants are generated by the compiler and can safely be used by the user.

$$BENCHMARK_NAMES

An array of names of the benchmark functions.

$$BENCHMARK_FNS

An array of addresses to the benchmark functions.

$$DATE

The current date.

$$FILE

The current file name.

$$FILEPATH

The current file with path.

$$FUNC

The current function name, will return ““ on the global level.

$$FUNCTION

The current function as an expression.

$$LINE

The current line as an integer.

$$LINE_RAW

Usually the same as $$LINE, but in case of a macro inclusion it returns the line in the macro rather than the line where the macro was included.

$$MODULE

The current module name.

$$TIME

The current time.

Compiler builtin functions

The $$ namespace defines compiler builtin functions. These special functions are not guaranteed to exist on all platforms, and are ways to wrap compiler implemented, optimized implementations of some particular functionality. They are mainly intended for standard library internal use. The standard library have macros that wrap these builtins, so they should normally not be used on their own.

$$trap

Emits a trap instruction.

$$unreachable

Inserts an “unreachable” annotation.

$$stacktrace

Returns the current “callstack” reference if available. OS and compiler dependent.

$$volatile_store

Takes a variable and a value and stores the value as a volatile store.

$$volatile_load

Takes a variable and returns the value using a volatile load.

$$memcpy

Builtin memcpy instruction.

$$memset

Builtin memset instruction.

$$prefetch

Prefetch a memory location.

$$sysclock

Access to the cycle counter register (or similar low latency clock) on supported architectures (e.g. RDTSC on x86), otherwise $$sysclock will yield 0.

$$syscall

Makes a syscall according to the platform convention on platforms where it is supported.

Math functions

Functions $$ceil, $$trunc, $$sin, $$cos, $$log, $$log2, $$log10, $$rint, $$round $$sqrt, $$roundeven, $$floor, $$sqrt, $$pow, $$exp, $$fma and $$fabs, $$copysign, $$round, $$nearbyint.

Can be applied to float vectors or numbers. Returns the same type.

Functions $$min, $$abs and $$max can be applied to any integer or float number or vector.

Function $pow_int takes a float or floating vector + an integer and returns the same type as the first parameter.

Saturated addition, subtraction and left shift for integers and integer vectors: $$sat_add, $$sat_shl, $$sat_sub.

Bit functions

$$fshl and $$fshr

Funnel shift left and right, takes either two integers or two integer vectors.

$$ctz, $$clz, $$bitreverse, $$bswap, $$popcount

Bit functions work on an integer or an integer vector.

Vector functions

$$reduce_add, $$reduce_mul, $$reduce_and, $$reduce_or, $$reduce_xor work on integer vectors.

$$reduce_fadd, $$reduce_fmul works on float vectors.

$$reduce_max, $$reduce_min works on any vector.

$$reverse reverses the values in any vector.

$$shufflevector rearranges the values of two vectors using a fixed mask into a resulting vector.


title: Library Packaging description: Library Packaging sidebar:

order: 227

Note, the library system is in early alpha, everything below is subject to change

C3 allows convenient packaging of C3 source files optionally with statically or dynamically linked libraries. To use such a library, simply pass the path to the library directory and add the library you wish to link to. The compiler will resolve any dependencies to other libraries and only compile those that are in use.

How it works

A library may be used either packaged or unpacked. If unpacked, it is simply a directory with the .c3l suffix, which contains all the necessary files, if packed, this is simply a compressed variant of a directory with the same structure.

The specification

In the top of the library resides the manifest.json file which has the following structure:

{
  "provides" : "my_lib",
  "execs" : [],
  "targets" : {
    "macos-x64" : {
      "linkflags" : [],
      "dependencies" : [],
      "linked-libs" : ["my_lib_static", "Cocoa.framework", "c"]
    },
    "windows-x64" : {
      "linkflags" : ["/stack:65536"],
      "dependencies" : ["ms_my_extra"],
      "linked-libs" : ["my_lib_static", "kernel32"],
      "execs" : [],
    }
  }
}

In the example here, this library supports two targets: macos-x64 and windows-x64. If we tried to use it with any other target, the compiler would give an error.

We see that if we use the windows-x64 target it will also load the ms_my_extra library. And we also see that the linker would have a special argument on that platform.

Both targets expect my_lib_static to be available for linking. If this library provides this or dynamic library it will be in the target sub-directories, so it likely has the path windows-x64/my_lib_static.lib and macos-z64/libmy_lib_static.a.

Source code

Aside from the manifest, C3 will read any C and C3 files in the same directory as manifest.json as well as any files in the target subdirectory for the current target. For static libraries typically a .c3i file (that is, a C3 file without any implementations) is provided, similar to how .h files are used in C.

Additional actions

"exec", which is available both at the top level and per-target, lists the scripts which will be invoked when a library is used. This requires running the compiler at full trust level using the --trust=full option.

How to – automatically – export libraries

This is not implemented yet, docs will materialize once it is finished


title: Inline Assembly description: Inline Assembly sidebar:

order: 232

C3 provides two ways to insert inline assembly: asm strings and asm blocks.

Asm strings

This form takes a single compile time string and passes it directly to the underlying backend without any changes.

int x = 0;
asm("nop");
int y = x;

Asm block

Asm blocks uses a common grammar for all types of processors. It assumes that all assembly statements can be reduced to the format:

instruction (arg (',' arg)*)?;

Where an arg is:

  1. An identifier, e.g. FOO, x.
  2. A numeric constant 1 0xFF etc.
  3. A register name (always lower case with a ‘$’ prefix) e.g. $eax $r7.
  4. The address of a variable e.g. &x.
  5. An indirect address: [addr] or [addr + index * <const> + offset].
  6. Any expression inside of “()” (will be evaluated before entering the asm block).

An example:

int aa = 3;
int g;
int* gp = &g;
int* xa = &a;
usz asf = 1;
asm
{
    movl x, 4;                  // Move 4 into the variable x
    movl [gp], x;               // Move the value of x into the address in gp
    movl x, 1;                  // Move 1 into x
    movl [xa + asf * 4 + 4], x; // Move x into the address at xa[asf + 1]
    movl $eax, (23 + x);        // Move 23 + x into EAX
    movl x, $eax;               // Move EAX into x
    movq [&z], 33;              // Move 33 into the memory address of z
}

The asm block will infer register clobbers and in/out parameters.

\Please note that the current state of inline asm is a work in progress, only a subset of x86, aarch64 and riscv instructions are available, other platforms have no support at all. It is likely that the grammar will be extended as more architectures are supported. More instructions can be added as they are needed, so please file issues when you encounter missing instructions you need.*


title: Build Commands description: Build Commands sidebar:

order: 240

import { FileTree } from ‘@astrojs/starlight/components’;

Building a project is done by invoking the C3 compiler with the build or run command inside of the project structure. The compiler will search upwards in the file hierarchy until a project.json file is found.

You can also customise the project build config.

Compile Individual Files

By default the compiler is compiling stand-alone files to output an executable binary.

c3c compile <file1> <file2> <file3>

Run

When starting out, with C3 it’s natural to use compile-run to try things out. For larger projects, the built-in build system is recommended instead.

The compile-run command works same as compilation, but also immediately runs the resulting executable.

c3c compile-run <file1> <file2> <file3>

Common additional parameters

Additional parameters: - --lib <path> add a library to search. - --output <path> override the output directory. - --path <path> execute as if standing at <path>

Init a new project

c3c init <project_name> [optional path]

Create a new project structure in the current directory.

Use the --template to select a template. The following are built in:

It is also possible to give the path to a custom template.

Additional parameters: - --template <path> indicate an alternative template to use.

For example c3c init hello_world creates the following structure:

- build/ - docs/ - lib/ - resources/ - scripts/ - src/ - main.c3 - test/ - LICENSE - project.json - README.md

Check the project configuration docs to learn more about configuring your project.

Test

c3c test

Will run any tests in the project in the "sources" directory defined in your project.json for example:

...
"sources": [ "src/**" ],
...

Tests are defined with a @test attribute, for example:

fn void! test_fn() @test
{
    assert(true == true, "true is definitely true");
}

Build

c3c build [target]

Build the project in the current path. It doesn’t matter where in the project structure you are.

The built-in templates define two targets: debug (which is the default) and release.

Clean

c3c clean

Build and Run

c3c run [target]

Build the target (if needed) and run the executable.

Clean and Run

c3c clean-run [target]

Clean, build and run the target.

Dist

c3c dist [target]

Not properly added yet

Clean, build and package the target for distribution. Will also run the target if it is a executable.

Docs

c3c docs [target]

Not added yet

Rebuilds the documentation.

Bench

c3c bench [target]

Runs benchmarks on a target.


title: Project Configuration description: Project Configuration sidebar:

order: 241

Customizing A Project

This is a description of the configuration options in project.json:

{
  // Language version of C3.
  "langrev": "1",
  // Warnings used for all targets.
  "warnings": [ "no-unused" ],
  // Directories where C3 library files may be found.
  "dependency-search-paths": [ "lib" ],
  // Libraries to use for all targets.
  "dependencies": [ ],
  // Authors, optionally with email.
  "authors": [ "John Doe <john.doe@example.com>" ],
  // Version using semantic versioning.
  "version": "0.1.0",
  // Sources compiled for all targets.
  "sources": [ "src/**" ],
  // C sources if the project also compiles C sources
  // relative to the project file.
  // "c-sources": [ "csource/**" ],
  // Include directories for C sources relative to the project file.
  // "c-include-dirs: [ "csource/include" ],
  // Output location, relative to project file.
  "output": "../build",
  // Architecture and OS target.
  // You can use 'c3c --list-targets' to list all valid targets,
  // "target": "windows-x64",
  // Current Target options:
  //    android-aarch64 
  //    elf-aarch64 elf-riscv32 elf-riscv64 elf-x86 elf-x64 elf-xtensa
  //    mcu-x86 mingw-x64 netbsd-x86 netbsd-x64 openbsd-x86 openbsd-x64
  //    freebsd-x86 freebsd-x64 ios-aarch64 
  //    linux-aarch64 linux-riscv32 linux-riscv64 linux-x86 linux-x64 
  //    macos-aarch64 macos-x64 
  //    wasm32 wasm64 
  //    windows-aarch64 windows-x64 
  "targets": {
    "linux-x64": {
      // Executable or library.
      "type": "executable",
      // Additional libraries, sources
      // and overrides of global settings here.
    },
  },
  // Global settings.
  // C compiler if the project also compiles C sources
  // defaults to 'cc'.
  "cc": "cc",
  // CPU name, used for optimizations in the LLVM backend.
  "cpu": "generic",
  // Debug information, may be "none", "full" and "line-tables".
  "debug-info": "full",
  // FP math behaviour: "strict", "relaxed", "fast".
  "fp-math": "strict",
  // Link libc other default libraries.
  "link-libc": true,
  // Memory environment: "normal", "small", "tiny", "none".
  "memory-env": "normal",
  // Optimization: "O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz".
  "opt": "O0",
  // Code optimization level: "none", "less", "more", "max".
  "optlevel": "none",
  // Code size optimization: "none", "small", "tiny".
  "optsize": "none",
  // Relocation model: "none", "pic", "PIC", "pie", "PIE".
  "reloc": "none",
  // Trap on signed and unsigned integer wrapping for testing.
  "trap-on-wrap": false,
  // Turn safety (contracts, runtime bounds checking, null pointer checks etc).
  "safe": true,
  // Compile all modules together, enables more inlining.
  "single-module": true,
  // Use / don't use soft float, value is otherwise target default.
  "soft-float": false,
  // Strip unused code and globals from the output.
  "strip-unused": true,
  // The size of the symtab, which limits the amount
  // of symbols that can be used. Should usually not be changed.
  "symtab": 1048576,
  // Use the system linker.
  "linker": "cc",
  // Include the standard library.
  "use-stdlib": true,
  // Set general level of x64 cpu: "baseline", "ssse3", "sse4", "avx1", "avx2-v1", "avx2-v2", "avx512", "native".
  "x86cpu": "native",
  // Set max type of vector use: "none", "mmx", "sse", "avx", "avx512", "native".
  "x86vec": "sse",
}

By default, an executable is assumed, but changing the type to "static-lib" or "dynamic-lib" creates static library and dynamic library targets respectively.

This part will be updated, stay tuned

Compilation options

The project file contains common settings at the top level, that can be overridden by each target, by simply assigning that particular key. So if the top level defines target to be macos-x64 and the actual target defines it to be windows-x64, then the windows-x64 will be used for compilation.

Similarly, compiler command line parameters can be used in turn to override the target setting.

targets

The list of targets that can be built.

dependencies

List of C3 libraries (“.c3l”) to use when compiling the target.

sources

List of source files to compile and for tests which will run.

cc

C compiler to use for compiling C sources (if C sources are compiled together with C3 files).

c-sources

List of C sources to compile, using the default C compiler.

linker-search-paths

This adds paths for the linker to search, when linking normal C libraries.

linked-libraries

This is a list of C libraries to link to. The names need to follow the normal naming standard for how libraries are provided to the system linker, so for example on Linux, libraries have names like libfoo.a but when presented to the linker the name is foo. As an example "linked-libraries": ["curl"] would on Linux look for the library libcurl.a and libcurl.so in the paths given by “linker-search-paths”.

version

Not handled yet

Version for library, will also be provided as a compile time constant.

authors

Not handled yet

List of authors to add for library compilation.

langrev

Not handled yet

The language revision to use.

features

This is a list of upper-case constants that can be tested for in the source code using $feature(NAME_OF_FEATURE).

warnings

Not completely supported yet

List of warnings to enable during compilation.

opt

Optimization setting: O0, O1, O2, O3, O4, O5, Os, Oz.

Target options

type

This mandatory option should be one of “executable”, “dynamic-lib” and “static-lib”.

More types will be added

Using environment variables

Not supported yet

In addition to constants any values starting with “$” will be assumed to be environment variables.

For example “$HOME” would on unix systems return the home directory. For strings that start with $ but should not be interpreted as an environment variable. For example, the string "\$HOME" would be interpreted as the plain string “$HOME”


title: Frequently Asked Questions description: Frequently asked questions about C3 sidebar:

order: 700

Standard library

Q: What are the most fundamental modules in the standard library?

A: By default C3 will implicitly import anything in std::core into your files. It contains string functions, allocators and conveniences for doing type introspection. The latter is in particular useful when writing contracts for macros:

Aside from the std::core module, std::collections is important as it holds various containers. Of those the generic List type in std::collections::list and the HashMap in std::collections::map are very frequently used.

IO is a must, and std::io contains std::io::file for working with files, std::io::path for working with paths. std::io itself contains functionality to writing to streams in various ways. Useful streams can be found in the stream sub folder.

Also of interest could be std::net for sockets. std::threads for platform independent threads, std::time for dates and timers, libc for invoking libc functions. std::os for working with OS specific code and std::math for math functions and vector methods.

Q: How do strings work?

A: C3 defines a native string type String, which is a distinct char[]. Because char[] is essentially a pointer + length, some care has to be taken to ensure that the pointer is properly managed.

For dynamic strings, or as a string builder, use DString. To get a String from a DString you can either get a view using str_view() or make a copy using copy_str(). In the former case, the String may become invalid if DString is then mutated.

ZString is a distinct zero terminated char*. It is used to model zero-terminated strings like in C. It is mostly useful interfacing with C.

WString is a Char16*, useful on those platforms, like Win32, where this is the common unicode format. Like ZString, it is mostly useful when interfacing with C.

Language features

Q: How do I use slices?

A: Slices are typically preferred in any situation where one in C would pass a pointer + length. It is a struct containing a pointer + a length.

Given an array, pointer or another slice you use either [start..end] or [start:len] to create it:

int[100] a;
int[] b = a[3..6]; // Or a[3:4]
b[0] = 1;          // Same as a[3] = 1

You can also just pass a pointer to an array:

b = &a; // Same as b = a[0..99];

The start and/or end may be omitted:

a[..6]; // a[0..6]
a[1..]; // a[1..99]
a[..];  // a[0..99];

It is possible to use ranges to assign:

a[1..2] = 5;         // Assign 5 to a[1] and a[2]
a[1..3] = a[11..13]; // Copy 11-13 to 1-3

It is important to remember that the lifetime of a slice is the same as the lifetime of its underlying pointer:

fn int[] buggy_code()
{
    int[3] a;
    int[] b = a[0..1];
    return b; // returning a pointer to a!
}

Q: How do I pass varargs to another function that takes varargs?

A: Use the splat operator, ...

fn void test(String format, args...)
{
    io::printfn(format, ...args);
}

fn void main()
{
    test("Format: %s %d", "Foo", 123);
}

Q: What are vectors?

A: Vectors are similar to arrays, but declared with [< >] rather than [ ], the element type may also only be of integer, float, bool or pointer types. Vectors are backed by SIMD types on supported platforms. Arithmetics available on the element type is available on the vector and is done element wise:

int[<2>] pos = { 1, 3 };
int[<2>] speed = { 5, 7 };
pos += speed;              // pos is now { 6, 10 }

Swizzling is also supported:

int[<3>] test = pos.yxx;    // test is now { 10, 6, 6 }

Any scalar value will be expanded to the vector size:

// Same as speed = speed * { 2, 2 }    
speed = speed * 2;

Memory management

Q: How do I work with memory?

A: There is malloc, calloc and free just like in C. The main difference is that these will invoke whatever the current heap allocator is, which does not need to be the allocator provided by libc. You can get the current heap allocator using allocator::heap() and do allocations directly. There is also a temporary allocator.

Convenience functions are available for allocating particular types: mem::new(Type) would allocate a single Type on the heap and zero initialize it. mem::alloc(Type) does the same but without zero initialization.

Alternatively, mem::new can take a second initializer argument:

Foo* f1 = malloc(Foo.sizeof);                   // No initialization
Foo* f2 = calloc(Foo.sizeof);                   // Zero initialization
Foo* f3 = mem::new(Foo);                        // Zero initialization
Foo* f4 = mem::alloc(Foo);                      // No initialization
Foo* f5 = mem::new(Foo, { 4, 10.0, .a = 123 }); // Initialized to argument

For arrays mem::new_array and mem::alloc_array works in corresponding ways:

Foo* foos1 = malloc(Foo.sizeof * len);    // No initialization
Foo* foos2 = calloc(Foo.sizeof * len);    // Zero initialization
Foo[] foos3 = mem::new_array(Foo, len);   // Zero initialization
Foo[] foos4 = mem::alloc_array(Foo, len); // No initialization

Regardless of how they are allocated, they can be freed using free()

Q: How does the temporary allocator work?

A: The temporary allocator is a kind of stack allocator. talloc, tcalloc and trealloc correspond to malloc, calloc and realloc. There is no free, as temporary allocations are free when pool of temporary objects are released. You use the @pool() macro to create a temporary allocation scope. When execution exits this scope, the temporary objects are all freed:

@pool()
{
    void* some_mem = talloc(128);
    foo(some_mem);
}; 
// Temporary allocations are automatically freed here.

Similar to the heap allocator, there is also mem::temp_new, mem::temp_alloc, mem::temp_new_array and mem::temp_alloc_array, which all work like their heap counterparts.

Q: How can I return a temporarily allocated object from inside a temporary allocation scope?

A: You need to pass in a copy of the temp allocator outside of @pool and allocate explicitly using that allocator. In addition, you need to pass this temp allocator to @pool to make the new temp allocator aware of the external temp allocator:

// Store the temp allocator
Allocator* temp = allocator::temp();
@pool(temp)
{
    // Note, 'allocator::temp() != temp' here!
    void* some_mem = talloc(128);
    // Allocate this on the external temp allocator
    Foo* foo = temp.new(Foo); 
    foo.z = foo(some_mem);
    // Now "some_mem" will be released,
    // but the memory pointed to by "foo" is still valid.
    return foo;
};

Interfacing with C code

Q: How do I call a C function from C3?

A: Just copy the C function definition and prefix it with extern (and don’t forget the fn as well).

Imagine for example that you have the function double test(int a, void* b). To call it from C3 just declare extern fn double test(CInt a, void* b) in the C3 code.

Q: My C function / global has a name that doesn’t conform to the C3 name requirements, just extern fn doesn’t work.

A: In this case you need to give the function a C3-compatible name and then use the @extern attribute to indicate its actual external name. For example, the function int *ABC(void *x) could be declared in the C3 code as extern fn int* abc(void* x) @extern("ABC").

There are many examples of this in the std::os modules.

Patterns

Q: When do I put functionality in method and when is it a free function?

A: In the C3 standard library, free functions are preferred unless the function is only acting on the particular type. Some exceptions exist, but prefer things like io::fprintf(file, "Hello %s", name) over file.fprintf("Hello %s", name). The former also has the advantage that it’s easier to extend to work with many types.

Q: Are there any naming conventions in the standard library what one should know about?

A: Yes. A function or method with new in the name will in general do one or more allocations and can take an optional allocator. A function or method with temp in the name will usually allocate using the temp allocator. The method free will free all memory associated with a type. destroy is similar to free but also indicates that other resources (such as file handles) are released. In some cases close is used instead of destroy.

Function and variable names use snake_case (all lower case with _ separating words).

Q: How do I create overloaded methods?

A: This can be achieved with macro methods.

Imagine you have two methods:

fn void Obj.func1(&self, String... args) @private {} // varargs variant
fn void Obj.func2(&self, Foo* pf) @private {} // Foo pointer variant

We can now create a macro method on Obj which compiles to different calls depending on arguments:

// The macro must be vararg, since the functions take different amount of arguments
macro void Obj.func(&self, ...)
{
    // Does it have a single argument of type 'Foo*'?
    $if $vacount == 1 && @typeis($vaarg(0), Foo*):
        // If so, dispatch to func2
        return self.func2($vaarg(0));
    $else
        // Otherwise, dispatch all varargs to func1 
        return self.func1($vasplat());
    $endif
}

The above would make it possible to use both obj.func("Abc", "Def") and obj.func(&my_foo).

Platform support

Q: How do I use WASM?

A: Currently WASM support is really incomplete.

You can try this:

compile --reloc=none --target wasm32 -g0 --link-libc=no --no-entry mywasm.c3

Unless you are compiling with something that already runs initializers, you will need to call the function runtime::wasm_initialize() early in your main or call it externally (for example from JS) with the name _initialize(), otherwise globals might not be set up properly.

This should yield an out.wasm file, but there is no CI running on the WASM code and no one is really using it yet, so the quality is low.

We do want WASM to be working really well, so if you’re interested in writing something in WASM - please reach out to the developer team and we’ll help you get things working.

Q: How do I conditionally compile based on compiler flags?

A: You can pass feature flags on the command line using -D SOME_FLAG or using the features key in the project file.

You can then test for them using $feature(FLAG_NAME):

int my_var @if($feature(USE_MY_VAR));

fn int test()
{
    $if $feature(USE_MY_VAR):
        return my_var;
    $else
        return 0;
    $endif
}

Syntax & Language design

Q: Why does C3 require that types start with upper case but functions with lower case?

A: C grammar is ambiguous. Usually compilers implement either the so-called lexer hack, but other methods exist as well, such as delayed parsing. It is also possible to make it unambiguous using infinite lookahead.

However, all of those methods makes it much harder for tools to search the source code accurately. By making the naming convention part of the grammar, C3 is straightforward to parse with a single token lookahead.

Q: Why are there no closures and only non-capturing lambdas?

A: With closures, life-time management of captured variables become important to track. This can become arbitrarily complex, and without RAII or any other memory management technique it is fairly difficult to make code safe. Non-capturing lambdas on the other hand are fairly safe.

Q: Why is it called C3 and not something better?

A: Naming a programming language isn’t easy. Most programming languages have pretty bad names, and while C3 isn’t the best, no real better alternative has come along.

Q: Why are there no static methods?

A: Static methods creates a tension between free functions in modules and functions namespaced by the type. Java for example, resolves this by not having free functions at all. C3 solves it by not having static methods (nor static variables). Consequently more functions becomes part of the module rather than the type.

Q: Why do macros with trailing bodies require ; at the end?

@test()
{
   // code
}; // <- Why is this needed?

A: All macro calls, including those with a trailing body, are expressions, so it would be ambiguous to let them terminate a statement without a much more complicated grammar. An example:

// How can the parser determine that the 
// last `}` ends the expression? (And does it?)
int a = @test() {} + @test() {}
*b = 123;
// In comparison, the grammar for this is easy:
int a = @test() {} + @test() {};
*b = 123;

C3 strives for a simple grammar, and so the trade-off having to use ; was a fairly low prices to pay for this feature.

Q: Why do only macros have ref arguments?

A: Ref arguments break the general contract of a call: what looks like a pass-by-value may suddenly be passed by reference. This makes code reading much harder, but is popular in C++ because: (1) No need for -> (2) It prevents passing of null pointers. Neither of these are required in C3 (-> is not needed, and & in the parameter contract will prevent nulls).

This leaves the case where there is a benefit for the user to create an implicit & on a call. These cases should be rare, and in C3, it’s not a problem creating a wrapper macro in those cases.

Note that macros that violate the call contract, such as ones using ref arguments, need to have the @ name prefix to indicate that it is indeed possibly violating “value is passed by value” semantics.


title: All Features description: A list of all features of C3. sidebar:

order: 701

Here is a summary of all the features of C3 and changes from C

Symbols and literals

Changes relating to literals, identifiers etc.

Added

  1. 0o prefix for octal.
  2. 0b prefix for binary.
  3. Optional “_” as digit separator.
  4. Hexadecimal byte data, e.g x"abcd".
  5. Base64 byte data, e.g. b64"QzM=".
  6. Type name restrictions (PascalCase).
  7. Variable and function name restrictions (must start with lower case letter).
  8. Constant name restrictions (no lower case).
  9. Character literals may be 2, 4, 8, 16 bytes long. (2cc, 4cc etc).
  10. Raw string literals between “`”.
  11. \e escape character.
  12. Source code must be UTF-8.
  13. Assumes \n for new row \r is stripped from source.
  14. Bit-width integer and float suffixes: u8/i8/u16/i16/… f32/f64/…
  15. The null literal is a pointer value of 0.
  16. The true and false are boolean constants true and false.

Removed

  1. Trigraphs / digraphs.
  2. 0123-style octal.
  3. z, LL and ULL suffixes.

Built-in types

Added

  1. Type declaration is left to right: int[4]*[2] a; instead of int (*a[2])[4];
  2. Simd vector types using [<>] syntax, e.g. float[<4>], use [<*>] for inferred length.
  3. Slice type built in, using [] suffix, e.g. int[]
  4. Distinct types, similar to a typedef but forms a new type. (Example: the String type is a distinct char[])
  5. Built-in 128-bit integer on all platforms.
  6. char is an unsigned 8-bit integer. ichar is its signed counterpart.
  7. Well-defined bitwidth for integer types: ichar/char (8 bits), short/ushort (16 bits), int/uint (32 bits), long/ulong (64 bits), int128/uint128 (128 bits)
  8. Pointer-sized iptr and uptr integers.
  9. isz and usz integers corresponding to the size_t bitwidth.
  10. Optional types are formed using the ! suffix.
  11. bool is the boolean type.
  12. typeid is a unique type identifier for a type, it can be used at runtime and compile time.
  13. any contains a typeid and void* allowing it to act as a reference to any type of value.
  14. anyfault holds any fault value (see below).

Changed

  1. Inferred array type uses [*] (e.g. int[*] x = { 1, 2 };).
  2. Flexible array member uses [*].

Removed

  1. The spiral rule type declaration (see above).
  2. Complex types
  3. size_t, ptrdiff_t (see above).
  4. Array types do not decay.

Types

Added

  1. bitstruct a struct with a container type allowing precise control over bit-layout, replacing bitfields and enum masks.
  2. fault an enum type with unique values which are used together with optional.
  3. Vector types.
  4. Optional types.
  5. enum allows a set of unique constants to be associated with each enum value.
  6. Compile time reflection and limited runtime reflection on types (see “Reflection”)
  7. All types have a typeid property uniquely referring to that particular type.
  8. Distinct types, which are similar to aliases, but represent distinctly different types.
  9. Types may have methods. Methods can be added to any type, including built-in types.
  10. Subtyping: using inline on a struct member allows a struct to be implicitly converted to this member type and use corresponding methods.
  11. Using inline on a distinct type allows it to be implicitly converted to its base type (but not vice versa).
  12. Types may add operator overloading to support foreach and subscript operations.
  13. Generic types through generic modules, using (< ... >) for the generic parameter list (e.g. List(<int>) list;).
  14. Interface types, any types which allows dynamic invocation of methods.

Changed

  1. typedef is replaced by def and has somewhat different syntax (e.g. def MyTypeAlias = int;).
  2. Function pointer syntax is prefix fn followed by a regular function declaration without the function name.

Removed

  1. Enums, structs and unions no longer have distinct namespaces.
  2. Enum, struct and union declarations should not have a trailing ‘;’
  3. Inline typedef is not allowed. def can only be used at the top level.
  4. Anonymous structs are not allowed.
  5. Type qualifiers are all removed, including const, restrict, volatile
  6. Function pointers types cannot be used “raw”, but must always be used through a type alias.

Introspection

Compile time type methods: alignof, associated, elements, extnameof, inf, inner, kindof, len, max, membersof, min, nan, names, params, returns, sizeof, typeid, values, qnameof, is_eq, is_ordered.

Runtime type methods: inner, kind, len, names, sizeof.

Expressions

Added

  1. Expression block using {| ... |}. Somewhat similar to GCC statement expressions.
  2. Array initializers may use ranges. (e.g. int[256] x = { [0..128] = 1 })
  3. ?: operator, returning the first value if it can be converted to a boolean true, otherwise the second value is returned.
  4. Orelse ?? returning the first value if it is a result, the second if the first value was an optional value.
  5. Rethrow ! suffix operator with an implicit return the value if it was an optional value.
  6. Dynamic calls, allowing calls to be made on the any and interfaces dispatched using a dynamic mechanism.
  7. Create a slice using a range subscript (e.g. a[4..8] to form a slice from element 4 to element 8).
  8. Two range subscript methods: [start..inclusive_end] and [start:length]. Start, end and length may be omitted for default values.
  9. Indexing from end: slices, arrays and vectors may be indexed from the end using ^. ^1 represents the last element. This works for ranges as well.
  10. Range assignment, assign a single value to an entire range e.g. a[4..8] = 1;.
  11. Slice assignment, copy one range to the other range e.g. a[4..8] = b[8..12];.
  12. Array, vector and slice comparison: == can be used to make an element-wise comparison of two containers.
  13. ? suffix operator turns a fault into an optional value.
  14. !! suffix panics if the value is an optional value.
  15. $defined(...) returns true if the last expression is defined (sub-expressions must be valid).
  16. $and(...) $or(...) perform compile time logic, and may also be written as &&& and ||| respectively.
  17. It does not check any elements after the first false value found for $and() also written as &&&. To check both conditions are false use: !false_condition &&& !false_condition.
  18. It does not check any values after the first true found for $or() also written as |||.
  19. Lambdas (anonymous functions) may be defined, they work just like functions and do not capture any state.
  20. Simple bitstructs (only containing booleans) may be manipulated using bit operations & ^ | ~ and assignment.
  21. Structs may implicitly convert to their inline member if they have one.
  22. Pointers to arrays may implicitly convert to slices.
  23. Any pointer may implicitly convert to an any with type being the pointee.
  24. Optional values will implicitly invoke “flatmap” on an expression it is a subexpression of.
  25. Swizzling for arrays and vectors.

Changed

  1. Compound literals use Type { ... } rather than (Type) { ... }
  2. Operator precedence of bit operations is higher than + and -.
  3. Well defined-evaluation order: left-to-right, assignment after expression evaluation.
  4. sizeof is $sizeof and only works on expressions. Use Type.sizeof on types.
  5. alignof is $alignof for expressions. Types use Type.alignof.
  6. Narrowing conversions are only allowed if all sub-expressions is as small or smaller than the type.
  7. Widening conversions are only allowed on simple expressions (i.e. most binary expressions and some unary may not be widened)

Removed

  1. The comma operator is removed.

Cast changes

Functions

Added

  1. Functions may be invoked using named arguments, the name is the dot-prefixed parameter name, e.g. foo(name: a, len: 2).
  2. Typed varargs are declared Type... argument, and will take 0 or more arguments of the given type.
  3. It is possible to “splat” an array or slice into the location of a typed vararg using ...: foo(a, b, ...list)
  4. any varargs are declared argument..., it can take 0 or more arguments of any type which are implicitly converted to the any type.
  5. The function declaration may have @inline or @noinline as a default.
  6. Using @inline or @noinline on a function call expression will override the function default.
  7. Type methods are functions defined in the form fn void Foo.my_method(Foo* foo) { ... }, they can be invoked using dot syntax.
  8. Type methods may be attached to any type, even arrays and vectors.
  9. Error handling using optional return types.

Changed

  1. Function declarations use the fn prefix.

Removed

  1. Functions with C-style varargs may be called, and declared as external functions, but not used for C3 functions.

Attributes

C3 adds a long range of attributes in the form @name(...). It is possible to create custom attribute groups using def (e.g. def MyAttribute(usz align) = { @aligned(align) @weak };) which groups certain attributes. Empty attribute groups are permitted.

The complete list: @align, @benchmark, @bigendian, @builtin, @callconv, @deprecated, @dynamic, @export, @extern, @if, @inline, @interface, @littleendian, @local, @maydiscard, @naked, @nodiscard, @noinit, @noreturn, @nostrip, @obfuscate, @operator, @overlap, @priority, @private, @public, @pure, @reflect, @section, @test, @used, @unused.

Declarations

Added

  1. var declaration for type inferred variables in macros. E.g. var a = some_value;
  2. var declaration for new type variables in macros. E.g. var $Type = int;
  3. var declaration for compile time mutable variables in function and macros. E.g. var $foo = 1;
  4. const declarations may be untyped. Such constants are not stored in the resulting binary.

Changed

  1. tlocal declares a variable to be thread local.
  2. static top level declarations are replaced with @local. (static in functions is unchanged)

Removed

  1. restrict removed.
  2. atomic should be replaced by atomic load/store operations.
  3. volatile should be replaced by volatile load/store operations.

Statements

Added

  1. Match-style variant of the switch statement, allows each case to hold an expression to test.
  2. Switching over type with typeid.
  3. Unpack any to the underlying type with an any-switch.
  4. nextcase to fallthrough to the next case.
  5. nextcase <expr> to jump to the case with the expression value (this may be an expression evaluated at runtime).
  6. nextcase default to jump to the default clause.
  7. Labelled while/do/for/foreach to use with break nextcase and continue.
  8. foreach to iterate over arrays, vectors, slices and user-defined containers using operator overloading.
  9. foreach_r to iterate in reverse.
  10. foreach / foreach_r may take the element by value or reference. The index may optionally be provided.
  11. $if, $switch, $for, $foreach statements executing at compile time.
  12. $echo printing a message at compile time.
  13. $assert compile time assert.
  14. defer statement to execute statements at scope exit.
  15. defer catch and defer try similar to defer but executes only on optional exit or regular exit of scope respectively.
  16. do statements may omit while, behaving same as while (0)
  17. if may have a label. Labelled if may be exited using labelled break.
  18. asm blocks for inline assembly.
  19. if-try statements allows you to run code where an expression is a result.
  20. if-catch statements runs code on fault. It can be used to implicitly unwrap variables.
  21. Exhaustive switching on enums.

Changed

  1. Switch cases will have implicit break, rather than implicit fallthrough.
  2. assert is an actual statement and may take a string or a format + arguments.
  3. static_assert is $assert and is a statement.

Removed

  1. goto removed, replaced by labelled break, continue and nextcase.

Compile time evaluation

Added

  1. @if(cond) to conditionally include a struct/union field, a user-defined type etc.
  2. Compile time variables with $ prefix e.g. $foo.
  3. $if...$else...$endif and $switch...$endswitch inside of functions to conditionally include code.
  4. $for and $foreach to loop over compile time variables and data.
  5. $typeof determines an expression type without evaluating it.
  6. Type properties may be accessed at compile time.
  7. $define returns true if the variable, function or type exists.
  8. $error emits an error if encountered.
  9. $embed includes a file as binary data.
  10. $include includes a file as text.
  11. $exec includes the output of a program as code.
  12. $evaltype takes a compile time string and turns it into a type.
  13. $eval takes a string and turns it into an identifier.
  14. $extnameof turns an identifier into its string external name.
  15. $nameof turns an identifier into its local string name.
  16. $qnameof turns an identifier into its local string name with the module prefixed.
  17. Compile time constant values are always compile time folded for arithmetic operations and casts.
  18. $$FUNCTION returns the current function as an identifier.

Changed

  1. #define for constants is replaced by untyped constants, e.g. #define SOME_CONSTANT 1 becomes const SOME_CONSTANT = 1;.
  2. #define for variable and function aliases is replaced by def, e.g. #define native_foo win32_foo becomes def native_foo = win32_foo;
  3. In-function #if...#else..#endif is replaced by $if, #if...#elif...#endif is replaced by $switch.
  4. For converting code into a string use $stringify.
  5. Macros for date, line etc are replaced by $$DATE, $$FILE, $$FILEPATH, $$FUNC, $$LINE, $$MODULE, $$TIME.

Removed

  1. Top level #if...#endif does not have a counterpart. Use @if instead.
  2. No #include directives, $include will include text but isn’t for the same use.

Macros

Added

  1. macro for defining macros.
  2. “Function-like” macros have no prefix and has only regular parameters or type parameters.
  3. “At”-macros are prefixed with @ and may also have compile time values, expression and ref parameters, and may have a trailing body.
  4. Type parameters have the prefix $ and conform to the type naming standard (“$TypeFoo”).
  5. “ref” parameters are declared using with a & prefix operator. This is similar to C++ ref parameters.
  6. Expression parameters are unevaluated expressions, this is similar to arguments to #define.
  7. Compile time values have a $ prefix and must contain compile time constant values.
  8. Any macro that evaluates to a constant result can be used as if it was the resulting constant.
  9. Macros may be recursively evaluated.
  10. Macros are inlined at the location where they are invoked.
  11. Unless resulting in a single constant, macros implicitly create a runtime scope.

Removed

  1. No #define macros.
  2. Macros cannot be incomplete statements.

Features provided by builtins

Some features are provided by builtins, and appears as normal functions and macros in the standard library but nonetheless provided unique functionality:

  1. @likely(...) / @unlikely(...) on branches affects compilation optimization.
  2. @anycast(...) casts an any with an optional result.
  3. unreachable(...) marks a path as unreachable with a panic in safe mode.
  4. unsupported(...) similar to unreachable but for functionality not implemented.
  5. @expect(...) expect a certain value with an optional probability for the optimizer.
  6. @prefetch(...) prefect a pointer.
  7. swizzle(...) swizzles a vector.
  8. @volatile_load(...) and @volatile_store(...) volatile load/store.
  9. @atomic_load(...) and @atomic_store(...) atomic load/store.
  10. compare_exchange(...) atomic compare exchange.
  11. Saturating add, sub, mul, shl on integers.
  12. Vector reduce operations: add, mul, and, or, xor, max, min.

Modules

  1. Modules are defined using module <name>. Where name is on the form foo::bar::baz
  2. Modules can be split into an unlimited number of module sections, each starting with the same module name declaration.
  3. The import statement imports a given module.
  4. Each module section has its own set of import statements.
  5. Importing a module gives access to the declarations that are @public.
  6. Declarations are default @public, but a module section may set a different default (e.g. module my_module @private;)
  7. @private means the declaration is only visible in the module.
  8. @local means only visible to the current module section.
  9. Imports are recursive. For example, import my_lib will implicitly also import my_lib::net.
  10. Multiple imports may be specified with the same import, e.g. import std::net, std::io;.
  11. Generic modules have a set of parameters after the module name module arr(<Type, LEN>);
  12. Generic modules are not type checked until any of its types, functions or globals are instantiated.

Contracts

  1. Doc contracts (starting with <*) are parsed.
  2. The first part, up until the first @ directive on a new line, is ignored.
  3. The @param directive for pointer arguments may define usage constraints [in] [out] and [inout].
  4. Pointer argument constraints may add a & prefix to indicate that they may not be null, e.g. [&inout].
  5. Contracts may be attached to generic modules, functions and macros.
  6. @require directives are evaluated given the arguments provided. Failing them may be a compile time or runtime error.
  7. The @ensure directive is evaluated at exit - if the return is a result and not an optional.
  8. return can be used as a variable identifier inside of @ensure, and holds the return value.
  9. @return! optionally lists the errors used. This will be checked at compile time.
  10. @pure says that no writing to globals is allowed inside and only @pure functions may be called.

Benchmarking

  1. Benchmarks are indicated by @benchmark.
  2. Marking a module section @benchmark makes all functions inside of it implicitly benchmarks.
  3. Benchmarks are usually not compiled.
  4. Benchmarks are instead only run by the compiler on request.

Testing

  1. Tests are indicated by @test.
  2. Marking a module section @test makes all functions inside of it implicitly tests.
  3. Tests are usually not compiled.
  4. Tests are instead only run by the compiler on request.

Safe / fast

Compilation has two modes: “safe” and “fast”. Safe will insert checks for out-of-bounds access, null-pointer deref, shifting by negative numbers, division by zero, violation of contracts and asserts.

Fast will assume all of those checks can be assumed to always pass. This means that unexpected behaviour may result from violating those checks. It is recommended to develop in “safe” mode.

If debug symbols are available, C3 will produce a stack trace in safe mode where an error occurs.


title: Comparisons With Other Languages description: How C3 compares to other languages sidebar:

order: 701

An important question to answer is “How does C3 compare to other similar programming languages?”. Here is an extremely brief (and not yet complete) overview.

C

As C3 is an evolution of C, the languages are quite similar. C3 adds features, but also removes a few.

In C3 but not in C
In C but not in C3

C++

C++ is a complex object oriented “almost superset” of C. It tries to be everything to everyone, while squeezing this into a C syntax. The language is well known for its many pitfalls and quirky corners – as well as its long compile times.

C3 is in many ways different from C++ in the same way that C is different from C++, but the semantic macro system and the generics close the gap in terms of writing reusable generic code. The C3 module system and error handling is also very different from how C++ does things.

In C++ but not in C3
In C3 but not in C++

Rust

Rust is a safe systems programming language. While not quite as complex as C++, it is still a feature rich programming language with semantic macros, traits and pattern matching to mention a few.

Error handling is handled using Result and Optional which is similar to how C3 works.

C3 compares to Rust much like C, although the presence of built-in slices and strings reduces the places where C3 is unsafe. Rust provides arrays and strings, but they are not built in.

In Rust but not in C3
In C3 but not in Rust

Zig

Zig is a systems programming language with extensive compile time execution to enable polymorphic functions and parameterized types. It aims to be a C replacement.

Compared to C3, Zig tries to be a completely new language in terms of syntax and feel. C3 uses macros to a modest degree where it is more pervasive in Zig, and does not depart from C to the same degree. Like Rust, it features slices as a first class type. The standard library uses an explicit allocator to allow it to work with many different allocation strategies.

Zig is a very ambitious project, aiming to support as many types of platforms as possible.

In Zig but not in C3
In C3 but not in Zig

Jai

Jai is a programming language aimed at high performance game programming. It has an extensive compile time meta programming functionality, even to the point of being able to run programs at compile time. It also has compile-time polymorphism, a powerful macro system and uses an implicit context system to switch allocation schemes.

In Jai but not in C3
In C3 but not in Jai

Odin

Odin is a language built for high performance but tries to remain a simple language to learn. Superficially the syntax shares much with Jai, and some of Jai’s features things – like an implicit context – also shows up in Odin. In contrast with both Jai and Zig, Odin uses only minimal compile time evaluation and instead only relies on parametric polymorphism to ensure reuse. It also contains conveniences, like maps and arrays built into the language. For error handling it relies on Go style tuple returns.

In Odin but not in C3
In C3 but not in Odin

D

D is an incredibly extensive language, it covers anything C++ does and adds much more. D manages this with much fewer syntactic quirks than C++. It is a strong, feature-rich language.

In D but not in C3

+ Many, many more features.

In C3 but not in D

title: Changes From C description: Changes From C sidebar:

order: 702

Although C3 is trying to improve on C, this does not only mean addition of features, but also removal, or breaking changes:

No mandatory header files

There is a C3 interchange header format for declaring interfaces of libraries, but it is only used for special applications.

Removal of the old C macro system

The old C macro system is replaced by a new C3 macro system.

Import and modules

C3 uses module imports instead of header includes to link modules together.

Member access using . even for pointers

The -> operator is removed, access uses dot for both direct and pointer access. Note that this is just single access: to access a pointer of a pointer (e.g. int**) an explicit dereference would be needed.

Different operator precedence

Notably bit operations have higher precedence than +/-, making code like this: a & b == c evaluate like (a & b) == c instead of C’s a & (b == c). See the page about precedence rules.

Removal of the const type qualifier

The const qualifier is only retained for actual constant variables. C3 uses a special type of post condition for functions to indicate that they do not alter in parameters.

<*
 This function ensures that foo is not changed in the function.
 @param [in] foo
 @param [out] bar
*>
fn void test(Foo* foo, Bar* bar)
{
    bar.y = foo.x;
    // foo.x = foo.x + 1 - compile time error, can't write to 'in' param.
    // int x = bar.y     - compile time error, can't read from an 'out' param.
}

Rationale: const correctness requires littering const across the code base. Although const is useful, it provides weaker guarantees that it appears.

Fixed arrays do not decay and have copy semantics

C3 has three different array types. Variable arrays and slices decay to pointers, but fixed arrays are value objects and do not decay.

int[3] a = { 1, 2, 3 };
int[4]* b = &a; // No conversion
int* c = a; // ERROR
int* d = &a; // Valid implicit conversion
int* e = b; // Valid implicit conversion
int[3] f = a; // Copy by value!
Removal of multiple declaration syntax with initialization

Only a single declaration with initialization is allowed per statement in C3:

int i, j = 1; // ERROR
int a = 1;    // Ok
int b, c;     // Ok

In conditionals, a special form of multiple declarations are allowed but each must then provide its type:

for (int i = 0, int j = 1; i < 10; i++, j++) { ... }
Integer promotions rules and safe signed-unsigned comparisons

Promotion rules for integer types are different from C. C3 allows implicit widening only where there is only a single way to widen the expression. To explain the latter: take the case of long x = int_val_1 + int_val_2. In C this would widen the result of the addition: long x = (long)(int_val_1 + int_val_2), but there is another possible way to widen: long x = (long)int_val_1 + (long)int_val_2. so in this case, the widening is disallowed. However, long x = int_val_1 is unambiguous, so C3 permits it just like C (read more on the conversion page.

C3 also adds safe signed-unsigned comparisons: this means that comparing signed and unsigned values will always yield the correct result:

// The code below would print "Hello C3!" in C3 and "Hello C!" in C.
int i = -1;
uint j = 1;
if (i < j)
{
    printf("Hello C3!\n");
}
else
{
    printf("Hello C!\n");
}
Goto removed

goto is removed and replaced with labelled break and continue together with the nextcase statement that allows you to jump between cases in a switch statement.

Rationale: It is very difficult to make goto work well with defer and implicit unwrapping of optional results. It is not just making the compiler harder to write, but the code is harder to understand as well. The replacements together with defer cover many if not all usages of goto in regular code.

Implicit break in switches

Empty case statements have implicit fall through in C3, otherwise the nextcase statement is needed nextcase can also be used to jump to any other case statement in the switch.

switch (h)
{
    case 1:
        a = 1;
        nextcase; // Fall through
    case 2:
        b = 123;
    case 3:
        a = 2;
        nextcase 2; // Jump to case 2
    default:
        a = 111;
}
Locals variables are implicitly zeroed

In C global variables are implicitly zeroed out, but local variables aren’t. In C3 local variables are zeroed out by default, but may be explicitly undefined (using the @noinit attribute) if you wish to match the C behaviour.

Rationale for this change
Compound literal syntax changed
// C style:
call_foo((Foo) { 1, 2, 3 });

// C++ style (1):
call_foo(Foo(1, 2, 3));

// C++ style (2):
call_foo(Foo { 1, 2, 3 });

// C3:
call_foo(Foo { 1, 2, 3 } );

// C3 with inference:
call_foo({ 1, 2, 3 });
Bitfields replaced by bitstructs

Bitfields are replaced by bitstructs that have a well-defined encapsulating type, and an exact bit layout.

// C
struct Foo
{
    int a : 3;
    unsigned b : 4;
    MyEnum c : 7;
};

struct Flags
{
    bool has_hyperdrive : 1;
    bool has_tractorbeam : 1;
    bool has_plasmatorpedoes : 1;
}    

// C3
bitstruct Foo : short
{  
    int a : 0..2;
    uint b : 3..6;
    MyEnum c : 7..13;
}

// Simple form, only allowed when all fields are bools.
struct Flags : char
{
    bool has_hyperdrive;
    bool has_tractorbeam;
    bool has_plasmatorpedoes;
}
Evaluation order is well-defined

Evaluation order is left-to-right, and in assignment expressions, assignment happens after expression evaluation.

Signed overflow is well-defined

Signed integer overflow always wraps using 2s complement. It is never undefined behaviour.

Octal removed

The old 0777 octal syntax is removed and replaced by a 0o prefix, e.g. 0o777. Strings do not support octal sequences aside from '\0'.


title: Rejected Ideas description: Rejected Ideas sidebar:

order: 703

These are ideas that will not be implemented in C3 with rationale given.

Constructors and destructors

A fundamental concept in C3 is that data is not “active”. This is to say there is no code associated with the data implicitly unlike constructors and destructors in an object oriented language. Not having constructors / destructors prevents RAII-style resource handling, but also allows the code to assume the memory can be freely allocated and initialized as it sees fit, without causing any corruption or undefined behaviour.

There is a fundamental difference between active objects and inert data, each has its advantages and disadvantages. C3 follows the C model, which is that data is passive and does not enforce any behaviour. This has very deep implications on the semantics of the language and adding constructors and destructors would change the language greatly, requiring modification of many parts of the language altering.

For that reason constructors and destructors will not be considered for C3.

Unicode identifiers

The main argument for unicode identifiers is that “it allows people to code in their own language”. However, there is no proof that this actually is used in practice. Furthermore there are practical issues, such as bidirectional text, characters with different code points that are rendered in an identical way etc.

Given the complexity and the lack of actual proven benefit, unicode identifiers will not happen for C3.


title: Grammar description: Grammar sidebar:

order: 999

Keywords

The following are reserved keywords used by C3:

void        bool        char        double
float       float16     int128      ichar
int         iptr        isz         long
short       uint128     uint        ulong
uptr        ushort      usz         float128
any         anyfault    typeid      assert
asm         bitstruct   break       case
catch       const       continue    def
default     defer       distinct    do
else        enum        extern      false
fault       for         foreach     foreach_r
fn          tlocal      if          inline
import      macro       module      nextcase
null        return      static      struct
switch      true        try         union
var         while
$alignof    $assert     $case       $default    
$defined    $echo       $embed      $exec
$else       $endfor     $endforeach $endif      
$endswitch  $eval       $evaltype   $error      
$extnameof  $for        $foreach    $if         
$include    $nameof     $offsetof   $qnameof    
$sizeof     $stringify  $switch     $typefrom   
$typeof     $vacount    $vatype     $vaconst    
$varef      $vaarg      $vaexpr     $vasplat

The following attributes are built in:

@align        @benchmark  @bigendian  @builtin
@cdecl        @deprecated @dynamic    @export
@extern       @extname    @inline     @interface
@littleendian @local      @maydiscard @naked
@nodiscard    @noinit     @noinline   @noreturn
@nostrip      @obfuscate  @operator   @overlap
@packed       @priority   @private    @public
@pure         @reflect    @section    @stdcall
@test         @unused     @used       @veccall
@wasm         @weak       @winmain

The following constants are defined:

$$BENCHMARK_FNS  $$BENCHMARK_NAMES $$DATE
$$FILE           $$FILEPATH        $$FUNC
$$FUNCTION       $$LINE            $$LINE_RAW
$$MODULE         $$TEST_FNS        $$TEST_NAMES
$$TIME

Yacc grammar

%{

#include <stdio.h>
#define YYERROR_VERBOSE
int yydebug = 1;
extern char yytext[];
extern int column;
int yylex(void);
void yyerror(char *s);
%}

%token IDENT HASH_IDENT CT_IDENT CONST_IDENT
%token TYPE_IDENT CT_TYPE_IDENT
%token AT_TYPE_IDENT AT_IDENT CT_INCLUDE
%token STRING_LITERAL INTEGER
%token INC_OP DEC_OP SHL_OP SHR_OP LE_OP GE_OP EQ_OP NE_OP
%token AND_OP OR_OP MUL_ASSIGN DIV_ASSIGN MOD_ASSIGN ADD_ASSIGN
%token SUB_ASSIGN SHL_ASSIGN SHR_ASSIGN AND_ASSIGN
%token XOR_ASSIGN OR_ASSIGN VAR NUL ELVIS NEXTCASE ANYFAULT
%token MODULE IMPORT DEF EXTERN
%token CHAR SHORT INT LONG FLOAT DOUBLE CONST VOID USZ ISZ UPTR IPTR ANY
%token ICHAR USHORT UINT ULONG BOOL INT128 UINT128 FLOAT16 FLOAT128 BFLOAT16
%token TYPEID BITSTRUCT STATIC BANGBANG AT_CONST_IDENT HASH_TYPE_IDENT
%token STRUCT UNION ENUM ELLIPSIS DOTDOT BYTES

%token CT_ERROR
%token CASE DEFAULT IF ELSE SWITCH WHILE DO FOR CONTINUE BREAK RETURN FOREACH_R FOREACH
%token FN FAULT MACRO CT_IF CT_ENDIF CT_ELSE CT_SWITCH CT_CASE CT_DEFAULT CT_FOR CT_FOREACH CT_ENDFOREACH
%token CT_ENDFOR CT_ENDSWITCH BUILTIN IMPLIES INITIALIZE FINALIZE CT_ECHO CT_ASSERT CT_EVALTYPE CT_VATYPE
%token TRY CATCH SCOPE DEFER LVEC RVEC OPTELSE CT_TYPEFROM CT_TYPEOF TLOCAL
%token CT_VASPLAT INLINE DISTINCT CT_VACONST CT_NAMEOF CT_VAREF CT_VACOUNT CT_VAARG
%token CT_SIZEOF CT_STRINGIFY CT_QNAMEOF CT_OFFSETOF CT_VAEXPR
%token CT_EXTNAMEOF CT_EVAL CT_DEFINED CT_CHECKS CT_ALIGNOF ASSERT
%token ASM CHAR_LITERAL REAL TRUE FALSE CT_CONST_IDENT
%token LBRAPIPE RBRAPIPE HASH_CONST_IDENT

%start translation_unit
%%

path
        : IDENT SCOPE
        | path IDENT SCOPE
        ;

path_const
    : path CONST_IDENT
    | CONST_IDENT
    ;

path_ident
    : path IDENT
    | IDENT
    ;

path_at_ident
    : path AT_IDENT
    | AT_IDENT
    ;

ident_expr
    : CONST_IDENT
    | IDENT
    | AT_IDENT
    ;

local_ident_expr
    : CT_IDENT
        | HASH_IDENT
    ;

ct_call
    : CT_ALIGNOF
    | CT_DEFINED
    | CT_EXTNAMEOF
    | CT_NAMEOF
    | CT_OFFSETOF
    | CT_QNAMEOF
    ;

ct_analyse
    : CT_EVAL
    | CT_SIZEOF
    | CT_STRINGIFY
    ;

ct_arg
    : CT_VACONST
        | CT_VAARG
        | CT_VAREF
        | CT_VAEXPR
    ;

flat_path
    : primary_expr param_path
    | type
    | primary_expr
    ;

maybe_optional_type
    : optional_type
    | empty
    ;

string_expr
    : STRING_LITERAL
    | string_expr STRING_LITERAL
    ;

bytes_expr
    : BYTES
    | bytes_expr BYTES
    ;

expr_block
    : LBRAPIPE opt_stmt_list RBRAPIPE
    ;

base_expr
    : string_expr
    | INTEGER
    | bytes_expr
    | NUL
    | BUILTIN CONST_IDENT
    | BUILTIN IDENT
    | CHAR_LITERAL
    | REAL
    | TRUE
    | FALSE
    | path ident_expr
    | ident_expr
    | local_ident_expr
    | type initializer_list
    | type '.' access_ident
    | type '.' CONST_IDENT
    | '(' expr ')'
    | expr_block
    | ct_call '(' flat_path ')'
    | ct_arg '(' expr ')'
    | ct_analyse '(' expr ')'
    | CT_VACOUNT
    | CT_CHECKS '(' expression_list ')'
    | lambda_decl compound_statement
    ;

primary_expr
    : base_expr
    | initializer_list
    ;

range_loc
    : expr
    | '^' expr
    ;

range_expr
    : range_loc DOTDOT range_loc
    | range_loc DOTDOT
    | DOTDOT range_loc
    | range_loc ':' range_loc
    | ':' range_loc
    | range_loc ':'
    | DOTDOT
    ;


call_inline_attributes
    : AT_IDENT
    | call_inline_attributes AT_IDENT
    ;

call_invocation
    : '(' call_arg_list ')'
    | '(' call_arg_list ')' call_inline_attributes
    ;

access_ident
    : IDENT
    | AT_IDENT
    | HASH_IDENT
    | CT_EVAL '(' expr ')'
    | TYPEID
    ;

call_trailing
    : '[' range_loc ']'
    | '[' range_expr ']'
    | call_invocation
    | call_invocation compound_statement
    | '.' access_ident
    | INC_OP
    | DEC_OP
    | '!'
    | BANGBANG
    ;

call_stmt_expr
    : base_expr
    | call_stmt_expr call_trailing
    ;

call_expr
    : primary_expr
    | call_expr call_trailing
    ;

unary_expr
    : call_expr
    | unary_op unary_expr
    ;

unary_stmt_expr
    : call_stmt_expr
    | unary_op unary_expr
    ;

unary_op
    : '&'
    | AND_OP
    | '*'
    | '+'
    | '-'
    | '~'
    | '!'
    | INC_OP
    | DEC_OP
    | '(' type ')'
    ;

mult_op
    : '*'
    | '/'
    | '%'
        ;

mult_expr
    : unary_expr
    | mult_expr mult_op unary_expr
    ;

mult_stmt_expr
    : unary_stmt_expr
    | mult_stmt_expr mult_op unary_expr
    ;

shift_op
    : SHL_OP
    | SHR_OP
    ;

shift_expr
    : mult_expr
    | shift_expr shift_op mult_expr
    ;

shift_stmt_expr
    : mult_stmt_expr
    | shift_stmt_expr shift_op mult_expr
    ;


bit_op
        : '&'
        | '^'
        | '|'
        ;

bit_expr
    : shift_expr
    | bit_expr bit_op shift_expr
    ;

bit_stmt_expr
    : shift_stmt_expr
    | bit_stmt_expr bit_op shift_expr
    ;

additive_op
    : '+'
    | '-'
        ;

additive_expr
    : bit_expr
    | additive_expr additive_op bit_expr
    ;

additive_stmt_expr
    : bit_stmt_expr
    | additive_stmt_expr additive_op bit_expr
    ;

relational_op
    : '<'
    | '>'
    | LE_OP
    | GE_OP
    | EQ_OP
    | NE_OP
    ;

relational_expr
    : additive_expr
    | relational_expr relational_op additive_expr
    ;

relational_stmt_expr
    : additive_stmt_expr
    | relational_stmt_expr relational_op additive_expr
    ;

rel_or_lambda_expr
    : relational_expr
    | lambda_decl IMPLIES relational_expr
    ;

and_expr
    : relational_expr
    | and_expr AND_OP relational_expr
    ;

and_stmt_expr
    : relational_stmt_expr
    | and_stmt_expr AND_OP relational_expr
    ;

or_expr
    : and_expr
    | or_expr OR_OP and_expr
    ;

or_stmt_expr
    : and_stmt_expr
    | or_stmt_expr OR_OP and_expr
    ;

or_expr_with_suffix
    : or_expr
    | or_expr '?'
    | or_expr '?' '!'
    ;

or_stmt_expr_with_suffix
    : or_stmt_expr
    | or_stmt_expr '?'
    | or_stmt_expr '?' '!'
    ;

ternary_expr
    : or_expr_with_suffix
    | or_expr '?' expr ':' ternary_expr
    | or_expr_with_suffix ELVIS ternary_expr
    | or_expr_with_suffix OPTELSE ternary_expr
    | lambda_decl implies_body
    ;

ternary_stmt_expr
    : or_stmt_expr_with_suffix
    | or_stmt_expr '?' expr ':' ternary_expr
    | or_stmt_expr_with_suffix ELVIS ternary_expr
    | or_stmt_expr_with_suffix OPTELSE ternary_expr
    | lambda_decl implies_body
    ;

assignment_op
    : '='
    | ADD_ASSIGN
    | SUB_ASSIGN
    | MUL_ASSIGN
    | DIV_ASSIGN
    | MOD_ASSIGN
    | SHL_ASSIGN
    | SHR_ASSIGN
    | AND_ASSIGN
    | XOR_ASSIGN
    | OR_ASSIGN
    ;

empty
    :
    ;

assignment_expr
        : ternary_expr
        | CT_TYPE_IDENT '=' type
        | unary_expr assignment_op assignment_expr
        ;
assignment_stmt_expr
        : ternary_stmt_expr
        | CT_TYPE_IDENT '=' type
        | unary_stmt_expr assignment_op assignment_expr
        ;

implies_body
    : IMPLIES expr
    ;

lambda_decl
    : FN maybe_optional_type fn_parameter_list opt_attributes
    ;

expr_no_list
    : assignment_stmt_expr
    ;

expr
    : assignment_expr
    ;


constant_expr
    : ternary_expr
    ;

param_path_element
    : '[' expr ']'
    | '[' expr DOTDOT expr ']'
    | '.' IDENT
    ;

param_path
    : param_path_element
    | param_path param_path_element
    ;

arg    : param_path '=' expr
    | type
    | param_path '=' type
    | expr
    | CT_VASPLAT '(' range_expr ')'
    | CT_VASPLAT '(' ')'
    | ELLIPSIS expr
    ;

arg_list
    : arg
    | arg_list ',' arg
    ;

call_arg_list
    : arg_list
    | arg_list ';'
    | arg_list ';' parameters
    | ';'
    | ';' parameters
    | empty
    ;

opt_arg_list_trailing
    : arg_list
    | arg_list ','
    | empty
    ;

enum_constants
    : enum_constant
    | enum_constants ',' enum_constant
    ;

enum_list
    : enum_constants
    | enum_constants ','
    ;

enum_constant
    : CONST_IDENT
    | CONST_IDENT '(' arg_list ')'
    | CONST_IDENT '(' arg_list ',' ')'
    ;

identifier_list
    : IDENT
    | identifier_list ',' IDENT
    ;

enum_param_decl
    : type
    | type IDENT
    | type IDENT '=' expr
    ;

base_type
    : VOID
    | BOOL
    | CHAR
    | ICHAR
    | SHORT
    | USHORT
    | INT
    | UINT
    | LONG
    | ULONG
    | INT128
    | UINT128
    | FLOAT
    | DOUBLE
    | FLOAT16
    | BFLOAT16
    | FLOAT128
    | IPTR
    | UPTR
    | ISZ
    | USZ
    | ANYFAULT
    | ANY
    | TYPEID
    | TYPE_IDENT
    | path TYPE_IDENT
    | CT_TYPE_IDENT
    | CT_TYPEOF '(' expr ')'
    | CT_TYPEFROM '(' constant_expr ')'
    | CT_VATYPE '(' constant_expr ')'
    | CT_EVALTYPE '(' constant_expr ')'
    ;

type
    : base_type
    | type '*'
    | type '[' constant_expr ']'
    | type '[' ']'
    | type '[' '*' ']'
    | type LVEC constant_expr RVEC
    | type LVEC '*' RVEC
    ;

optional_type
    : type
    | type '!'
    ;

local_decl_after_type
    : CT_IDENT
    | CT_IDENT '=' constant_expr
    | IDENT opt_attributes
    | IDENT opt_attributes '=' expr
    ;

local_decl_storage
    : STATIC
    | TLOCAL
    ;

decl_or_expr
    : var_decl
    | optional_type local_decl_after_type
    | expr
    ;

var_decl
    : VAR IDENT '=' expr
    | VAR CT_IDENT '=' expr
    | VAR CT_IDENT
    | VAR CT_TYPE_IDENT '=' type
    | VAR CT_TYPE_IDENT
    ;

initializer_list
    : '{' opt_arg_list_trailing '}'
    ;

ct_case_stmt
        : CT_CASE constant_expr ':' opt_stmt_list
        | CT_CASE type ':' opt_stmt_list
        | CT_DEFAULT ':' opt_stmt_list
        ;

ct_switch_body
    : ct_case_stmt
        | ct_switch_body ct_case_stmt
        ;

ct_for_stmt
        : CT_FOR '(' for_cond ')' opt_stmt_list CT_ENDFOR
    ;

ct_foreach_stmt
    : CT_FOREACH '(' CT_IDENT ':' expr ')' opt_stmt_list CT_ENDFOREACH
    | CT_FOREACH '(' CT_IDENT ',' CT_IDENT ':' expr ')' opt_stmt_list CT_ENDFOREACH
    ;
ct_switch
        : CT_SWITCH '(' constant_expr ')'
        | CT_SWITCH '(' type ')'
        | CT_SWITCH
       ;

ct_switch_stmt
    : ct_switch ct_switch_body CT_ENDSWITCH
    ;

var_stmt
    : var_decl ';'

decl_stmt_after_type
    : local_decl_after_type
    | decl_stmt_after_type ',' local_decl_after_type
    ;

declaration_stmt
    : const_declaration
    | local_decl_storage optional_type decl_stmt_after_type ';'
    | optional_type decl_stmt_after_type ';'
    ;

return_stmt
    : RETURN expr ';'
    | RETURN ';'
    ;

catch_unwrap_list
    : relational_expr
    | catch_unwrap_list ',' relational_expr
    ;

catch_unwrap
    : CATCH catch_unwrap_list
    | CATCH IDENT '=' catch_unwrap_list
    | CATCH type IDENT '=' catch_unwrap_list
    ;

try_unwrap
    : TRY rel_or_lambda_expr
    | TRY IDENT '=' rel_or_lambda_expr
    | TRY type IDENT '=' rel_or_lambda_expr
    ;

try_unwrap_chain
    : try_unwrap
    | try_unwrap_chain AND_OP try_unwrap
    | try_unwrap_chain AND_OP rel_or_lambda_expr
    ;

default_stmt
    : DEFAULT ':' opt_stmt_list
    ;

case_stmt
    : CASE expr ':' opt_stmt_list
    | CASE expr DOTDOT expr ':' opt_stmt_list
    | CASE type ':' opt_stmt_list
    ;

switch_body
    : case_stmt
    | default_stmt
    | switch_body case_stmt
    | switch_body default_stmt
    ;

cond_repeat
    : decl_or_expr
    | cond_repeat ',' decl_or_expr
    ;

cond
    : try_unwrap_chain
    | catch_unwrap
    | cond_repeat
    | cond_repeat ',' try_unwrap_chain
    | cond_repeat ',' catch_unwrap
    ;

else_part
    : ELSE if_stmt
    | ELSE compound_statement
    ;

if_stmt
    : IF optional_label paren_cond '{' switch_body '}'
    | IF optional_label paren_cond '{' switch_body '}' else_part
    | IF optional_label paren_cond statement
    | IF optional_label paren_cond compound_statement else_part
    ;

expr_list_eos
    : expression_list ';'
    | ';'
    ;

cond_eos
    : cond ';'
    | ';'
    ;

for_cond
    : expr_list_eos cond_eos expression_list
    | expr_list_eos cond_eos
    ;

for_stmt
    : FOR optional_label '(' for_cond ')' statement
    ;

paren_cond
    : '(' cond ')'
    ;

while_stmt
    : WHILE optional_label paren_cond statement
    ;

do_stmt
    : DO optional_label compound_statement WHILE '(' expr ')' ';'
    | DO optional_label compound_statement ';'
    ;

optional_label_target
    : CONST_IDENT
    | empty
    ;

continue_stmt
    : CONTINUE optional_label_target ';'
    ;

break_stmt
    : BREAK optional_label_target ';'
    ;

nextcase_stmt
    : NEXTCASE CONST_IDENT ':' expr ';'
    | NEXTCASE expr ';'
    | NEXTCASE CONST_IDENT ':' type ';'
    | NEXTCASE type ';'
    | NEXTCASE ';'
    ;

foreach_var
    : optional_type '&' IDENT
    | optional_type IDENT
    | '&' IDENT
    | IDENT
    ;

foreach_vars
    : foreach_var
    | foreach_var ',' foreach_var
    ;

foreach_stmt
    : FOREACH optional_label '(' foreach_vars ':' expr ')' statement
    : FOREACH_R optional_label '(' foreach_vars ':' expr ')' statement
    ;

defer_stmt
    : DEFER statement
    | DEFER TRY statement
    | DEFER CATCH statement
    ;

ct_if_stmt
    : CT_IF constant_expr ':' opt_stmt_list CT_ENDIF
    | CT_IF constant_expr ':' opt_stmt_list CT_ELSE opt_stmt_list CT_ENDIF
    ;

assert_expr
    : try_unwrap_chain
    | expr
    ;

assert_stmt
    : ASSERT '(' assert_expr ')' ';'
    | ASSERT '(' assert_expr ',' expr ')' ';'
    ;

asm_stmts
    : asm_stmt
    | asm_stmts asm_stmt
    ;

asm_instr
    : INT
    | IDENT
    | INT '.' IDENT
    | IDENT '.' IDENT
    ;

asm_addr
    : asm_expr
    | asm_expr additive_op asm_expr
    | asm_expr additive_op asm_expr '*' INTEGER
    | asm_expr additive_op asm_expr '*' INTEGER additive_op INTEGER
    | asm_expr additive_op asm_expr shift_op INTEGER
    | asm_expr additive_op asm_expr additive_op INTEGER
    ;

asm_expr
    : CT_IDENT
    | CT_CONST_IDENT
    | IDENT
    | '&' IDENT
    | CONST_IDENT
    | REAL
    | INTEGER
    | '(' expr ')'
    | '[' asm_addr ']'

asm_exprs
    : asm_expr
    | asm_exprs ',' asm_expr
    ;

asm_stmt
    : asm_instr asm_exprs ';'
    | asm_instr ';'
    ;

asm_block_stmt
    : ASM '(' expr ')'
    | ASM '{' asm_stmts '}'
    | ASM '{' '}'
    ;


/* Order here matches compiler */
statement
    : compound_statement
    | var_stmt
    | declaration_stmt
    | return_stmt
    | if_stmt
    | while_stmt
    | defer_stmt
    | switch_stmt
    | do_stmt
    | for_stmt
    | foreach_stmt
    | continue_stmt
    | break_stmt
    | nextcase_stmt
    | asm_block_stmt
        | ct_echo_stmt
    | ct_assert_stmt
        | ct_if_stmt
        | ct_switch_stmt
        | ct_foreach_stmt
        | ct_for_stmt
        | expr_no_list ';'
        | assert_stmt
        | ';'
    ;

compound_statement
    : '{' opt_stmt_list '}'
    ;

statement_list
    : statement
    | statement_list statement
    ;

opt_stmt_list
    : statement_list
    | empty
    ;

switch_stmt
    : SWITCH optional_label '{' switch_body '}'
    | SWITCH optional_label '{' '}'
    | SWITCH optional_label paren_cond '{' switch_body '}'
    | SWITCH optional_label paren_cond '{' '}'
    ;

expression_list
        : decl_or_expr
        | expression_list ',' decl_or_expr
        ;

optional_label
    : CONST_IDENT ':'
    | empty
    ;

ct_assert_stmt
    : CT_ASSERT constant_expr ':' constant_expr ';'
    | CT_ASSERT constant_expr ';'
    | CT_ERROR constant_expr ';'
    ;

ct_include_stmt
    : CT_INCLUDE string_expr ';'
    ;

ct_echo_stmt
    : CT_ECHO constant_expr ';'

bitstruct_declaration
    : BITSTRUCT TYPE_IDENT ':' type opt_attributes bitstruct_body

bitstruct_body
    : '{' '}'
    | '{' bitstruct_defs '}'
    | '{' bitstruct_simple_defs '}'
    ;

bitstruct_defs
    : bitstruct_def
    | bitstruct_defs bitstruct_def
    ;

bitstruct_simple_defs
    : base_type IDENT ';'
    | bitstruct_simple_defs base_type IDENT ';'
    ;

bitstruct_def
    : base_type IDENT ':' constant_expr DOTDOT constant_expr ';'
    | base_type IDENT ':' constant_expr ';'
    ;

static_declaration
    : STATIC INITIALIZE opt_attributes compound_statement
    | STATIC FINALIZE opt_attributes compound_statement
    ;

attribute_name
    : AT_IDENT
    | AT_TYPE_IDENT
    | path AT_TYPE_IDENT
    ;

attribute_operator_expr
    : '&' '[' ']'
    | '[' ']' '='
    | '[' ']'
    ;

attr_param
    : attribute_operator_expr
    | constant_expr
    ;

attribute_param_list
    : attr_param
    | attribute_param_list ',' attr_param
    ;

attribute
    : attribute_name
    | attribute_name '(' attribute_param_list ')'
    ;

attribute_list
    : attribute
    | attribute_list attribute
    ;

opt_attributes
       : attribute_list
        | empty
        ;

trailing_block_param
    : AT_IDENT
    | AT_IDENT '(' ')'
    | AT_IDENT '(' parameters ')'
    ;

macro_params
    : parameters
    | parameters ';' trailing_block_param
    | ';' trailing_block_param
    | empty
    ;

macro_func_body
    : implies_body ';'
    | compound_statement
    ;

macro_declaration
        : MACRO macro_header '(' macro_params ')' opt_attributes macro_func_body
    ;

struct_or_union
    : STRUCT
    | UNION
    ;

struct_declaration
    : struct_or_union TYPE_IDENT opt_attributes struct_body
        ;

struct_body
        : '{' struct_declaration_list '}'
    ;

struct_declaration_list
    : struct_member_decl
        | struct_declaration_list struct_member_decl
        ;

enum_params
    : enum_param_decl
    | enum_params ',' enum_param_decl
    ;

enum_param_list
    : '(' enum_params ')'
    | '(' ')'
    | empty
    ;

struct_member_decl
        : type identifier_list opt_attributes ';'
        | struct_or_union IDENT opt_attributes struct_body
        | struct_or_union opt_attributes struct_body
        | BITSTRUCT ':' type opt_attributes bitstruct_body
        | BITSTRUCT IDENT ':' type opt_attributes bitstruct_body
        | INLINE type IDENT opt_attributes ';'
        | INLINE type opt_attributes ';'
    ;


enum_spec
    : ':' type enum_param_list
    | empty
    ;

enum_declaration
    : ENUM TYPE_IDENT enum_spec opt_attributes '{' enum_list '}'
    ;

faults
    : CONST_IDENT
    | faults ',' CONST_IDENT
    ;

fault_declaration
        : FAULT TYPE_IDENT opt_attributes '{' faults '}'
        | FAULT TYPE_IDENT opt_attributes '{' faults ',' '}'
        ;

func_macro_name
    : IDENT
    | AT_IDENT
    ;

func_header
    : optional_type type '.' func_macro_name
    | optional_type func_macro_name
    ;


macro_header
    : func_header
    | type '.' func_macro_name
    | func_macro_name
    ;

fn_parameter_list
    : '(' parameters ')'
    | '(' ')'
    ;

parameters
    : parameter '=' expr
    | parameter
    | parameters ',' parameter
    | parameters ',' parameter '=' expr
    ;

parameter
    : type IDENT opt_attributes
    | type ELLIPSIS IDENT opt_attributes
    | type ELLIPSIS CT_IDENT
    | type CT_IDENT
        | type ELLIPSIS opt_attributes
    | type HASH_IDENT opt_attributes
    | type '&' IDENT opt_attributes
    | type opt_attributes
    | '&' IDENT opt_attributes
    | HASH_IDENT opt_attributes
    | ELLIPSIS
    | IDENT opt_attributes
    | IDENT ELLIPSIS opt_attributes
    | CT_IDENT
    | CT_IDENT ELLIPSIS
    ;

func_definition
    : FN func_header fn_parameter_list opt_attributes ';'
    | FN func_header fn_parameter_list opt_attributes macro_func_body
    ;

const_declaration
    : CONST CONST_IDENT opt_attributes '=' expr ';'
    | CONST type CONST_IDENT opt_attributes '=' expr ';'
    ;

func_typedef
    : FN optional_type fn_parameter_list
    ;

opt_distinct_inline
    : DISTINCT
    | DISTINCT INLINE
    | INLINE DISTINCT
    | INLINE
    | empty
    ;

generic_parameters
    : bit_expr
    | type
    | generic_parameters ',' bit_expr
    | generic_parameters ',' type
    ;

typedef_type
    : func_typedef
    | type opt_generic_parameters
    ;



multi_declaration
    : ',' IDENT
    | multi_declaration ',' IDENT
    ;

global_storage
    : TLOCAL
    | empty
    ;

global_declaration
    : global_storage optional_type IDENT opt_attributes ';'
    | global_storage optional_type IDENT multi_declaration opt_attributes ';'
    | global_storage optional_type IDENT opt_attributes '=' expr ';'
    ;

opt_tl_stmts
    : top_level_statements
    | empty
    ;

tl_ct_case
    : CT_CASE constant_expr ':' opt_tl_stmts
    | CT_CASE type ':' opt_tl_stmts
        | CT_DEFAULT ':' opt_tl_stmts
        ;

tl_ct_switch_body
        : tl_ct_case
        | tl_ct_switch_body tl_ct_case
        ;

define_attribute
    : AT_TYPE_IDENT '(' parameters ')' opt_attributes '=' '{' opt_attributes '}'
    | AT_TYPE_IDENT opt_attributes '=' '{' opt_attributes '}'
    ;

opt_generic_parameters
    : '<' generic_parameters '>'
    | empty
    ;



define_ident
    : IDENT '=' path_ident opt_generic_parameters
    | CONST_IDENT '=' path_const opt_generic_parameters
    | AT_IDENT '=' path_at_ident opt_generic_parameters
        ;

define_declaration
    : DEF define_ident ';'
    | DEF define_attribute ';'
    | DEF TYPE_IDENT opt_attributes '=' opt_distinct_inline typedef_type ';'
    ;

tl_ct_if
    : CT_IF constant_expr ':' opt_tl_stmts CT_ENDIF
    | CT_IF constant_expr ':' opt_tl_stmts CT_ELSE opt_tl_stmts CT_ENDIF
    ;

tl_ct_switch
    : ct_switch tl_ct_switch_body CT_ENDSWITCH
    ;

module_param
        : CONST_IDENT
        | TYPE_IDENT
        ;

module_params
    : module_param
        | module_params ',' module_param
        ;

module
    : MODULE path_ident opt_attributes ';'
    | MODULE path_ident '<' module_params '>' opt_attributes ';'
    ;

import_paths
    : path_ident
    | path_ident ',' path_ident
    ;

import_decl
        : IMPORT import_paths opt_attributes ';'
        ;

translation_unit
    : top_level_statements
    | empty
    ;

top_level_statements
    : top_level
    | top_level_statements top_level
    ;

opt_extern
    : EXTERN
    | empty
    ;

top_level
    : module
    | import_decl
    | opt_extern func_definition
    | opt_extern const_declaration
    | opt_extern global_declaration
    | ct_assert_stmt
    | ct_echo_stmt
    | ct_include_stmt
    | tl_ct_if
    | tl_ct_switch
    | struct_declaration
    | fault_declaration
    | enum_declaration
    | macro_declaration
    | define_declaration
    | static_declaration
    | bitstruct_declaration
    ;


%%

void yyerror(char *s)
{
    fflush(stdout);
    printf("\n%*s\n%*s\n", column, "^", column, s);
}

int main(int argc, char *argv[])
{
    yyparse();
    return 0;
}

title: C3 Specification description: C3 Specification sidebar:

order: 999

THIS SPECIFICATION IS UNDER DEVELOPMENT

Notation

The syntax is specified using Extended Backus-Naur Form (EBNF):

production  ::= PRODUCTION_NAME '::=' expression?
expression  ::= alternative ("|" alternative)* 
alternative ::= term term*
term        ::= PRODUCTION_NAME | TOKEN | set | group | option | repetition
set         ::= '[' (range | CHAR) (rang | CHAR)* ']'
range       ::= CHAR '-' CHAR 
group       ::= '(' expression ')'
option      ::= expression '?'
repetition  ::= expression '*'

Productions are expressions constructed from terms and the following operators, in increasing precedence:

|   alternation
()  grouping
?  option (0 or 1 times)
*  repetition (0 to n times)

Uppercase production names are used to identify lexical tokens. Non-terminals are in lower case. Lexical tokens are enclosed in single quotes ‘’.

The form a..b represents the set of characters from a through b as alternatives.

Source code representation

A program consists of one or more translation units stored in files written in the Unicode character set, stored as a sequence of bytes using the UTF-8 encoding. Except for comments and the contents of character and string literals, all input elements are formed only from the ASCII subset (U+0000 to U+007F) of Unicode.

A raw byte stream is translated into a sequence of tokens which white space and comments are discarded. Doc comments may optionally be discarded as well. The resulting input elements form the tokens that are the terminal symbols of the syntactic grammar.

Lexical Translations

A raw byte stream is translated into a sequence of tokens which white space and comments are discarded. Doc comments may optionally be discarded as well. The resulting input elements form the tokens that are the terminal symbols of the syntactic grammar.

The longest possible translation is used at each step, even if the result does not ultimately make a correct program while another lexical translation would.

Example: a--b is translated as a, --, b, which does not form a grammatically correct expression, even though the tokenization a, -, -, b could form a grammatically correct expression.

Line Terminators

The C3 compiler divides the sequence of input bytes into lines by recognizing line terminators

Lines are terminated by the ASCII LF character (U+000A), also known as “newline”. A line termination specifies the termination of the // form of a comment.

Input Elements and Tokens

An input element may be:

  1. White space
  2. Comment
  3. Doc Contract
  4. Token

A token may be:

  1. Identifier
  2. Keyword
  3. Literal
  4. Separator
  5. Operator

A Doc Contract consists of:

  1. A stream of descriptive text
  2. A list of directive Tokens

Those input elements that are not white space or comments are tokens. The tokens are the terminal symbols of the syntactic grammar. Whitespace and comments can serve to separate tokens that might be tokenized in another manner. For example the characters + and = may form the operator token += only if there is no intervening white space or comment.

White Space

White space is defined as the ASCII horizontal tab character (U+0009), form feed character (U+000A), vertical tab ( U+000B), carriage return (U+000D), space character (U+0020) and the line terminator character (U+000D).

WHITESPACE      ::= [ \t\f\v\r\n]

Letters and digits

UC_LETTER       ::= [A-Z]
LC_LETTER       ::= [a-z]
LETTER          ::= UC_LETTER | LC_LETTER
DIGIT           ::= [0-9]
HEX_DIGIT       ::= [0-9a-fA-F]
BINARY_DIGIT    ::= [01]
OCTAL_DIGIT     ::= [0-7]
LC_LETTER_US    ::= LC_LETTER | "_"
UC_LETTER_US    ::= UC_LETTER | "_"
ALPHANUM        ::= LETTER | DIGIT
ALPHANUM_US     ::= ALPHANUM | "_"
UC_ALPHANUM_US  ::= UC_LETTER_US | DIGIT
LC_ALPHANUM_US  ::= LC_LETTER_US | DIGIT

Comments

There are three types of regular comments:

  1. // text a line comment. The text between // and line end is ignored.
  2. /* text */ block comments. The text between /* and */ is ignored. It has nesting behaviour, so for every /* discovered between the first /* and the last */ a corresponding */ must be found.

Doc contract

  1. <* text *> doc block comment. The text between <* and *> is optionally parsed using the doc comment syntactic grammar. A compiler may choose to read <* text *> as a regular comment.

Identifiers

Identifiers name program entities such as variables and types. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter or underscore.

C3 has three types of identifiers: const identifiers - containing only underscore and upper-case letters, type identifiers - starting with an upper case letter followed by at least one underscore letter and regular identifiers, starting with a lower case letter.

IDENTIFIER      ::=  "_"* LC_LETTER ALPHANUM_US*
CONST_IDENT     ::=  "_"* UC_LETTER UC_ALPHANUM_US*
TYPE_IDENT      ::=  "_"* UC_LETTER UC_ALPHANUM_US* LC_LETTER ALPHANUM_US*
CT_IDENT        ::=  "$" IDENTIFIER
CT_CONST_IDENT  ::=  "$" CONST_IDENT
CT_TYPE_IDENT   ::=  "$" TYPE_IDENT
AT_TYPE_IDENT   ::=  "@" TYPE_IDENT
PATH_SEGMENT    ::= "_"* LC_LETTER LC_ALPHANUM_US*

Keywords

The following keywords are reserved and may not be used as identifiers:

asm         any         anyfault
assert      attribute   break
case        cast        catch
const       continue    default
defer       def         do
else        enum        extern
errtype     false       fn
generic     if          import
inline      macro
module      nextcase    null
public      return      struct
switch      true        try
typeid      var         void        
while

bool        quad        double      
float       long        ulong
int         uint        byte
short       ushort      char
isz         usz         float16
float128

$and        $assert     $case       
$default    $echo       $else       
$error      $endfor     $endforeach 
$endif      $endswitch  $for        
$foreach    $if         $switch     
$typef      $vaarg      $vaconst
$vacount    $varef      $vatype

Operators and punctuation

The following character sequences represent operators and punctuation.

&       @       ~       |       ^       :
,       /       $       .       ;       )
>       <       #       {       }       -
(       )       *       [       ]       %
>=      <=      +       +=      -=      !
?       ?:      &&      ??      &=      |=
^=      /=      ..      ==      ({      })
[<      >]      (<      >)      ++      --      
%=      !=      ||      ::      <<      >>      
!!      ...     <<=     >>=

Integer literals

An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: 0b or 0B for binary, 0o, or 0O for octal, and 0x or 0X for hexadecimal. A single 0 is considered a decimal zero. In hexadecimal literals, letters a through f and A through F represent values 10 through 15.

For readability, an underscore character _ may appear after a base prefix or between successive digits; such underscores do not change the literal’s value.

INTEGER         ::= DECIMAL_LIT | BINARY_LIT | OCTAL_LIT | HEX_LIT
DECIMAL_LIT     ::= '0' | [1-9] ('_'* DECIMAL_DIGITS)?
BINARY_LIT      ::= '0' [bB] '_'* BINARY_DIGITS
OCTAL_LIT       ::= '0' [oO] '_'* OCTAL_DIGITS
HEX_LIT         ::= '0' [xX] '_'* HEX_DIGITS

BINARY_DIGIT    ::= [01]
HEX_DIGIT       ::= [0-9a-fA-F]

DECIMAL_DIGITS  ::= DIGIT ('_'* DIGIT)*
BINARY_DIGITS   ::= BINARY_DIGIT ('_'* BINARY_DIGIT)*
OCTAL_DIGITS    ::= OCTAL_DIGIT ('_'* OCTAL_DIGIT)*
HEX_DIGITS      ::= HEX_DIGIT ('_'* HEX_DIGIT)*
42
4_2
0_600
0o600
0O600           // second character is capital letter 'O'
0xBadFace
0xBad_Face
0x_67_7a_2f_cc_40_c6
170141183460469231731687303715884105727
170_141183_460469_231731_687303_715884_105727

0600            // Invalid, non zero decimal number may not start with 0 
_42             // an identifier, not an integer literal
42_             // invalid: _ must separate successive digits
0_xBadFace      // invalid: _ must separate successive digits

Floating point literals

A floating-point literal is a decimal or hexadecimal representation of a floating-point constant.

A decimal floating-point literal consists of an integer part (decimal digits), a decimal point, a fractional part (decimal digits), and an exponent part (e or E followed by an optional sign and decimal digits). One of the integer part or the fractional part may be elided; one of the decimal point or the exponent part may be elided. An exponent value exp scales the mantissa (integer and fractional part) by powers of 10.

A hexadecimal floating-point literal consists of a 0x or 0X prefix, an integer part (hexadecimal digits), a radix point, a fractional part (hexadecimal digits), and an exponent part (p or P followed by an optional sign and decimal digits). One of the integer part or the fractional part may be elided; the radix point may be elided as well, but the exponent part is required. An exponent value exp scales the mantissa (integer and fractional part) by powers of 2.

For readability, an underscore character _ may appear after a base prefix or between successive digits; such underscores do not change the literal value.

FLOAT_LIT       ::= DEC_FLOAT_LIT | HEX_FLOAT_LIT
DEC_FLOAT_LIT   ::= DECIMAL_DIGITS '.' DECIMAL_DIGITS? DEC_EXPONENT? 
                    | DECIMAL_DIGITS DEC_EXPONENT
                    | '.' DECIMAL_DIGITS DEC_EXPONENT?
DEC_EXPONENT    ::= [eE] [+-]? DECIMAL_DIGITS
HEX_FLOAT_LIT   ::= '0' [xX] HEX_MANTISSA HEX_EXPONENT
HEX_MANTISSA    ::= HEX_DIGITS '.' HEX_DIGITS?
                    | HEX_DIGITS
                    | '.' HEX_DIGITS 
HEX_EXPONENT    ::= [pP] [+-] DECIMAL_DIGITS

Characters

Characters are the fundamental components of strings and character literals.

CHAR_ELEMENT    ::= [\x20-\x26] | [\x28-\x5B] | [\x5D-\x7F]
CHAR_LIT_BYTE   ::= CHAR_ELEMENT | \x5C CHAR_ESCAPE
CHAR_ESCAPE     ::= [abefnrtv\'\"\\] 
                    | 'x' HEX_DIGIT HEX_DIGIT
UNICODE_CHAR    ::= unicode_char                    
                    | 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
                    | 'U' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT 
                          HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT

Backslash escapes

The following backslash escapes are available for characters and string literals:

\0      0x00 zero value
\a      0x07 alert/bell
\b      0x08 backspace
\e      0x1B escape
\f      0x0C form feed
\n      0x0A newline
\r      0x0D carriage return
\t      0x09 horizontal tab
\v      0x0B vertical tab
\\      0x5C backslash
\'      0x27 single quote '
\"      0x22 double quote "
\x      Escapes a single byte hex value
\u      Escapes a two byte unicode hex value 
\U      Escapes a four byte unicode hex value

String literals

A string literal represents a string constant obtained from concatenating a sequence of characters. String literals are character sequences between double quotes, as in “bar”. Within the quotes, any character may appear except newline and unescaped double quote. The text between the quotes forms the value of the literal, with backslash escapes interpreted as they are in rune literals, with the same restrictions. The two-digit hexadecimal (\xnn) escapes represent individual bytes of the resulting string; all other escapes represent the (possibly multibyte) UTF-8 encoding of individual characters. Thus inside a string literal \xFF represent a single byte of value 0xFF = 255, while ÿ, \u00FF, \U000000FF and \xc3\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF.

STRING_LIT      ::= \x22 (CHAR_LIT_BYTE | UNICODE_CHAR)* \x22

Compile time string concatenation

Strings will concatenate if declared in sequence.

Example:

String s = "abc" "def" "ghi";
// This is equivalent to:
String s = "abcdefghi";

Raw string literals

Raw string literals are enclosed between `` and consist of the raw UTF8 in the source code between the “`”. A sequence of two “`” will be interpreted as a single escaped “`” that does not terminate the literal.

Compile time concatenation

Raw strings will concatenate with other regular strings and raw strings ( see string literal compile time concatenation).

Source code pre-filtering

The source code will pre-filter \r (0x0D) from the source code. This means that it is also implicitly filtered out of raw strings.

Character literals

A character literal is enclosed in ' and may either consist of 1, 2, 4, 8, 16 bytes.

CHARACTER_LIT   ::= "'" (CHAR_LIT_BYTE+) | UNICODE_CHAR "'"

Types

Types consist of built-in types and user-defined types (enums, structs, unions, bitstructs, fault and distinct).

Boolean types

bool may have the two values true and false. It holds a single bit of information but is stored in a char type.

Integer types

The built-in integer types:

char      unsigned 8-bit
ichar     signed 8-bit
ushort    unsigned 16-bit
short     signed 16-bit
uint      unsigned 32-bit
int       signed 32-bit
ulong     unsigned 64-bit
long      signed 64-bit
uint128   unsigned 128-bit
int128    singed 128-bit

In addition, the following type aliases exist:

uptr      unsigned pointer size
iptr      signed pointer size
usz       unsigned pointer offset / object size
isz       signed pointer offset  / object size

Floating point types

Built-in floating point types:

float16   IEEE 16-bit*
bfloat16  Brainfloat*
float     IEEE 32-bit
double    IEEE 64-bit
float128  IEEE 128-bit*

(* optionally supported)

Vector types

A vector lowers to the platform’s vector types where available. A vector has a base type and a width.

vector_type        ::= type "[<" length ">]"

Vector base type

The base type of a vector must be boolean, an integer or a floating point type.

Min width

The vector width must be at least 1.

Element access

Vector elements are accessed using []. It is possible to take the address of a single element.

Alignment

Alignment of vectors are platform dependent, but is at least the alignment of its element type.

Vector operations

Vectors support the same arithmetics as its underlying type, and will perform the operation element-wise.

Example:

int[<2>] a = { 1, 3 };
int[<2>] b = { 2, 7 };

int[<2>] c = a * b;
// Equivalent to
int[<2>] c = { a[0] * b[0], a[1] * b[1] };

Array types

An array has the alignment of its elements. An array must have at least one element.

Slice types

The slice consist of a pointer, followed by an usz length, having the alignment of pointers.

Pointer types

A pointer is the address to memory.

pointer_type       ::= type "*"

Pointee type

The type of the memory pointed to is the pointee type. It may be any runtime type.

iptr and uptr

A pointer may be losslessly cast to an iptr or uptr. An iptr or uptr may be cast to a pointer of any type.

The wildcard pointer void*

The void* may implicitly cast into any other pointer type. The void* [implicitly casts into any other pointer.

A void* pointer may never be dereferenced.

Pointer arithmetic on void*

Performing pointer arithmetics on void* will assume that the element size is 1. This includes pointer arithmetics using subscripting.

Subscripting

Subscripting a pointer is equal to performing pointer arithmetics using the index, followed by a deref. Subscripts on pointers may be negative and will never do bounds checks.

Deref

Dereferencing a pointer will return the value in the memory location interpreted as the pointee type.

#

Struct types

A struct may not have zero members.

Alignment

A non-packed struct has the alignment of the member that has the highest alignment. A packed struct has alignment 1. See align attribute for details on changing the alignment.

Union types

A union may not have zero members.

Alignment

A union has the alignment of the member that has the highest alignment. See align attribute for details on changing the alignment.

Fault types

A fault is an extensible enum which can be used to create an Excuse for an empty optional.

Alignment

A fault type has the same alignment as a pointer. See align attribute for details on changing the alignment.

Enum types

Function types

Typeid type

The typeid is a pointer sized value which uniquely identifies a type.

Any type

The any is a fat pointer (2 pointers wide) holding a pointer to a value and its corresponding typeid. It cannot be dereferenced.

Fields

.ptr returns a void* pointer to the underlying value .type returns the typeid of the underlying value.

Switching over any

Switching over an any value creates an any switch.

Anyfault type

Declarations and scope

Expressions

Assignment expression

assignment_expr    ::= ct_type_assign | unary_expr assignment_op expr
ct_type_assign     ::= ct_type_ident "=" type
assignment_op      ::= "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "<<=" | ">>=" | "&=" | "^=" | "|="

Type assign

This assigns a new type to a compile time type variable. The value of the expression is the type assigned.

Combined assign

All assignment operations except for “=” are combined assign operation. They first perform the operation indicated by the leftmost character(s) in the operator (e.g + for +=, << for <<= etc) with the lhs and the rhs. The result is then assigned to the left hand side. The result of the operation is the new value of the left hand side.

Implicit conversion

If the left hand side is a pointer and the operation is “+=” or “-=” an attempt to implicitly convert to isz/usz will be tried.

For all other types and operations, an implicit conversion of rhs to the type of lhs will be tried.

Ternary, elvis and or-else expressions

ternary_group_expr ::= suffix_group_expr | ternary_expr | elvis_expr | orelse_expr
ternary_expr       ::= or_expr "?" expr ":" ternary_group_expr
elvis_expr         ::= suffix_expr "?:" ternary_group_expr
orelse_expr        ::= suffix_expr "??" ternary_group_expr

Ternary evaluation

The most left-hand expression is evaluated to a boolean. If it is true, the value of the middle expression is returned, otherwise the last expression is returned.

Only the most left-hand expression and the returned expressions are evaluated.

The middle and last expression are implicitly converted to their unified type.

The resulting type is the unified type.

Elvis evaluation

Lhs and rhs are implicitly converted to their unified type.

The lhs is evaluated, it is then converted to a boolean, if the result it true, return the lhs value before its boolean conversion. Otherwise return the right hand side.

The right hand side is only evaluated if the lhs evaluates to false.

The resulting type is the unified type.

Orelse evaluation

The lhs must be optional. The non-optional type for lhs and rhs are calculated. The unified type of the result is calculated. Lhs are converted to the unified type preserving their optionality.

At runtime, lhs is evaluated. If it evaluates to an optional, rhs is returned instead.

Rhs is only evaluated if lhs evaluates to an optional.

The resulting type of the orelse is the post conversion type of the rhs.

Suffix expression

Suffix expressions convert a fault to an optional.

suffix_group_exp   ::= or_group_expr | suffix_expr
suffix_expr        ::= or_group_expr "?" "!"?

Effect of “?”

The “?” will convert the expression into an optional. The left hand side must be a fault type. If an optional “!” follows, this optional is immediately returned, as if by a return <expr>? statement.

Type of the expression

The type is a wildcard optional. If “!” is added, it is a wildcard type.

Rethrow expression

If the expression is optional, implicitly return with the optional value.

rethrow_expr       ::= expr "!"

The expression to rethrow

The expression must have an optional type, otherwise this is a compile time error.

Type

The type of “rethrow” is the inner expr type without optional.

Relational expression

rel_group_expr     ::= add_group_expr | relational_expr
relational_expr    ::= rel_group_expr relational_op add_group_expr
relational_op      ::= "<" | ">" | "<=" | ">="

TODO

And expression

This binary expression evaluates the lhs, and if the result is true evaluates the rhs. The result is true if both lhs and rhs are true.

and_group_expr     ::= rel_group_expr | and_expr
and_expr           ::= and_group_expr "&&" rel_group_expr

Type

The type of the and-expression is bool.

Or expression

This binary expression evaluates the lhs, and if the result is false evaluates the rhs. The result is true if lhs or rhs is true.

or_group_expr      ::= and_group_expr | or_expr
or_expr            ::= or_group_expr "||" and_group_expr

Constant folded arithmetics

Constant folding will happen for constant integers and floating. Vectors operations will not be constant-folded.

Constant folded operations are: multiplication, division, addition, subtraction, bit shifts, bit negation, bitwise and, or and xor, comparison, logical and/or, and negation.

Type

The type of the or-expression is bool.

Casts

Pointer casts

Integer to pointer cast

Any integer of pointer size or larger may be explicitly cast to a pointer. An integer to pointer cast is considered non-constant, except in the special case where the integer == 0. In that case, the result is constant null.

Example:

byte a = 1;
int* b = (int*)a; // Invalid, pointer type is > 8 bits.
int* c = (int*)1; // Valid, but runtime value.
int* d = (int*)0; // Valid and constant value.

Pointer to integer cast

A pointer may be cast to any integer, truncating the pointer value if the size of the pointer is larger than the pointer size. A pointer to integer cast is considered non-constant, except in the special case of a null pointer, where it is equal to the integer value 0.

Example:

fn void test() { ... }
def VoidFunc = fn void test();

VoidFunc a = &test;
int b = (int)null;
int c = (int)a; // Invalid, not constant
int d = (int)((int*)1); // Invalid, not constant

Subscript operator

The subscript operator may take as its left side a pointer, array or slice. The index may be of any integer type. TODO NOTE The subscript operator is not symmetrical as in C. For example in C3 array[n] = 33 is allowed, but not n[array] = 33. This is a change from C.

Operands

Compound Literals

Compound literals have the format

compound_literal   ::= TYPE_IDENTIFIER '(' initializer_list ')'
initializer_list   ::= '{' (initializer_param (',' initializer_param)* ','?)? '}'
initializer_param  ::= expression | designator '=' expression
designator         ::= array_designator | range_designator | field_designator
array_designator   ::= '[' expression ']'
range_designator   ::= '[' range_expression ']'
field_designator   ::= IDENTIFIER
range_expression   ::= (range_index)? '..' (range_index)?
range_index        ::= expression | '^' expression

Taking the address of a compound literal will yield a pointer to stack allocated temporary.

Function calls

Function argument resolution

Call slots are in order: regular slots, vaarg slot, name-only slots.

No regular slots may appear after the vaarg slot, however there may be named parameters with default values after the vaarg slot if it’s not a raw vaarg.

These “name-only” slots need to have a parameter name and a default value, and may only be called as named arguments.

Named arguments may never be splat expressions.

  1. Step through all the arguments, resolve the named arguments and determine if there are any regular arguments.
  2. If there are regular arguments, then named arguments may only be in name-only slots, otherwise it is an error.
  3. If there are named arguments in the regular slots, all slots not provided arguments must have default values.
  4. Proceed with evaluation of arguments from left to right in call invocation order.
  5. Regular arguments are placed in the regular slots from left to right.
  6. If a regular argument is a splat expression, evaluate it without inference and determine if it is an array, vector, untyped list or slice with a known size, otherwise it is an error.
  7. A regular argument splat will be expanded into as many slots as its length, this may expand into vaarg arguments.
  8. In the vaarg slot, splatting a slice will forward it.
  9. In the vaarg slot, splatting an array, vector or untyped list will expand its elements as if they were provided as arguments.
  10. A named argument may never appear more than once.
  11. The vaarg slot may never be accessed using named arguments.

Varargs

For varargs, a bool or any integer smaller than what the C ABI specifies for the c int type is cast to int. Any float smaller than a double is cast to double. Compile time floats will be cast to double. Compile time integers will be cast to c int type.

Statements

stmt               ::= compound_stmt | non_compound_stmt
non_compound_stmt  ::= assert_stmt | if_stmt | while_stmt | do_stmt | foreach_stmt | foreach_r_stmt 
                       | for_stmt | return_stmt | break_stmt | continue_stmt | var_stmt 
                       | declaration_stmt | defer_stmt | nextcase_stmt | asm_block_stmt
                       | ct_echo_stmt | ct_error_stmt | ct_assert_stmt | ct_if_stmt | ct_switch_stmt 
                       | ct_for_stmt | ct_foreach_stmt | expr_stmt

Asm block statement

An asm block is either a string expression or a brace enclosed list of asm statements.

asm_block_stmt      ::= "asm" ("(" constant_expr ")" | "{" asm_stmt* "}")
asm_stmt            ::= asm_instr asm_exprs? ";"
asm_instr           ::= ("int" | IDENTIFIER) ("." IDENTIFIER)
asm_expr            ::= CT_IDENT | CT_CONST_IDENT | "&"? IDENTIFIER | CONST_IDENT | FLOAT_LITERAL
                        | INTEGER | "(" expr ")" | "[" asm_addr "]"
asm_addr            ::= asm_expr (additive_op asm_expr asm_addr_trail?)?
asm_addr_trail      ::= "*" INTEGER (additive_op INTEGER)? | (shift_op | additive_op) INTEGER

TODO

Assert statement

The assert statement will evaluate the expression and call the panic function if it evaluates to false.

assert_stmt        ::= "assert" "(" expr ("," assert_message)? ")" ";"
assert_message     ::= constant_expr ("," expr)*

Conditional inclusion

assert statements are only included in “safe” builds. They may turn into assume directives for the compiler on “fast” builds.

Assert message

The assert message is optional. It can be followed by an arbitrary number of expressions, in which case the message is understood to be a format string, and the following arguments are passed as values to the format function.

The assert message must be a compile time constant. There are no restriction on the format argument expressions.

Panic function

If the assert message has no format arguments or no assert message is included, then the regular panic function is called. If it has format arguments then panicf is called instead.

In the case the panicf function does not exist (for example, compiling without the standard library), then the format and the format arguments will be ignored and the assert will be treated as if no assert message was available.

Break statement

A break statement exits a while, for, do, foreach or switch scope. A labelled break may also exit a labelled if.

break_stmt         ::= "break" label? ";"

Break labels

If a break has a label, then it will instead exit an outer scope with the label.

Unreachable code

Any statement following break in the same scope is considered unreachable.

Compile time echo statement

During parsing, the compiler will output the text in the statement when it is semantically checked. The statement will be turned into a NOP statement after checking.

ct_echo_stmt       ::= "$echo" constant_expr ";"

The message

The message must be a compile time constant string.

Compile time assert statement

During parsing, the compiler will check the compile time expression and create a compile time error with the optional message. After evaluation, the $assert becomes a NOP statement.

ct_assert_stmt     ::= "$assert" constant_expr (":" constant_expr) ";"

Evaluated expression

The checked expression must evaluate to a boolean compile time constant.

Error message

The second parameter, which is optional, must evaluate to a constant string.

Compile time error statement

During parsing, when semantically checked this statement will output a compile time error with the message given.

ct_error_stmt      ::= "$error" constant_expr ";"

Error message

The parameter must evaluate to a constant string.

Compile time if statement

If the cond expression is true, the then-branch is processed by the compiler. If it evaluates to false, the else-branch is processed if it exists.

ct_if_stmt         ::= "$if" constant_expr ":" stmt* ("$else" stmt*)? "$endif"

Cond expression

The cond expression must be possible to evaluate to true or false at compile time.

Scopes

The “then” and “else” branches will add a compile time scope that is exited when reaching $endif. It adds no runtime scope.

Evaluation

Statements in the branch not picked will not be semantically checked.

Compile time switch statement

ct_switch_stmt     ::= "$switch" ("(" ct_expr_or_type ")")? ct_case_stmt+ "$endswitch"
ct_case_stmt       ::= ("$default" | "$case" ct_expr_or_type) ":" stmt*

No cond expression switch

If the cond expression is missing, evaluation will go through each case until one case expression evaluates to true.

Type expressions

If a cond expression is a type, then all case statement expressions must be types as well.

Ranged cases

Compile time switch does not support ranged cases.

Fallthrough

If a case clause has no statements, then when executing the case, rather than exiting the switch, the next case clause immediately following it will be used. If that one should also be missing statements, the procedure will be repeated until a case clause with statements is encountered, or the end of the switch is reached.

Break and nextcase

Compile time switches do not support break nor nextcase.

Evaluation of statements

Only the case which is first matched has its statements processed by the compiler. All other statements are ignored and will not be semantically checked.

Continue statement

A continue statement jumps to the cond expression of a while, for, do or foreach

continue_stmt      ::= "continue" label? ";"

Continue labels

If a continue has a label, then it will jump to the cond of the while/for/do in the outer scope with the corresponding label.

Unreachable code

Any statement following continue in the same scope is considered unreachable.

Declaration statement

A declaration statement adds a new runtime or compile time variable to the current scope. It is available after the declaration statement.

declaration_stmt   ::= const_declaration | local_decl_storage? optional_type decls_after_type ";"
local_decl_storage ::= "tlocal" | "static"
decls_after_type   ::= local_decl_after_type ("," local_decl_after_type)*
decl_after_type    ::= CT_IDENT ("=" constant_expr)? | IDENTIFIER opt_attributes ("=" expr)?

Thread local storage

Using tlocal allocates the runtime variable as a thread local variable. In effect this is the same as declaring the variable as a global tlocal variable, but the visibility is limited to the function. tlocal may not be combined with static.

The initializer for a tlocal variable must be a valid global init expression.

Static storage

Using static allocates the runtime variable as a function global variable. In effect this is the same as declaring a global, but visibility is limited to the function. static may not be combined with tlocal.

The initializer for a static variable must be a valid global init expression.

Scopes

Runtime variables are added to the runtime scope, compile time variables to the compile time scope. See var statements .

Multiple declarations

If more than one variable is declared, no init expressions are allowed for any of the variables.

No init expression

If no init expression is provided, the variable is zero initialized.

Opt-out of zero initialization

Using the @noinit attribute opts out of zero initialization.

Self referencing initialization

An init expression may refer to the address of the same variable that is declared, but not the value of the variable.

Example:

void* a = &a;  // Valid
int a = a + 1; // Invalid

Defer statement

The defer statements are executed at (runtime) scope exit, whether through return, break, continue or rethrow.

defer_stmt         ::= "defer" ("try" | "catch")? stmt

Defer in defer

The defer body (statement) may not be a defer statement. However, if the body is a compound statement then this may have any number of defer statements.

Static and tlocal variables in defer

Static and tlocal variables are allowed in a defer statement. Only a single variable is instantiated regardless of the number of inlining locations.

Defer and return

If the return has an expression, then it is evaluated before the defer statements (due to exit from the current function scope), are executed.

Example:

int a = 0;
defer a++;
return a;
// This is equivalent to
int a = 0;
int temp = a;
a++;
return temp;

Defer and jump statements

A defer body may not contain a break, continue, return or rethrow that would exit the statement.

Defer execution

Defer statements are executed in the reverse order of their declaration, starting from the last declared defer statement.

Defer try

A defer try type of defer will only execute if the scope is left through normal fallthrough, break, continue or a return with a result.

It will not execute if the exit is through a rethrow or a return with an optional value.

Defer catch

A defer catch type of defer will only execute if the scope is left through a rethrow or a return with an optional value

It will not execute if the exit is a normal fallthrough, break, continue or a return with a result.

Non-regular returns - longjmp, panic and other errors

Defers will not execute when doing longjmp terminating through a panic or other error. They are only invoked on regular scope exits.

Expr statement

An expression statement evaluates an expression.

expr_stmt          ::= expr ";"

No discard

If the expression is a function or macro call either returning an optional or annotated @nodiscard, then the expression is a compile time error. A function or macro returning an optional can use the @maydiscard attribute to suppress this error.

If statement

An if statement will evaluate the cond expression, then execute the first statement (the “then clause”) in the if-body if it evaluates to “true”, otherwise execute the else clause. If no else clause exists, then the next statement is executed.

if_stmt            ::= "if" (label ":")? "(" cond_expr ")" if_body
if_body            ::= non_compound_stmt | compound_stmt else_clause? | "{" switch_body "}"
else_clause        ::= "else" (if_stmt | compound_stmt)

Scopes

Both the “then” clause and the else clause open new scopes, even if they are non-compound statements. The cond expression scope is valid until the exit of the entire statement, so any declarations in the cond expression are available both in then and else clauses. Declarations in the “then” clause is not available in the else clause and vice versa.

Special parsing of the “then” clause

If the then-clause isn’t a compound statement, then it must follow on the same row as the cond expression. It may not appear on a consecutive row.

Break

It is possible to use labelled break to break out of an if statement. Note that an unlabelled break may not be used.

If-try

The cond expression may be a try-unwrap chain. In this case, the unwrapped variables are scoped to the “then” clause only.

If-catch

The cond expression may be a catch-unwrap. The unwrap is scoped to the “then” clause only. If one or more variables are in the catch, then the “else” clause have these variables implicitly unwrapped.

Example:

int! a = foo();
int! b = foo();
if (catch a, b)
{
    // Do something
}
else
{
    int x = a + b; // Valid, a and b are implicitly unwrapped.
}

If-catch implicit unwrap

If an if-catch’s “then”-clause will jump out of the outer scope in all code paths and the catch is on one or more variables, then this variable(s) will be implicitly unwrapped in the outer scope after the if-statement.

Example:

int! a = foo();
if (catch a)
{
  return;
}  
int x = a; // Valid, a is implicitly unwrapped.

Nextcase statement

Nextcase will jump to another switch case.

nextcase_stmt      ::= "nextcase" ((label ":")? (expr | "default"))? ";"

Labels

When a nextcase has a label, the jump is to the switch in an outer scope with the corresponding label.

No expression jumps

A nextcase without any expression jumps to the next case clause in the current switch. It is not possible to use no expression nextcase with labels.

Jumps to default

Using default jumps to the default clause of a switch.

Missing case

If the switch has constant case values, and the nextcase expression is constant, then the value of the expression must match a case clause. Not matching a case is a compile time error.

If one or more cases are non-constant and/or the nextcase expression is non-constant, then no compile time check is made.

Variable expression

If the nextcase has a non-constant expression, or the cases are not all constant, then first the nextcase expression is evaluated. Next, execution will proceed as if the switch was invoked again, but with the nextcase expression as the switch cond expression. See switch statement.

If the switch does not have a cond expression, nextcase with an expression is not allowed.

Unreachable code

Any statement in the same scope after a nextcase are considered unreachable.

Switch statement

switch_stmt        ::= "switch" (label ":")? ("(" cond_expr ")")? switch body
switch_body        ::= "{" case_clause* "}"
case_clause        ::= default_stmt | case_stmt
default_stmt       ::= "default" ":" stmt*
case_stmt          ::= "case" label? expr (".." expr)? ":" stmt*

Regular switch

If the cond expression exists and all case statements have constant expression, then first the cond expression is evaluated, next the case corresponding to the expression’s value will be jumped to and the statement will be executed. After reaching the end of the statements and a new case clause or the end of the switch body, the execution will jump to the first statement after the switch.

If-switch

If the cond expression is missing or the case statements are non-constant expressions, then each case clause will be evaluated in order after the cond expression has been evaluated (if it exists):

  1. If a cond expression exists, calculate the case expression and execute the case if it is matching the cond expression. A default statement has no expression and will always be considered matching the cond expression reached.
  2. If no con expression exists, calculate the case expression and execute the case if the expression evaluates to “true” when implicitly converted to boolean. A default statement will always be considered having the “true” result.

Any-switch

If the cond expression is an any type, the switch is handled as if switching was done over the type field of the any. This field has the type of typeid, and the cases follows the rules for switching over typeid.

If the cond expression is a variable, then this variable is implicitly converted to a pointer with the pointee type given by the case statement.

Example:

any a = abc();
switch (a)
{
    case int:
        int b = *a;   // a is int*
    case float:
        float z = *a; // a is float*
    case Bar:
        Bar f = *a;   // a is Bar*
    default:
        // a is not unwrapped
}

Ranged cases

Cases may be ranged. The start and end of the range must both be constant integer values. The start must be less or equal to the end value. Using non-integers or non-constant values is a compile time error.

Fallthrough

If a case clause has no statements, then when executing the case, rather than exiting the switch, the next case clause immediately following it will be executed. If that one should also be missing statement, the procedure will be repeated until a case clause with statements is encountered (and executed), or the end of the switch is reached.

Exhaustive switch

If a switch case has a default clause or it is switching over an enum and there exists a case for each enum value then the switch is exhaustive.

Break

If an unlabelled break, or a break with the switch’s label is encountered, then the execution will jump out of the switch and proceed directly after the end of the switch body.

Unreachable code

If a switch is exhaustive and all case clauses end with a jump instruction, containing no break statement out of the current switch, then the code directly following the switch will be considered unreachable.

Switching over typeid

If the switch cond expression is a typeid, then case declarations may use only the type name after the case, which will be interpreted as having an implicit .typeid. Example: case int: will be interpreted as if written case int.typeid.

Nextcase without expression

Without a value nextcase will jump to the beginning of the next case clause. It is not allowed to put nextcase without an expression if there are no following case clauses.

Nextcase with expression

Nextcase with an expression will evaluate the expression and then jump as if the switch was entered with the cond expression corresponding to the value of the nextcase expression. Nextcase with an expression cannot be used on a switch without a cond expression.

Do statement

The do statement first evaluates its body (inner statement), then evaluates the cond expression. If the cond expression evaluates to true, jumps back into the body and repeats the process.

do_stmt            ::= "do" label? compound_stmt ("while" "(" cond_expr ")")? ";"

Unreachable code

The statement after a do is considered unreachable if the cond expression cannot ever be false and there is no break out of the do.

Break

break will exit the do with execution continuing on the following statement.

Continue

continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached.

Do block

If no while part exists, it will only execute the block once, as if it ended with while (false), this is called a “do block”

For statement

The for statement will perform the (optional) init expression. The cond expression will then be tested. If it evaluates to true then the body will execute, followed by the incr expression. After execution will jump back to the cond expression and execution will repeat until the cond expression evaluates to false.

for_stmt           ::= "for" label? "(" init_expr ";" cond_expr? ";" incr_expr ")" stmt
init_expr          ::= decl_expr_list?
incr_expr          ::= expr_list?

Init expression

The init expression is only executed once before the rest of the for loop is executed. Any declarations in the init expression will be in scope until the for loop exits.

The init expression may optionally be omitted.

Incr expression

The incr expression is evaluated before evaluating the cond expr every time except for the first one.

The incr expression may optionally be omitted.

Cond expression

The cond expression is evaluated every loop. Any declaration in the cond expression is scoped to the current loop, i.e. it will be reinitialized at the start of every loop.

The cond expression may optionally be omitted. This is equivalent to setting the cond expression to always return true.

Unreachable code

The statement after a for is considered unreachable if the cond expression cannot ever be false, or is omitted and there is no break out of the loop.

Break

break will exit the for with execution continuing on the following statement after the for.

Continue

continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached.

Equivalence of while and for

A while loop is functionally equivalent to a for loop without init and incr expressions.

Foreach and foreach_r statements

The foreach statement will loop over a sequence of values. The foreach_r is equivalent to foreach but the order of traversal is reversed. foreach starts with element 0 and proceeds step by step to element len - 1. foreach_r starts starts with element len - 1 and proceeds step by step to element 0.

foreach_stmt       ::= "foreach" label? "(" foreach_vars ":" expr ")" stmt
foreach_r_stmt     ::= "foreach_r" label? "(" foreach_vars ":" expr ")" stmt
foreach_vars       ::= (foreach_index ",")? foreach_var
foreach_var        ::= type? "&"? IDENTIFIER

Break

break will exit the foreach statement with execution continuing on the following statement after.

Continue

continue will cause the next iteration to commence, as if the end of the statement had been reached.

Iteration by value or reference

Normally iteration are by value. Each element is copied into the foreach variable. If & is added before the variable name, the elements will be retrieved by reference instead, and consequently the type of the variable will be a pointer to the element type instead.

Foreach variable

The foreach variable may omit the type. In this case the type is inferred. If the type differs from the element type, then an implicit conversion will be attempted. Failing this is a compile time error.

Foreach index

If a variable name is added before the foreach variable, then this variable will receive the index of the element. For foreach_r this mean that the first value of the index will be len - 1.

The index type defaults to usz.

If an optional type is added to the index, the index will be converted to this type. The type must be an integer type. The conversion happens as if the conversion was a direct cast. If the actual index value would exceed the maximum representable value of the type, this does not affect the actual iteration, but may cause the index value to take on an incorrect value due to the cast.

For example, if the optional index type is char and the actual index is 256, then the index value would show 0 as (char)256 evaluates to zero.

Modifying the index variable will not affect the foreach iteration.

Foreach support

Foreach is natively supported for any slice, array, pointer to an array, vector and pointer to a vector. These types support both iteration by value and reference.

In addition, a type with operator overload for len and [] will support iteration by value, and a type with operator overload for len and &[] will support iteration by reference.

Return statement

The return statement evaluates its expression (if present) and returns the result.

return_stmt        ::= "return" expr? ";"

Jumps in return statements

If the expression should in itself cause an implicit return, for example due to the rethrow operator !, then this jump will happen before the return.

An example:

return foo()!;
// is equivalent to:
int temp = foo()!;
return temp;

Return from expression blocks

A return from an expression block only returns out of the expression block, it never returns from the expression block’s enclosing scope.

Empty returns

An empty return is equivalent to a return with a void type. Consequently constructs like foo(); return; and return (void)foo(); are equivalent.

Unreachable code

Any statement directly following a return in the same scope are considered unreachable.

While statement

The while statement evaluates the cond expression and executes the statement if it evaluates to true. After this the cond expression is evaluated again and the process is repeated until cond expression returns false.

while_stmt         ::= "while" label? "(" cond_expr ")" stmt

Unreachable code

The statement after a while is considered unreachable if the cond expression cannot ever be false and there is no break out of the while.

Break

break will exit the while with execution continuing on the following statement.

Continue

continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached.

Var statement

A var statement declares a variable with inferred type, or a compile time type variable. It can be used both for runtime and compile time variables. The use for runtime variables is limited to macros.

var_stmt           ::= "var" IDENTIFIER | CT_IDENT | CT_TYPE_IDENT ("=" expr)? ";"

Inferring type

In the case of a runtime variable, the type is inferred from the expression. Not providing an expression is a compile time error. The expression must resolve to a runtime type.

For compile time variables, the expression is optional. The expression may resolve to a runtime or compile time type.

Scope

Runtime variables will follow the runtime scopes, identical to behaviour in a declaration statement. The compile time variables will follow the compile time scopes which are delimited by scoping compile time statements ($if, $switch, $foreach and $for).

Attributes

Attributes are modifiers attached to modules, variables, type declarations etc.

name used with
@align fn, const, variables, user-defined types, struct member
@benchmark module, fn
@bigendian bitstruct only
@builtin macro, fn, global, constant
@callconv fn, call
@deprecated fn, macro, interface, variables, constants, user-defined types, struct member
@dynamic fn
@export fn, globals, constants, struct, union, enum, fault
@extern fn, globals, constants, user-defined types
@if all except local variables and calls
@inline fn, call
@interface fn
@littleendian bitstruct only
@local module, fn, macro, globals, constants, user-defined types, attributes and aliases
@maydiscard fn, macro
@naked fn
@nodiscard fn, macro
@noinit variables
@noinline fn, call
@noreturn fn, macro
@nostrip fn, globals, constants, struct, union, enum, fault
@obfuscate enum, fault
@operator fn, macro
@optional interface methods
@overlap bitstruct only
@packed struct, union
@priority initializer/finalizer
@private module, fn, macro, globals, constants, user-defined types, attributes and aliases
@public module, fn, macro, globals, constants, user-defined types, attributes and aliases
@pure call
@reflect fn, globals, constants, user-defined types
@section fn, globals, constants
@test module, fn
@unused all except call and initializer/finalizers
@used all except call and initializer/finalizers
@weak fn, globals, constants
@winmain fn

@deprecated

Takes an optional constant string. If the node is in use, print the deprecation and add the optional string if present.

@optional

Marks an interface method as optional, and so does not need to be implemented by a conforming type.

@winmain

Marks a main function as a win32 winmain function, which is the entrypoint for a windowed application on Windows. This allows the main function to take a different set of arguments than usual.

@callconv

@callconv can be used with a function or a call. It takes a constant string which is either “veccall”, “stdcall” or “cdecl”. If more than one @callconv is applied to a function or call, the last one takes precedence.

User defined attributes

User defined attributes group a list of attributes.

attribute_decl     ::= "def" AT_TYPE_IDENT ("(" parameters ")")? attribute* "=" "{" attribute* "}" ";"

Empty list of attributes

The list of attributes may be empty.

Parameter arguments

Arguments given to user defined attributes will be passed on to the attributes in the list.

Expansion

When a user defined attribute is encountered, its list of attributes is copied and appended instead of the user defined attribute. Any argument passed to the attribute is evaluated and passed as a constant by the name of the parameter to the evaluation of the attribute parameters in the list.

Nesting

A user defined attribute can contain other user defined attributes. The definition may not be cyclic.

Methods

Operator overloading

@operator overloads may only be added to user defined types (distinct, unions, struct, enum and fault).

Indexing operator ([])

This requires a return type and a method parameter, which is the index.

Reference indexing operator (&[])

This requires a return type and a method parameter, which is the index. If [] is implemented, it should return a pointer to [].

Assigning index operator (=[])

This has a void return type, and index should match that of [] and &[]. Value should match that of [] and be the pointee of the result of &[].

Len operator (len)

This must have an integer return type.

Dynamic methods

@dynamic may be used on methods for any type except any and interfaces.

Modules

Module paths are hierarchal, with each sub-path appended with ‘::’ + the name:

path               ::= PATH_SEGMENT ("::" PATH_SEGMENT)

Each module declaration starts its own module section. All imports and all @local declarations are only visible in the current module section.

module_section     ::= "module" path opt_generic_params? attributes? ";"
generic_param      ::= TYPE_IDENT | CONST_IDENT
opt_generic_params ::= "(<" generic_param ("," generic_param)* ">)"

Any visibility attribute defined in a module section will be the default visibility in all declarations in the section.

If the @benchmark attribute is applied to the module section then all function declarations will implicitly have the @benchmark attribute.

If the @test attribute is applied to the module section then all function declarations will implicitly have the @test attribute.


title: Thank You description: Thank You sidebar:

order: 1000

Thank You

Special Mentions