Introduction

C* (pronounced “C star”) is a C-based hybrid low-level/high-level general-purpose programming language. It is designed for applications where runtime performance and programmer productivity matter. The language is simple and unopinionated, supporting imperative, generic, data-oriented, functional, and object-oriented programming. The language also aims to support interoperability and side-by-side usage with existing C/C++ code to allow gradual adoption.

Goals

The primary goals of C* are:

Why C*?

C++ is a huge and complex language that has accumulated many problems over the years, for example:

It is clear that we can’t keep adding more and more features to C++ and writing new code in it forever. Sooner or later it will be replaced by another language or languages (for writing new code).

“Within C++, there is a much smaller and cleaner language struggling to get out.” — Bjarne Stroustrup

C* is trying to be that language.

Let’s take a look at another popular systems programming language, Rust, which also solves the majority of the above problems. Rust differs from C* in the following aspects:

So in summary, C* is intended to be used over Rust for non-safety-critical applications where programmer productivity, ergonomics, and performance are more important than Rust’s safety and explicitness.

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”.

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

Improved syntax

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++

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, such as implicit calls to copy constructors and copy assignment operators like in C++.

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 some level of interoperability with C++ APIs is a longer-term goal.

Additional language features

Destructuring: While iterating a map, the key and value from the key-value pair can be destructured into separate variables. Functions can return multiple return values, which can then be destructured into separate variables at the call site.

Compile-time reflection to 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 as 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 to allow 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.

Simple type inference for local and global variables, to improve productivity and make the code more readable, as well as more amenable to type changes when refactoring. The strong type system ensures that type inference will not cause any unexpected typing issues.

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

Design principles

Difference between C* and Rust

Difference between C* and Zig

Difference between C* and Jai

Resources for further C* language design


  1. https://boats.gitlab.io/blog/post/2017-01-04-the-rust-module-system-is-too-confusing/↩︎