The compiler for the QuarticC (C⁴) programming language.
See the codeQuantumC (C⁴) is a general-purpose systems programming language. It is not a quantum computing language. It does not simulate quantum logic, compile to quantum circuits, or run on quantum hardware. QuantumC (C^4) is unrelated to the C99-to-OpenQASM compiler, QuantumC
int main() {
`qout("Hello, World!");
return 0;
}
Docs are here. Book coming soon.
More Powerful Than Explosives™/j
C⁴ combines explicitness and simplicity to produce readable code, improving both DX and UX. Code should still make sense next week, even to the person who wrote it.
QuantumC (C⁴/C^4) is a compiled, statically typed C-Style Systems programming language, focusing on clean code and low global scope pollution.
Go to the docs
Install the binaries from GitHub, use the Package & Version Manager, or:
You must have clang installed. (Obviously, it's a clang project)
After cloning the repo to your machine, run.
cd QuantumC
chmod +x install.sh
./install.sh
# Now, you can type
qc [flags] (filepath)
# to run a file
# or
qc [flags]
# to enter the REPL
QuantumC uses the following versioning scheme:
cMa.Mo.MiP
, where c is critical, for massive additions, such as the compiler being added, Ma being major versions, tracking large collections of features, Mo being moderate versions, tracking collections of similar features, Mi being minor versions, which track individual feature milestones within the current moderate version's theme, and P being the patch version.
For the version
x1.2.34
c = x
Ma = 1
Mo = 2
Mi = 3
P = 4
P is omitted if it is 0. Critical versions represent the largest generational milestones in QuantumC's development.
v = Interpreter x = Compiler (Current) f = Feature-complete compiler s = Self-hosted compiler
Critical versions are intentionally rare and denote architectural milestones, not language features.
Development toward future critical versions may begin before the current critical version is complete. Multiple critical generations may therefore be in development simultaneously.
Minor (Mi) is always a single decimal digit (0-9). Once a minor version reaches 9, the next release increments the moderate version instead.
Unlike semantic versioning, QuantumC versions describe the scale and category of language evolution rather than API compatibility.
Current Version: x1.0.47 = "I actually fixed try catch" Next Version: x1.0.5 = "Probably tagged enums"
Critical
└─ Added Compiler
Major
└─ N/A
Moderate
└─ Modifiers
Minor
└─ Filesystem
Patch
└─ Fixed default args on namespaced functions
These are deprecations in the past 3 moderate versions (x0.26.* -> x1.0.*)
Self parameter is now a pointer.
This now requires explicit dereference (must use -> instead of .)
Concepts as constraints must start with proves
| Category | Feature | Status |
|---|---|---|
| Core Logic | Variables & Types (int, float, double, string, char, bool) | Done |
Constants (const) & auto Inference | Done | |
| Long and Short types | Done | |
| Operators | Standard Math & Expressions (includes #^ power operator) | Done |
Control Flow (if/else, switch, while, for, foreach) | Done | |
| Functions | Functions with Default Parameters | Done |
| Lambdas & Higher-Order Functions | Done | |
| Native Multi-Return Values | Done | |
| Data Structures | Arrays (with .length member) | Done |
Spread Syntax for Arrays (@) | Done | |
| Enums | Done | |
| Advanced | Structs, Classes, & Namespaces | Done |
| Union Types (TypeScript-style) | Done | |
| Advanced OOP & Operator Overloading | Done | |
| Manual Memory Management | Done | |
| System | F-Strings (Python-style interpolation) | Done |
| Random Number Generation | Done | |
| Stdlib Part 1 & 2 | Done | |
| Future | Stdlib Part 3 | Planned |
| Inline ASM | Done | |
| Generics | Done | |
| Classes | Done | |
| Structs | Done | |
| Unions | Done | |
| Functions & Methods | Done | |
| Variadic Generic Arguments | Planned | |
restrict, out, inout, volatile | Done | |
| Extern | Done | |
| Bitwise Logic | Done | |
| Really fancy operator overloads | Done | |
Try/Catch and throw | Done | |
| Error message quality and helpfulness upgrade | Done |
See the full list of remaining features in the roadmap.
If you want to contribute to C^4, fork the repository, make your changes, and open a pull request.
Commits should follow this format:
category(thing): message
Available categories:
improve Improvements to existing functionality
feat New functionality or language features
clean Code cleanup, refactoring, or technical debt
errors Changes to diagnostics, warnings, or error handling / adding more errors, warnings, notes, help, suggestions, etc
fix Bug fixes
For example:
feat(parser, compiler): add modifier support
errors(warnings, diagnostics, help, suggestions): add help suggestions to warnings
clean(compiler): simplify type checking
fix(codegen): handle missing returns in main
improve(lexer): reduce tokenization overhead
If your commit is about multiple catagorys, comma-seperate.
errors(warnings),feat(cli-flags): added warnings & -W flags
But at least make your commits sensical. Unlike mine
FINALLY IM DONE WHY DID THAT TAKE 5 DAYS AAGHAGAHGAH IT WORKS
Please don't. If you forgot something in a previous commit, i like to use
oops(README): forgot to re-add readme.md
MIT License - See LICENSE for details
qbool qb = both;
qif (qb && qtrue /* evaluates to both */) {
`qout("True path"); // Executes
} qelse {
`qout("False path");
}
int main() {
int* ptr = `malloc(sizeof "int");
*ptr = 42;
`qout("%p", ptr);
`free(ptr);
}
namespace Exported {
/*
Exported namespaces are merged during include resolution.
See the include-system docs for full details.
*/
#include <Math, std> // std is an alias for ~/.qc/lib/stdlib.qc. This line imports the Math namespace from the standard library.
}
int main() {
return Math::Max(1234, 432); // Using the math namespace.
}
Namespaces can also declare dependencies on other namespaces in the same file
using #depends, ensuring includers automatically see required types even if
they only explicitly included one namespace from that file:
#depends(x: y)
namespace x { /* uses y::Something internally */ }
namespace y { /* ... */ }
Want to learn more? Check out the docs for it.
| Feature | C++ | Zig | Rust | QuantumC |
|---|---|---|---|---|
| Total Runtime | Medium | Medium | Medium | Medium |
| Compile Time (relative) | Slow | Medium | Medium | Medium |
| Runtime | Fast | Medium | Medium | Fast |
| Memory safety | Manual | GPA | Borrow checker | Manual |
| Multi-return | Structs | Tuples | Tuples | Native |
| Generics | Templates + Concepts | Type as Argument | Trait Based | Constraint-Based |
Based on the last reliable benchmark results, QuantumC showed performance in the same general range as C++, while offering a similar set of quality-of-life improvements found in languages such as Zig.According to most recent benchmarks, in tested cases C^4 runs either at a similar or faster speed than C++, with equal or faster compiles.
QuantumC has unusual naming conventions:
| Type | Convention | Why? |
|---|---|---|
| Variables | snake_case | It's familiar to Python devs who changed their ways, C++, C, Zig, Go, and Rust devs. |
| Functions | camelCase | It allows for instant knowledge between if an identifier is a var, or function (lambdas use var casing, not function casing) |
| User Types | PascalCase | It is common across basically every programming language. |
| Constants | SCREAMING_SNAKE_CASE | Same as above. |
| Private Member Variables | __snake_case | Variable case prepended with __. Most underscores. |
| Protected Member Variables | _snake_case | Less underscores. |
| Protected Methods | __camelCase | Unique casing, more underscores. |
| Private Methods | camel_Snake_Case | Function casing, more underscores. |
| Namespaces | PascalCase | Same as user types. |
| Namespaces Not Meant For Inclusion | Pascal_Snake_Case | Unique casing style, more underscores, you have to be trying to include this. |
| Global Scope Functions | camel_Snake_Case | Unique casing style, more underscores, similarity to private methods is intentional, because global scope cannot be included. |
| Methods Used By Compiler | _camelCase | Different from everything else. (these methods are iterators and stuff. Methods you define and compiler uses) |
| Compiler Reserved | _qc_, __qc_ and qc_ | Unique, hard to use accidently |
| Compiler Intrinsics | ` + snake_case | Unique, impossible to use accidently |
Max line size is around 120 relative to your starting indentation, tabs or spaces, lf newlines, comments are //, doc comments are ///, and top-level doc comments are //!. File paths are unquoted, everything other than main should go in a namespace when applicable, and namespaces should fit the following rules:
Namespaces should do one thing well, similar to the UNIX philosophy,
Namespaces should have either:
1. one type or group of tightly related types such as bigints and their core helpers,
2. above + namespaces containing extra helpers
3. helper functions / utility functions (think a `Math` namespace with log, cos...)
4. OR anything if directly mapping C/C++/Zig/Rust code to C^4
Types in namespaces should have short names: The namespace should have the longer name e.g.
namespace Array {
class Arr<T, int S = 0> {
...
}
}
Pointer asterisks bind to the type rather than the variable. The final * belongs to the declarator, unless it's a function return type. Then it's all on the type.
int** *x;
int* ptr_add(int *p) ...
Files are kebab-case. This is optional.
QuantumC naming conventions are designed to make code readable without requiring the reader to inspect library code. Names should provide immediate context.
My rule: RTFM once, not RTMSCE5S (Read The Manual and Source Code Every 5 Seconds), and these conventions make things hard to forget or mis-type, unlike C/C++ where every library uses entirely different conventions.
Example:
namespace Network {
class Client {
string server_name;
void connectToServer() {
...
}
}
}
namespace Not_Embezzeling { // Intentionally formatted as a non-inclusion namespace.
// Sure, you may not want to type all of that. That means your users absolutely don't.
..
}
QuantumC follows four core rules:
QuantumC uses a classic multi-pass compilation pipeline:
.o) or WebAssembly binaries via LLVM's target machines.int, string GetStatus() {
return 200, "Success";
}
int main() {
int code, string alias = GetStatus();
`qout("%s", f"Code: {code}, AKA: {alias}\n");
return 0;
}
Define variables that can hold multiple types using a simple | syntax. The parser automatically distinguishes these from standard aliases:
int main() {
// A Union Type (TypeScript-style)
type IdT = int | string;
IdT id = 101;
id = "A101"; // Perfectly valid
// A Standard Alias
type UserID = int;
UserID myId = 5;
}
Extern "C" is the only supported extern style, and thus no string is needed to say where you are externing to.
extern:
int add(int a, int b) {
return a + b;
}
:extern
Extern is only for externalizing api. To use foreign code, you must put it in a foreign block.
foreign:
int do_some_c_stuff(int x);
:foreign
The QuantumC inline ASM syntax is like a simplified version of the GCC syntax:
inline(R"(
mov rax, 1
mov rdi, 1
mov rsi, $0r ; argument 1 (Hello, World!)
mov rdx, $1r ; argument 2 (14)
syscall
)"/* your inline asm string */, "Hello, World", 14, "~{rax,rdi,rsi,rdx}" /* clobbers */);
You can also use AT&T ASM syntax by making sure the first 5 characters of your ASM string are
; ATT
QuantumC has 4 special (non-const) storage modifiers.
volatile
Volatile means the compiler won't optimize it. It can be used before variable declarations, or on function definitions. On methods, it must go after the access modifier and final, and before the return type.volatile void infinite_time() {
while(true) {
}
return;
}
restrictrestrict tells the compiler that this pointer is the exclusive access path to the referenced memory. Other unrelated pointers must not access the same memory in a way that violates the restrict contract. This allows more aggressive optimization.
Example:
void doSomePointer(int *restrict ptr, int *other) {
*ptr = 10;
*other = 20; // Undefined behavior if other points to the same memory as ptr
}
out
Out tells the compiler that this parameter is write-only, and this memory address will not be copied. It also only exists for optimization purposes.void writeOnly(out int p) {
p = 123; // OK
int x = p; // ILLEGAL. Breaks contract.
int *x = &p; // ILLEGAL also.
}
inout
Inout tells the compiler that this parameter will be read and written from, but its address will not be copied.void rw(inout int p) {
p = 123; // OK
int x = p; // OK
int *x = &p; // NOT OK
}
QuantumC variadic arguments look like this:
int add_all(...args) {
int res = 0;
while (!`is_empty(args)) {
res += `next(args, "int");
}
return res;
}
args is a variadic argument (hence the ...), which can be passed any number of parameters.
is_empty is a runtime compiler intrinsic that takes a variadic arg-list and returns whether it is empty.
next is a runtime compiler intrinsic that takes a variadic arg-list and the expected type and returns the next element in the arg list cast to that type.
You may use C-Style variadic arguments too, but only in foreign blocks.
foreign:
void printf(char* fmt, ...);
:foreign
QuantumC has all the standard bitwise logic operators; however, it has a non-standard XOR and Right-Shift token.
The Bitwise XOR operator in C^4 is $. It is $ because ^ and ^^ are already used tokens, and C^4 avoids repeating tokens to improve quick readability.
The same logic applies for right-shift: |> is the right-shift token, because it allows the parser to immediately determine:
Node<Vector<X>> // Is this RSHIFT or ending generics?
This is easy to determine (because it would be illegal for rshift to be there); however, using this non-normal rshift operator allows it to be instant because:
Node<Vector<X>> // This is unrelated to RSHIFT
QuantumC also has a special operator for logical right shift: :>. This is because it allows avoiding constantly casting between signed and unsigned integer types, unlike C++'s "arithmetic if signed, logical if unsigned"
2 more unique bitwise things QuantumC does:
<<< and |>> and LROT and RROT, allowing 1 instruction rotations instead of 6+.3 << 3 + 2 == 26. This is because the shift expression l << r is equivalent to l * 2 ^ r. If r was 2 + 2, that would be l * 2 ^ 2 + 2, which would be l * 4 + 2.All methods are virtual. If you want to use polymorphism, inherited methods MUST be defined in the same order as the parent class, and new methods must be defined after.
QuantumC generics have simple syntax:
class C<T> {
T x;
C() {
}
}
int main() {
C<int> thing = C();
}
Generics are allowed on structs, classes, concepts, unions, aliases, functions, and methods.
The unique thing about QuantumC's generics is its constraint system:
The constraint system follows this syntax:
<T([constraint]:[[!]<[typename]'|'...>]>
Main constraints are as follows:
usertype: non-primitive type
primitive: primitive type
pointer: any pointer type
numeric: any numeric type
So
<T(numeric:)>
Can be any numeric type. The subconstraint system is like this:
<T(:!int|string)>
This means "T can be any type other than int or string". The ! means not anything in this set, and the type|type means these types. QuantumC also has non-type generic parameters.
<int S> // S is a non-type generic parameter (a compile time int)
In Rust,
<T(numeric:)>
Would be
<T: std::ops::Add<Output = T> + std::ops::Sub<Output = T> + std::ops::Mul<Output = T> + std::ops::Div<Output = T> + PartialOrd + Copy>
And in C++, it would be
template <typename T>
requires std::is_arithmetic_v<T>
T
or in old SFINAE C++
template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
T
And in Zig:
...(comptime T: type, ...) ... {
switch (@typeInfo(T)) {
.Int, .Float => ...,
else => @compileError("T must be numeric"),
}
}
Iterators work as follows: The class you want to iterate must define the following method:
MyIterator _begin() // Returns an iterator to the beginning of the class
_end is optional, and returns an iterator to the end of the class.
The iterator itself MUST define the following 2 methods:
bool _atEnd() // Returns true if there is nothing left to iterate
T _next() // Returns the current value then moves to the next
and may define the following recommended extra methods:
bool _atStart() // Returns true if the iterator is currently at index 0
T _prev() // Returns the element at current index -- and decrements current index
void _moveTo(whateveryouwant idx) // sets index to idx
MyIterator<U> _map<U>(fn(T elem) -> U) // preforms a operation on every element
MyClass<T> _collect() // returns the class that this iterator iterates
MyIterator<T> _filter(fn(T elem) -> bool) // returns a iterator of only the elements that are true for that lambda
void _inval_destroy() // invalidates the collection this iterator iterates
MyIterator<T> _take(int n) // returns a iterator to the first n elements
MyIterator<T> _drop(int n) // returns a iterator skipping the first n elements
Example:
class ArrayIterator<T> {
T* data;
int size;
int current_index;
ArrayIterator(T* data, int size, bool is_end) {
this.data = data;
this.size = size;
this.current_index = `ternary(is_end, size, 0);
}
bool _atEnd() {
return this.size <= this.current_index;
}
T _next() {
if (!this._atEnd()) {
return this.data[this.current_index++];
}
return this.data[this.current_index];
}
bool _atStart() {
return this.current_index <= 0;
}
T _prev() {
if (!this._atStart()) {
return this.data[--this.current_index];
}
return this.data[this.current_index];
}
void _moveTo(int index) {
if (index >= this.size) {
index = this.size - 1;
} else if (index < 0) {
index = 0;
}
this.current_index = index;
}
}
class Array<T, int S = 0> {
T* data;
int size;
Array() {
this.data = nullptr;
this.size = 0;
}
void operator[]=(T* data, int length) {
if (length > S) {
this.size = length;
} else {
this.size = S;
}
this.data = `malloc(sizeof "T" * this.size);
for (int i = 0; i < this.size; i++) {
this.data[i] = data[i];
}
}
T operator[](int index) {
return this.data[index];
}
ArrayIterator<T> _begin() {
return ArrayIterator<T>(this.data, this.size, false);
}
ArrayIterator<T> _end() {
return ArrayIterator<T>(this.data, this.size, true);
}
}
The standard library collections have iterators in the structure of this:
namespace Array
namespace Iterator {
class It {
...
}
}
class Arr {
Iterator::It _begin() {
...
}
...
}
}
C^4 supports both try/catch exception handling and multireturn/unions for error handling. You are encouraged to use both, or combine them where appropriate. Just document if you throw or not.
Example:
int main() {
try {
throw 123;
} catch (int e) {
...
}
}
Or for union-based:
struct myerror {
...
}
type MyResult = int | myerror;
MyResult myThing() {
return myerror{...};
}
Nested try/catch blocks are not allowed becuase they just obfuscate what your code is doing without adding any new functionality.
C^4 has defer, like Go and Zig.
Unlike Go's defer, C^4 defer does not participate in stack unwinding. Deferred code is not automatically executed when an exception propagates out of a scope.
This behavior is intentional: making defer participate in stack unwinding would introduce additional runtime overhead.
defer should not be considered a destructor mechanism. It is a scope-exit convenience feature, not RAII.
C^4's concepts are like a combination of Typescript interfaces, Rust traits, and C++ concepts.
concept Printable {
1_of {
void print();
void print(Self self);
}
default {
class:
void print() {
`qout("Printing. . .");
}
else:
void print(Self self) {
`qout ("Printing. . .");
}
}
}
class PDF {
string data;
PDF(string data) {
this.data = data;
}
void print() {
`qout("%s", this.data);
}
}
class ASCII {
string data;
ASCII(string data) {
this.data = data;
}
}
struct Paper {
string data;
}
type File = PDF | ASCII;
/// Print would print PDFs data
PDF proves Printable;
/// Print would print "Printing. . ."
ASCII proves Printable;
/// Print would print the paper's data
Paper proves Printable with_proof { // with_proof block to add additional definitions, like impl in Rust
void print(Self self) {
`qout("%s", self.data);
}
}
/// Would print "Printing. . ."
File proves Printable;
The Self arguments are explicit for non-class methods added through concepts. This is to emphasize structs are not classes, and should stick to being POD. Concepts only allow methods on structs because it's better for DRY than having 50 functions with similar names.
namespace Exported {
#include<Vector, std>
}
int main() {
Vector::Vec<int> my_vec = [1, 2, 3];
my_vec.push(123);
`qout("%i", my_vec[2]);
return 0;
}
There is currently an unknown bug with the self-hosted runtime.
All benchmarks were executed on Linux (x86_64) using /usr/bin/time across 500 runs per optimization tier (4,000 total benchmark runs). Workload: identical compute-heavy arithmetic and bitwise algorithm global state.
Src can be found in the bench<...> files in the project root.
| Category | Winner | Advantage |
|---|---|---|
| Compilation Speed | C^4 (qc) | ~1.85x faster (up to 47.3% lower latency) |
| Compiler Memory (Peak RSS) | C^4 (qc) | 31.6% less RAM (~60 MB leaner) |
| Compiler OS Page Faults | C^4 (qc) | 68.4% fewer page faults (~10,000 fewer faults/compile) |
Unoptimized Runtime (-O0) | C^4 (qc) | 2.0x faster (50.0% reduction in execution time) |
Optimized Runtime (-O1–-O3) | Tie | Exact Parity (Both clock identical 30.00 ms) |
| Binary Runtime Memory | C^4 (qc) | 44.7% less memory (2.11 MB vs 3.81 MB) |
| Opt Level | Compiler | Elapsed Time | User CPU Time | Sys CPU Time | CPU Util | Relative Speed |
|---|---|---|---|---|---|---|
-O0 | Clang C++ | 301.54 ms | 271.60 ms | 23.82 ms | 99.0% | 1.00x |
C^4 (qc) | 159.02 ms | 95.40 ms | 8.50 ms | 68.7% | 1.90x faster (-47.3%) | |
-O1 | Clang C++ | 313.14 ms | 282.86 ms | 23.64 ms | 99.0% | 1.00x |
C^4 (qc) | 168.74 ms | 48.28 ms | 11.22 ms | 39.0% | 1.86x faster (-46.1%) | |
-O2 | Clang C++ | 314.12 ms | 283.80 ms | 24.12 ms | 99.0% | 1.00x |
C^4 (qc) | 169.04 ms | 49.26 ms | 11.40 ms | 39.5% | 1.86x faster (-46.2%) | |
-O3 | Clang C++ | 315.72 ms | 284.98 ms | 23.88 ms | 99.0% | 1.00x |
C^4 (qc) | 171.54 ms | 55.04 ms | 11.78 ms | 42.6% | 1.84x faster (-45.7%) |
Note: At
-O3, C^4 uses 5.2x less User CPU time (55.04 ms vs 284.98 ms) by eliminating C++ preprocessor and template instantiation overhead.
| Opt Level | Compiler | Peak RSS (MB) | Minor Page Faults | Major Faults | I/O (Out) |
|---|---|---|---|---|---|
-O0 | Clang C++ | 177.84 MB (182,110 KB) | 14,777.7 | 0 | 40.0 |
C^4 (qc) | 129.70 MB (132,818 KB) | 5,080.1 | 0 | 416.0 | |
| Delta | -27.1% RAM | -65.6% faults | — | — | |
-O1 | Clang C++ | 177.97 MB (182,239 KB) | 14,802.2 | 0 | 40.0 |
C^4 (qc) | 128.79 MB (131,886 KB) | 4,625.0 | 0 | 72.0 | |
| Delta | -27.6% RAM | -68.8% faults | — | — | |
-O2 | Clang C++ | 177.97 MB (182,244 KB) | 14,835.1 | 0 | 40.0 |
C^4 (qc) | 128.75 MB (131,841 KB) | 4,627.8 | 0 | 72.0 | |
| Delta | -27.6% RAM | -68.8% faults | — | — | |
-O3 | Clang C++ | 188.09 MB (192,601 KB) | 14,635.2 | 0 | 40.0 |
C^4 (qc) | 128.73 MB (131,819 KB) | 4,620.6 | 0 | 72.0 | |
| Delta | -31.6% RAM (-59.4 MB) | -68.4% faults | — | — |
| Opt Level | Clang (C++) Binary | C^4 Binary | Speedup / Delta |
|---|---|---|---|
-O0 | 60.00 ms | 30.00 ms | 2.00x faster (+100% speedup) |
-O1 | 30.00 ms | 30.00 ms | Parity (1.00x) |
-O2 | 30.00 ms | 30.00 ms | Parity (1.00x) |
-O3 | 30.00 ms | 30.00 ms | Parity (1.00x) |
| Opt Level | Binary | Peak RSS (KB) | Peak RSS (MB) | Minor Faults |
|---|---|---|---|---|
-O0 | Clang C++ | 3,891.34 KB | 3.80 MB | 150.9 |
C^4 (qc) | 2,334.83 KB | 2.28 MB | 97.9 | |
| Delta | -39.9% memory | — | -35.1% faults | |
-O1 | Clang C++ | 3,892.55 KB | 3.80 MB | 150.7 |
C^4 (qc) | 2,156.53 KB | 2.11 MB | 90.6 | |
| Delta | -44.6% memory | — | -39.9% faults | |
-O2 | Clang C++ | 3,898.58 KB | 3.81 MB | 150.8 |
C^4 (qc) |
&& and || do not short-circuit on unions. Volatile does not exist on structs or class fields, and does not work on property accesses. In general, volatile is not 100%.
Vector
List
Array
AdvQBool
Math
Utils
OSInterop
Made by Luca Fazio
432 commits
C++
93.6%
C
6.1%
The compiler for the QuarticC (C⁴) programming language.
See the codeQuantumC (C⁴) is a general-purpose systems programming language. It is not a quantum computing language. It does not simulate quantum logic, compile to quantum circuits, or run on quantum hardware. QuantumC (C^4) is unrelated to the C99-to-OpenQASM compiler, QuantumC
int main() {
`qout("Hello, World!");
return 0;
}
Docs are here. Book coming soon.
More Powerful Than Explosives™/j
C⁴ combines explicitness and simplicity to produce readable code, improving both DX and UX. Code should still make sense next week, even to the person who wrote it.
QuantumC (C⁴/C^4) is a compiled, statically typed C-Style Systems programming language, focusing on clean code and low global scope pollution.
Go to the docs
Install the binaries from GitHub, use the Package & Version Manager, or:
You must have clang installed. (Obviously, it's a clang project)
After cloning the repo to your machine, run.
cd QuantumC
chmod +x install.sh
./install.sh
# Now, you can type
qc [flags] (filepath)
# to run a file
# or
qc [flags]
# to enter the REPL
QuantumC uses the following versioning scheme:
cMa.Mo.MiP
, where c is critical, for massive additions, such as the compiler being added, Ma being major versions, tracking large collections of features, Mo being moderate versions, tracking collections of similar features, Mi being minor versions, which track individual feature milestones within the current moderate version's theme, and P being the patch version.
For the version
x1.2.34
c = x
Ma = 1
Mo = 2
Mi = 3
P = 4
P is omitted if it is 0. Critical versions represent the largest generational milestones in QuantumC's development.
v = Interpreter x = Compiler (Current) f = Feature-complete compiler s = Self-hosted compiler
Critical versions are intentionally rare and denote architectural milestones, not language features.
Development toward future critical versions may begin before the current critical version is complete. Multiple critical generations may therefore be in development simultaneously.
Minor (Mi) is always a single decimal digit (0-9). Once a minor version reaches 9, the next release increments the moderate version instead.
Unlike semantic versioning, QuantumC versions describe the scale and category of language evolution rather than API compatibility.
Current Version: x1.0.47 = "I actually fixed try catch" Next Version: x1.0.5 = "Probably tagged enums"
Critical
└─ Added Compiler
Major
└─ N/A
Moderate
└─ Modifiers
Minor
└─ Filesystem
Patch
└─ Fixed default args on namespaced functions
These are deprecations in the past 3 moderate versions (x0.26.* -> x1.0.*)
Self parameter is now a pointer.
This now requires explicit dereference (must use -> instead of .)
Concepts as constraints must start with proves
| Category | Feature | Status |
|---|---|---|
| Core Logic | Variables & Types (int, float, double, string, char, bool) | Done |
Constants (const) & auto Inference | Done | |
| Long and Short types | Done | |
| Operators | Standard Math & Expressions (includes #^ power operator) | Done |
Control Flow (if/else, switch, while, for, foreach) | Done | |
| Functions | Functions with Default Parameters | Done |
| Lambdas & Higher-Order Functions | Done | |
| Native Multi-Return Values | Done | |
| Data Structures | Arrays (with .length member) | Done |
Spread Syntax for Arrays (@) | Done | |
| Enums | Done | |
| Advanced | Structs, Classes, & Namespaces | Done |
| Union Types (TypeScript-style) | Done | |
| Advanced OOP & Operator Overloading | Done | |
| Manual Memory Management | Done | |
| System | F-Strings (Python-style interpolation) | Done |
| Random Number Generation | Done | |
| Stdlib Part 1 & 2 | Done | |
| Future | Stdlib Part 3 | Planned |
| Inline ASM | Done | |
| Generics | Done | |
| Classes | Done | |
| Structs | Done | |
| Unions | Done | |
| Functions & Methods | Done | |
| Variadic Generic Arguments | Planned | |
restrict, out, inout, volatile | Done | |
| Extern | Done | |
| Bitwise Logic | Done | |
| Really fancy operator overloads | Done | |
Try/Catch and throw | Done | |
| Error message quality and helpfulness upgrade | Done |
See the full list of remaining features in the roadmap.
If you want to contribute to C^4, fork the repository, make your changes, and open a pull request.
Commits should follow this format:
category(thing): message
Available categories:
improve Improvements to existing functionality
feat New functionality or language features
clean Code cleanup, refactoring, or technical debt
errors Changes to diagnostics, warnings, or error handling / adding more errors, warnings, notes, help, suggestions, etc
fix Bug fixes
For example:
feat(parser, compiler): add modifier support
errors(warnings, diagnostics, help, suggestions): add help suggestions to warnings
clean(compiler): simplify type checking
fix(codegen): handle missing returns in main
improve(lexer): reduce tokenization overhead
If your commit is about multiple catagorys, comma-seperate.
errors(warnings),feat(cli-flags): added warnings & -W flags
But at least make your commits sensical. Unlike mine
FINALLY IM DONE WHY DID THAT TAKE 5 DAYS AAGHAGAHGAH IT WORKS
Please don't. If you forgot something in a previous commit, i like to use
oops(README): forgot to re-add readme.md
MIT License - See LICENSE for details
qbool qb = both;
qif (qb && qtrue /* evaluates to both */) {
`qout("True path"); // Executes
} qelse {
`qout("False path");
}
int main() {
int* ptr = `malloc(sizeof "int");
*ptr = 42;
`qout("%p", ptr);
`free(ptr);
}
namespace Exported {
/*
Exported namespaces are merged during include resolution.
See the include-system docs for full details.
*/
#include <Math, std> // std is an alias for ~/.qc/lib/stdlib.qc. This line imports the Math namespace from the standard library.
}
int main() {
return Math::Max(1234, 432); // Using the math namespace.
}
Namespaces can also declare dependencies on other namespaces in the same file
using #depends, ensuring includers automatically see required types even if
they only explicitly included one namespace from that file:
#depends(x: y)
namespace x { /* uses y::Something internally */ }
namespace y { /* ... */ }
Want to learn more? Check out the docs for it.
| Feature | C++ | Zig | Rust | QuantumC |
|---|---|---|---|---|
| Total Runtime | Medium | Medium | Medium | Medium |
| Compile Time (relative) | Slow | Medium | Medium | Medium |
| Runtime | Fast | Medium | Medium | Fast |
| Memory safety | Manual | GPA | Borrow checker | Manual |
| Multi-return | Structs | Tuples | Tuples | Native |
| Generics | Templates + Concepts | Type as Argument | Trait Based | Constraint-Based |
Based on the last reliable benchmark results, QuantumC showed performance in the same general range as C++, while offering a similar set of quality-of-life improvements found in languages such as Zig.According to most recent benchmarks, in tested cases C^4 runs either at a similar or faster speed than C++, with equal or faster compiles.
QuantumC has unusual naming conventions:
| Type | Convention | Why? |
|---|---|---|
| Variables | snake_case | It's familiar to Python devs who changed their ways, C++, C, Zig, Go, and Rust devs. |
| Functions | camelCase | It allows for instant knowledge between if an identifier is a var, or function (lambdas use var casing, not function casing) |
| User Types | PascalCase | It is common across basically every programming language. |
| Constants | SCREAMING_SNAKE_CASE | Same as above. |
| Private Member Variables | __snake_case | Variable case prepended with __. Most underscores. |
| Protected Member Variables | _snake_case | Less underscores. |
| Protected Methods | __camelCase | Unique casing, more underscores. |
| Private Methods | camel_Snake_Case | Function casing, more underscores. |
| Namespaces | PascalCase | Same as user types. |
| Namespaces Not Meant For Inclusion | Pascal_Snake_Case | Unique casing style, more underscores, you have to be trying to include this. |
| Global Scope Functions | camel_Snake_Case | Unique casing style, more underscores, similarity to private methods is intentional, because global scope cannot be included. |
| Methods Used By Compiler | _camelCase | Different from everything else. (these methods are iterators and stuff. Methods you define and compiler uses) |
| Compiler Reserved | _qc_, __qc_ and qc_ | Unique, hard to use accidently |
| Compiler Intrinsics | ` + snake_case | Unique, impossible to use accidently |
Max line size is around 120 relative to your starting indentation, tabs or spaces, lf newlines, comments are //, doc comments are ///, and top-level doc comments are //!. File paths are unquoted, everything other than main should go in a namespace when applicable, and namespaces should fit the following rules:
Namespaces should do one thing well, similar to the UNIX philosophy,
Namespaces should have either:
1. one type or group of tightly related types such as bigints and their core helpers,
2. above + namespaces containing extra helpers
3. helper functions / utility functions (think a `Math` namespace with log, cos...)
4. OR anything if directly mapping C/C++/Zig/Rust code to C^4
Types in namespaces should have short names: The namespace should have the longer name e.g.
namespace Array {
class Arr<T, int S = 0> {
...
}
}
Pointer asterisks bind to the type rather than the variable. The final * belongs to the declarator, unless it's a function return type. Then it's all on the type.
int** *x;
int* ptr_add(int *p) ...
Files are kebab-case. This is optional.
QuantumC naming conventions are designed to make code readable without requiring the reader to inspect library code. Names should provide immediate context.
My rule: RTFM once, not RTMSCE5S (Read The Manual and Source Code Every 5 Seconds), and these conventions make things hard to forget or mis-type, unlike C/C++ where every library uses entirely different conventions.
Example:
namespace Network {
class Client {
string server_name;
void connectToServer() {
...
}
}
}
namespace Not_Embezzeling { // Intentionally formatted as a non-inclusion namespace.
// Sure, you may not want to type all of that. That means your users absolutely don't.
..
}
QuantumC follows four core rules:
QuantumC uses a classic multi-pass compilation pipeline:
.o) or WebAssembly binaries via LLVM's target machines.int, string GetStatus() {
return 200, "Success";
}
int main() {
int code, string alias = GetStatus();
`qout("%s", f"Code: {code}, AKA: {alias}\n");
return 0;
}
Define variables that can hold multiple types using a simple | syntax. The parser automatically distinguishes these from standard aliases:
int main() {
// A Union Type (TypeScript-style)
type IdT = int | string;
IdT id = 101;
id = "A101"; // Perfectly valid
// A Standard Alias
type UserID = int;
UserID myId = 5;
}
Extern "C" is the only supported extern style, and thus no string is needed to say where you are externing to.
extern:
int add(int a, int b) {
return a + b;
}
:extern
Extern is only for externalizing api. To use foreign code, you must put it in a foreign block.
foreign:
int do_some_c_stuff(int x);
:foreign
The QuantumC inline ASM syntax is like a simplified version of the GCC syntax:
inline(R"(
mov rax, 1
mov rdi, 1
mov rsi, $0r ; argument 1 (Hello, World!)
mov rdx, $1r ; argument 2 (14)
syscall
)"/* your inline asm string */, "Hello, World", 14, "~{rax,rdi,rsi,rdx}" /* clobbers */);
You can also use AT&T ASM syntax by making sure the first 5 characters of your ASM string are
; ATT
QuantumC has 4 special (non-const) storage modifiers.
volatile
Volatile means the compiler won't optimize it. It can be used before variable declarations, or on function definitions. On methods, it must go after the access modifier and final, and before the return type.volatile void infinite_time() {
while(true) {
}
return;
}
restrictrestrict tells the compiler that this pointer is the exclusive access path to the referenced memory. Other unrelated pointers must not access the same memory in a way that violates the restrict contract. This allows more aggressive optimization.
Example:
void doSomePointer(int *restrict ptr, int *other) {
*ptr = 10;
*other = 20; // Undefined behavior if other points to the same memory as ptr
}
out
Out tells the compiler that this parameter is write-only, and this memory address will not be copied. It also only exists for optimization purposes.void writeOnly(out int p) {
p = 123; // OK
int x = p; // ILLEGAL. Breaks contract.
int *x = &p; // ILLEGAL also.
}
inout
Inout tells the compiler that this parameter will be read and written from, but its address will not be copied.void rw(inout int p) {
p = 123; // OK
int x = p; // OK
int *x = &p; // NOT OK
}
QuantumC variadic arguments look like this:
int add_all(...args) {
int res = 0;
while (!`is_empty(args)) {
res += `next(args, "int");
}
return res;
}
args is a variadic argument (hence the ...), which can be passed any number of parameters.
is_empty is a runtime compiler intrinsic that takes a variadic arg-list and returns whether it is empty.
next is a runtime compiler intrinsic that takes a variadic arg-list and the expected type and returns the next element in the arg list cast to that type.
You may use C-Style variadic arguments too, but only in foreign blocks.
foreign:
void printf(char* fmt, ...);
:foreign
QuantumC has all the standard bitwise logic operators; however, it has a non-standard XOR and Right-Shift token.
The Bitwise XOR operator in C^4 is $. It is $ because ^ and ^^ are already used tokens, and C^4 avoids repeating tokens to improve quick readability.
The same logic applies for right-shift: |> is the right-shift token, because it allows the parser to immediately determine:
Node<Vector<X>> // Is this RSHIFT or ending generics?
This is easy to determine (because it would be illegal for rshift to be there); however, using this non-normal rshift operator allows it to be instant because:
Node<Vector<X>> // This is unrelated to RSHIFT
QuantumC also has a special operator for logical right shift: :>. This is because it allows avoiding constantly casting between signed and unsigned integer types, unlike C++'s "arithmetic if signed, logical if unsigned"
2 more unique bitwise things QuantumC does:
<<< and |>> and LROT and RROT, allowing 1 instruction rotations instead of 6+.3 << 3 + 2 == 26. This is because the shift expression l << r is equivalent to l * 2 ^ r. If r was 2 + 2, that would be l * 2 ^ 2 + 2, which would be l * 4 + 2.All methods are virtual. If you want to use polymorphism, inherited methods MUST be defined in the same order as the parent class, and new methods must be defined after.
QuantumC generics have simple syntax:
class C<T> {
T x;
C() {
}
}
int main() {
C<int> thing = C();
}
Generics are allowed on structs, classes, concepts, unions, aliases, functions, and methods.
The unique thing about QuantumC's generics is its constraint system:
The constraint system follows this syntax:
<T([constraint]:[[!]<[typename]'|'...>]>
Main constraints are as follows:
usertype: non-primitive type
primitive: primitive type
pointer: any pointer type
numeric: any numeric type
So
<T(numeric:)>
Can be any numeric type. The subconstraint system is like this:
<T(:!int|string)>
This means "T can be any type other than int or string". The ! means not anything in this set, and the type|type means these types. QuantumC also has non-type generic parameters.
<int S> // S is a non-type generic parameter (a compile time int)
In Rust,
<T(numeric:)>
Would be
<T: std::ops::Add<Output = T> + std::ops::Sub<Output = T> + std::ops::Mul<Output = T> + std::ops::Div<Output = T> + PartialOrd + Copy>
And in C++, it would be
template <typename T>
requires std::is_arithmetic_v<T>
T
or in old SFINAE C++
template <typename T, typename = std::enable_if_t<std::is_arithmetic_v<T>>>
T
And in Zig:
...(comptime T: type, ...) ... {
switch (@typeInfo(T)) {
.Int, .Float => ...,
else => @compileError("T must be numeric"),
}
}
Iterators work as follows: The class you want to iterate must define the following method:
MyIterator _begin() // Returns an iterator to the beginning of the class
_end is optional, and returns an iterator to the end of the class.
The iterator itself MUST define the following 2 methods:
bool _atEnd() // Returns true if there is nothing left to iterate
T _next() // Returns the current value then moves to the next
and may define the following recommended extra methods:
bool _atStart() // Returns true if the iterator is currently at index 0
T _prev() // Returns the element at current index -- and decrements current index
void _moveTo(whateveryouwant idx) // sets index to idx
MyIterator<U> _map<U>(fn(T elem) -> U) // preforms a operation on every element
MyClass<T> _collect() // returns the class that this iterator iterates
MyIterator<T> _filter(fn(T elem) -> bool) // returns a iterator of only the elements that are true for that lambda
void _inval_destroy() // invalidates the collection this iterator iterates
MyIterator<T> _take(int n) // returns a iterator to the first n elements
MyIterator<T> _drop(int n) // returns a iterator skipping the first n elements
Example:
class ArrayIterator<T> {
T* data;
int size;
int current_index;
ArrayIterator(T* data, int size, bool is_end) {
this.data = data;
this.size = size;
this.current_index = `ternary(is_end, size, 0);
}
bool _atEnd() {
return this.size <= this.current_index;
}
T _next() {
if (!this._atEnd()) {
return this.data[this.current_index++];
}
return this.data[this.current_index];
}
bool _atStart() {
return this.current_index <= 0;
}
T _prev() {
if (!this._atStart()) {
return this.data[--this.current_index];
}
return this.data[this.current_index];
}
void _moveTo(int index) {
if (index >= this.size) {
index = this.size - 1;
} else if (index < 0) {
index = 0;
}
this.current_index = index;
}
}
class Array<T, int S = 0> {
T* data;
int size;
Array() {
this.data = nullptr;
this.size = 0;
}
void operator[]=(T* data, int length) {
if (length > S) {
this.size = length;
} else {
this.size = S;
}
this.data = `malloc(sizeof "T" * this.size);
for (int i = 0; i < this.size; i++) {
this.data[i] = data[i];
}
}
T operator[](int index) {
return this.data[index];
}
ArrayIterator<T> _begin() {
return ArrayIterator<T>(this.data, this.size, false);
}
ArrayIterator<T> _end() {
return ArrayIterator<T>(this.data, this.size, true);
}
}
The standard library collections have iterators in the structure of this:
namespace Array
namespace Iterator {
class It {
...
}
}
class Arr {
Iterator::It _begin() {
...
}
...
}
}
C^4 supports both try/catch exception handling and multireturn/unions for error handling. You are encouraged to use both, or combine them where appropriate. Just document if you throw or not.
Example:
int main() {
try {
throw 123;
} catch (int e) {
...
}
}
Or for union-based:
struct myerror {
...
}
type MyResult = int | myerror;
MyResult myThing() {
return myerror{...};
}
Nested try/catch blocks are not allowed becuase they just obfuscate what your code is doing without adding any new functionality.
C^4 has defer, like Go and Zig.
Unlike Go's defer, C^4 defer does not participate in stack unwinding. Deferred code is not automatically executed when an exception propagates out of a scope.
This behavior is intentional: making defer participate in stack unwinding would introduce additional runtime overhead.
defer should not be considered a destructor mechanism. It is a scope-exit convenience feature, not RAII.
C^4's concepts are like a combination of Typescript interfaces, Rust traits, and C++ concepts.
concept Printable {
1_of {
void print();
void print(Self self);
}
default {
class:
void print() {
`qout("Printing. . .");
}
else:
void print(Self self) {
`qout ("Printing. . .");
}
}
}
class PDF {
string data;
PDF(string data) {
this.data = data;
}
void print() {
`qout("%s", this.data);
}
}
class ASCII {
string data;
ASCII(string data) {
this.data = data;
}
}
struct Paper {
string data;
}
type File = PDF | ASCII;
/// Print would print PDFs data
PDF proves Printable;
/// Print would print "Printing. . ."
ASCII proves Printable;
/// Print would print the paper's data
Paper proves Printable with_proof { // with_proof block to add additional definitions, like impl in Rust
void print(Self self) {
`qout("%s", self.data);
}
}
/// Would print "Printing. . ."
File proves Printable;
The Self arguments are explicit for non-class methods added through concepts. This is to emphasize structs are not classes, and should stick to being POD. Concepts only allow methods on structs because it's better for DRY than having 50 functions with similar names.
namespace Exported {
#include<Vector, std>
}
int main() {
Vector::Vec<int> my_vec = [1, 2, 3];
my_vec.push(123);
`qout("%i", my_vec[2]);
return 0;
}
There is currently an unknown bug with the self-hosted runtime.
All benchmarks were executed on Linux (x86_64) using /usr/bin/time across 500 runs per optimization tier (4,000 total benchmark runs). Workload: identical compute-heavy arithmetic and bitwise algorithm global state.
Src can be found in the bench<...> files in the project root.
| Category | Winner | Advantage |
|---|---|---|
| Compilation Speed | C^4 (qc) | ~1.85x faster (up to 47.3% lower latency) |
| Compiler Memory (Peak RSS) | C^4 (qc) | 31.6% less RAM (~60 MB leaner) |
| Compiler OS Page Faults | C^4 (qc) | 68.4% fewer page faults (~10,000 fewer faults/compile) |
Unoptimized Runtime (-O0) | C^4 (qc) | 2.0x faster (50.0% reduction in execution time) |
Optimized Runtime (-O1–-O3) | Tie | Exact Parity (Both clock identical 30.00 ms) |
| Binary Runtime Memory | C^4 (qc) | 44.7% less memory (2.11 MB vs 3.81 MB) |
| Opt Level | Compiler | Elapsed Time | User CPU Time | Sys CPU Time | CPU Util | Relative Speed |
|---|---|---|---|---|---|---|
-O0 | Clang C++ | 301.54 ms | 271.60 ms | 23.82 ms | 99.0% | 1.00x |
C^4 (qc) | 159.02 ms | 95.40 ms | 8.50 ms | 68.7% | 1.90x faster (-47.3%) | |
-O1 | Clang C++ | 313.14 ms | 282.86 ms | 23.64 ms | 99.0% | 1.00x |
C^4 (qc) | 168.74 ms | 48.28 ms | 11.22 ms | 39.0% | 1.86x faster (-46.1%) | |
-O2 | Clang C++ | 314.12 ms | 283.80 ms | 24.12 ms | 99.0% | 1.00x |
C^4 (qc) | 169.04 ms | 49.26 ms | 11.40 ms | 39.5% | 1.86x faster (-46.2%) | |
-O3 | Clang C++ | 315.72 ms | 284.98 ms | 23.88 ms | 99.0% | 1.00x |
C^4 (qc) | 171.54 ms | 55.04 ms | 11.78 ms | 42.6% | 1.84x faster (-45.7%) |
Note: At
-O3, C^4 uses 5.2x less User CPU time (55.04 ms vs 284.98 ms) by eliminating C++ preprocessor and template instantiation overhead.
| Opt Level | Compiler | Peak RSS (MB) | Minor Page Faults | Major Faults | I/O (Out) |
|---|---|---|---|---|---|
-O0 | Clang C++ | 177.84 MB (182,110 KB) | 14,777.7 | 0 | 40.0 |
C^4 (qc) | 129.70 MB (132,818 KB) | 5,080.1 | 0 | 416.0 | |
| Delta | -27.1% RAM | -65.6% faults | — | — | |
-O1 | Clang C++ | 177.97 MB (182,239 KB) | 14,802.2 | 0 | 40.0 |
C^4 (qc) | 128.79 MB (131,886 KB) | 4,625.0 | 0 | 72.0 | |
| Delta | -27.6% RAM | -68.8% faults | — | — | |
-O2 | Clang C++ | 177.97 MB (182,244 KB) | 14,835.1 | 0 | 40.0 |
C^4 (qc) | 128.75 MB (131,841 KB) | 4,627.8 | 0 | 72.0 | |
| Delta | -27.6% RAM | -68.8% faults | — | — | |
-O3 | Clang C++ | 188.09 MB (192,601 KB) | 14,635.2 | 0 | 40.0 |
C^4 (qc) | 128.73 MB (131,819 KB) | 4,620.6 | 0 | 72.0 | |
| Delta | -31.6% RAM (-59.4 MB) | -68.4% faults | — | — |
| Opt Level | Clang (C++) Binary | C^4 Binary | Speedup / Delta |
|---|---|---|---|
-O0 | 60.00 ms | 30.00 ms | 2.00x faster (+100% speedup) |
-O1 | 30.00 ms | 30.00 ms | Parity (1.00x) |
-O2 | 30.00 ms | 30.00 ms | Parity (1.00x) |
-O3 | 30.00 ms | 30.00 ms | Parity (1.00x) |
| Opt Level | Binary | Peak RSS (KB) | Peak RSS (MB) | Minor Faults |
|---|---|---|---|---|
-O0 | Clang C++ | 3,891.34 KB | 3.80 MB | 150.9 |
C^4 (qc) | 2,334.83 KB | 2.28 MB | 97.9 | |
| Delta | -39.9% memory | — | -35.1% faults | |
-O1 | Clang C++ | 3,892.55 KB | 3.80 MB | 150.7 |
C^4 (qc) | 2,156.53 KB | 2.11 MB | 90.6 | |
| Delta | -44.6% memory | — | -39.9% faults | |
-O2 | Clang C++ | 3,898.58 KB | 3.81 MB | 150.8 |
C^4 (qc) |
&& and || do not short-circuit on unions. Volatile does not exist on structs or class fields, and does not work on property accesses. In general, volatile is not 100%.
Vector
List
Array
AdvQBool
Math
Utils
OSInterop
Made by Luca Fazio
432 commits
C++
93.6%
C
6.1%