C*

Introduction

Note: This page is incomplete and parts of it may be out of date.

C* is a modern system programming language designed as an alternative to C++, C, and Rust. The primary goals of C* are programmer productivity and performance. Memory safety is also a priority, but while C* improves on what C++ and C have to offer, it doesn’t go as far as Rust at the detriment of ergonomics and productivity.

Better compilation model

All of the above should result in faster compilation times and increased programmer productivity compared to C and C++.

Standard build system

Building a C* project is done with a single command: cx build, which works out of the box without a single line of build configuration code. It finds all C* source files in the project directory (recursively) and compiles them into an executable. Additional build information, such as dependencies, can be declared in a package configuration file. cx build will then download or find these dependencies, and link them into the resulting executable.

No need to learn a new build system for each project, or manage project-specific build scripts, which is often the case with C and C++ projects.

Long-term goal: include a built-in linting tool that runs during compilation to enforce a specific coding style, or to disallow uses of certain language features.

Standard package manager

Using and distributing third-party libraries should be made as easy as possible. For example, there should be no platform-specific dependency or release management. Adding a dependency or publishing a library means running a single command.

Initially supports fetching dependencies from Git repositories. Other version control systems, binary distributions, system package managers, etc. will be supported later.

Faster than C and C++

More optimization opportunities:

C* uses the open-source LLVM library as a code generation back-end, benefiting from all current and future optimizations implemented in LLVM.

No hidden expensive operations, like implicit calls to copy constructors and copy assignment operators in C++.

Improved syntax

C*’s syntax is clean, consistent, and similar to the C family of languages.

No C-style cast syntax, only C++-style casts and built-in type constructors.

Examples of syntax

Syntax of a lambda returning the result of a single expression, with argument type inference:

// C++
a([](auto& b) { return b.c(); });

// C*
a(b -> b.c());

Function pointer declaration:

// C/C++
void (*f)(int);

// C*
void(int) f;

Simple and expressive language

Semantics are more in line with usual programmer expectations:

Safer by default

C* inserts at least the following safety checks by default:

These checks can be disabled individually or globally to make the code run faster.

The compiler warns if a variable might be read before being initialized, for example when passing it as an out parameter to a C function. If you know the warning is safe to ignore, you can suppress it by assigning the keyword undefined to the variable.

The compiler warns if a function’s return value is ignored, by default. The function can be annotated to suppress this behavior, if the return value is safe to ignore. The call site can also explicitly declare the return value as unused by assigning it to _, instead of casting to void. _ is a magic identifier which is also used in other contexts to mean “ignore this value”.

Transparent interoperation with existing C APIs

C headers can be imported directly from C* code. The Clang API is used to parse the C headers and allow the contained declarations to be accessed from C* code. C* functions can be declared extern "C" to enable calling them from C.

Support for the same level of interoperability with C++ APIs is a longer-term goal.

Improved type system

Stronger typing to help avoid bugs and to make refactoring easier:

Modern type system features for convenience and performance:

Standard library covers common use cases better

Language features

Destructuring

While iterating a map, the key and value from the key-value tuple can be destructured into separate variables:

for (var (key, value) in myMap) {
    ...
}

Destructure the return value of a function returning multiple values as a tuple:

(int, bool) foo() {
    var a = 42;
    var b = false;
    return (a, b);
}

void main() {
    var (a, b) = foo();
}

Compile-time reflection

Should cover common use cases such as iteration over enum cases, getting enum case string representations, printing the active type of a union, etc. Should follow the “pay only for what you use” principle.

Named arguments

An optional way to avoid cryptic call sites like foo(true, false) by labeling the arguments with the corresponding parameter name, with the compiler checking that the argument labels match the parameter names, e.g. foo(a: true, b: false). Consequently, named arguments don’t have to be in same order as the parameters. This also enables function overloading on parameter names.

Defer statement

The defer keyword allows declaring arbitrary code, such as resource cleanup functions, to be executed when exiting the current scope. In C++ one has to write an RAII wrapper class to achieve this.

Type inference

Type inference for local and global variables, to improve productivity and make the code more amenable to type changes when refactoring. The strong type system still has your back.

…and all the good parts from C and C++