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)
For Loop
// the for-loop is the same as C99.
for ( int i = 0 ; i < 10 ; i ++ )
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)
While Loop
// again exactly the same as C
while (Point * p = getPoint ())
Enum And Switch
Switches have implicit break and scope. Use “nextcase” to implicitly fallthrough or use comma:
fn void demo_enum (Height h)
// Completely empty cases are not allowed.
break ; // Explicit break required, since switches can't be empty.
// special checking of switching on enum types
default : // warning: default label in switch which covers all enumeration value
// Using "nextcase" will fallthrough to the next case statement,
// and each case statement starts its own scope.
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.
State start = State . values [ 0 ];
usz enums = State . elements ; // 2
String[] names = State . names ; // [ "START", "STOP" ]
Defer
Defer will be invoked on scope exit.
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.
defer try io:: printn ( " X " );
defer catch io:: printn ( " B " );
defer catch (err) io:: printfn ( " %s " , err . message );
if (x == 1 ) return FooError ? ;
test ( 1 ); // Prints "FOOBA" and returns a FooError
Struct Types
def Callback = fn int ( char c);
// named sub-structs (x.other.value)
int status; // ok, no name clash with other status
// anonymous sub-structs (x.value)
int status; // error, name clash with other status in MyData
// anonymous union (x.person)
// named sub-unions (x.either.this)
Function Pointers
def Callback = fn int ( char* text, int value);
fn int my_callback ( char* text, int value)
Callback cb = & my_callback;
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.
fn double! divide ( int a, int b)
// We return an optional result of type DIVISION_BY_ZERO
if (b == 0 ) return MathError . DIVISION_BY_ZERO ? ;
return ( double )a / ( double )b;
// Re-returning an optional result uses "!" suffix
// ratio is an optional result.
double! ratio = divide ( foo (), bar ());
// Handle the optional result value if it exists.
case MathError . DIVISION_BY_ZERO :
io:: printn ( " Division by zero \n " );
io:: printn ( " Unexpected error! " );
// 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);
case IoError . FILE_NOT_FOUND :
io:: printfn ( " I could not find the file %s " , filename);
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);
io:: printfn ( " Failed to load %s : %s " , filename, err);
// We return, so that below 'file' will be unwrapped.
// 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
@param array " the array to test "
@param length " length of the array "
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:
fn void Foo . next (Foo * this )
io:: printfn ( " %d " , foo . i );
Macros
Macro arguments may be immediately evaluated.
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.
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 "
square ( " hello " ); // Error: cannot multiply "hello"
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:
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 );
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:
return @ fib ($n - 1 ) + @ fib ($n - 2 );
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.
fn void Stack . push (Stack * this , Type element)
if ( this . capacity == this . size )
this . elems = realloc ( this . elems , Type . sizeof * this . capacity );
this . elems [ this . size ++ ] = element;
fn Type Stack . pop (Stack * this )
return this . elems [ --this . size ];
fn bool Stack . empty (Stack * this )
Testing it out:
def IntStack = Stack ( <int> );
io:: printfn ( " pop: %d " , stack . pop ());
io:: printfn ( " pop: %d " , stack . pop ());
io:: printfn ( " pop: %f " , dstack . pop ());
Read more about generic modules here
Dynamic Calls
Runtime dynamic dispatch through interfaces:
// Define a dynamic interface
struct Bob (MyName) { int x; }
// Required implementation as Bob implements MyName
fn String Bob . myname (Bob * ) @dynamic { return " I am Bob! " ; }
fn String int . myname ( int* ) @dynamic { return " I am int! " ; }
io:: printn ( " I don't know who I am. " );
Read more about dynamic calls here .