cweet: Ergonomic, Strongly Typed C

The cweet programming languge aims to be a safer, more expressive replacement for the C programming language.

Expressions

cweet provides a set of powerful compound expressions that allows one to write terser code. In the following, the array sum can be a const array whereas in C any complex initialization will force the variable to be mutable.

// if-else as expressions.
var max = if (a > b) then a else b;

// A block of code as an expression.
var sum = block {
    var r : [100][100]int = undefined;
    loop for (var i; 0 .. r.len[0] - 1) {
        loop for (var j; 0 .. r.len[1] - 1) {
            r[i][j] = i+j;
        }
    }
    r // value of the block.
};

// loops as expressions.
var tmp = 0;
var sum = loop for (var i; 1 .. 10) break (tmp) { tmp += i; };

A problem with the last loop is that it introduces the mutable variable tmp into the scope. A with expression can be used to avoid this.

var sum = with (var tmp = 0) loop for (var i; 1 .. 10) break (tmp) { tmp += i; }

The with statement can also be used to implement the if (init; expr) construct in C++.

with (var s = a + b) if (s > 19) {
   // ...
} else {
   // The whole if-else is in the scope of with.
   // So s is available here.
}

Discriminative Unions and Pattern Matching

Support for tagged/anonymous discriminative unions with pattern matching.

// Tagged discriminative Unions.
type NetStatus = choice { Connected{ip : [4]byte}, Disconnected };

// match expression.
var desc = match (status) {
    Connected{[127, 0, 0, 1]} => "localhost",
    Connected{[192,168, ...]} => "private",
    Disconnected              => "disconnected",
    _                         => "other",
};

cweet also support anonymous tagged unions and they correspond to commutative sums. i.e., (int|float) and (float|int) are the same type.

// Anonymous discriminative unions.
fn is_zero(i : int|double) : bool {
    var epsilon = 0.01;

    return match (i) {
        `i : int    => i == 0, // A backtick indicates that a new binding is introduced.
        `d : double => abs(d) < epsilon
    };
}

var x = is_zero(3.14); // false
var y = is_zero(42); // false

A using can be used to unwrap product types and to introduce bindings by pattern matching.

type Status = choice { Connected{Address}, Disconnected };
type Address = choice { IPV4{[4]byte}, IPV6{[6]byte} };

type Point2D = struct { x : int, y : int };

// A struct with no patterns "unwraps" the struct
var dist = using (point) sqrt(x*x + y*y);

// Assume s1 and s2 are connected with IPV4 addresses.
// Equivalent to accessing a tagged union without checking the tag.
var cmp = using (s1.Connected.a.IPV4.a as `a1, s2.Connected.a.IPV4.a as `a2) equal(a1, a2);

It is undefined behaviour if the tag of s1 or s2 is not Connected or the Address is not IPV4.

Error Handling

If an expression has type maybe!T, then the expression may produce a value of type T or cause an error.

The following function cannot cause a segmentation fault.

fn max(x : *int, y : *int) : *int {
    return if (*x > *y) x else y;
}

A nullable pointer *T dereferences into a maybe!T. Therefore, the if-else expression has type maybe!(*int). If the value of this expression is a Nothing, it is automatically converted to the error indicator value of the return type, i.e., Null.

On the other hand, this wouldn't compile.

fn max(x : *int, y : *int) : int {
    return if (*x > *y) *x else *y;
}

because the type of the if-else expression is maybe!int. To fix this, you can either change the return type to maybe!int or change the function to accept only non-null pointers.

fn max(x : &int, y : &int) : int {
    return if (*x > *y) *x else *y;
}

One can write sum types and use them for error handling as follows:

type Handle = int;
type File = choice { Open{Handle}, DoesNotExist, NoPermission };

fn open(name : []char) : File;

// Handle success in a lexical scope.
// On Error, return error.
match (open("/etc/passwd").Open.a) {
    `f => use(f)
}

// Don't introduce new scope.
// Returns on error.
var f = open("/etc/passwd").Open.a;
use(f);

// Assume open always succeeds.
using (open("/etc/passwd").Open.a as `f) {
    use(f);
}

// Explicitly handle errors.
match (open("/etc/passwd")) {
    Open{`f} => use(f),
    DoesNotExist => print("File does not exist."),
    NoPermission => print("No Permission.")
}

The expression f.Open.a has type maybe!Handle. The expressions and pattern matches in a using are assumed to always succeed.

For more details spec.