QuantumC (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;
}Check the web-demo out at learnhardcode.dpdns.org/QuantumC/qc.html. Or go to the docs right here.
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.
- Modern Syntax - Clean, intuitive, no boilerplate (other than main)
- Functions & Lambdas - First-class functions with
fnsyntax for lambdas andtype name(args-type args-name)syntax for normal functions - Multi-Return Values - Return multiple values without structs
- Type Safety - Strong typing with
autoinference - Quantum Booleans - Superposition of true/false
- F-Strings - Python-style string interpolation
- Special class methods - Representation, evaluation, and operator overloading
Go to the docs
Install the binaries from GitHub, use the Package & Version Manager, or:
You must have clang installed (or the path /usr/lib/clang//include/unwind.h must exist)
Also, if cloning a binary and not building from source, you must have the current latest libstdc++ (if on linux).
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- Roadmap
- Manual Memory Management
- Include System
- Why QuantumC?
- Code Conventions
- Type collections and aliases
- Systems-y stuff
- Generics and Iterators
- Misc.
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.1 = "Modifiers & Atomics" Next Version: x1.0.1 = "More Error Stuff"
Critical
└─ Added Compiler
Major
└─ N/A
Moderate
└─ Modifiers
Minor
└─ N/A
Patch
└─ Concepts were jank
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.
Found a bug? Have a feature request? Open an Issue!!
Want to contribute? PRs welcome!
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. Current benchmarks are being improved, and results should be considered preliminary.
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:
- Forced Cleanliness: QuantumC is designed to reward readable code. Language features should make the obvious solution the clean solution. Clean is not defined as 'Convenient for language', it means what it should be. Clean is not a "pythonic" equivalent; it is self-explanatory.
- Your Memory, Your Problem: QuantumC does not prevent dangerous code. It expects the programmer to understand the consequences. If you want, you can write a segfault handler with a segfault in it. QuantumC is strongly typed, but union types are designed to be ergonomic rather than restrictive. Unlike Rust or TypeScript, QuantumC does not force exhaustive narrowing before every union operation. I will give you a loaded shotgun. If you blow your leg off, don't blame the gunsmith.
- No Hiding: QuantumC is an explicit language, so your code does what it looks like it does. Nothing is hidden inside the parser while pretending to be stdlib, nothing is hidden away in some back catacomb. If it is an intrinsic, it says it is.
- No Excessive Syntax: No capture lists on lambdas, no templates, no infinite <>, no Rust "bird droppings", no ! and @ everywhere.
QuantumC uses a classic multi-pass compilation pipeline:
- Lexical Analysis / Preprocessing: Custom lexer converts text to tokens in one loop.
- AST Parsing: Recursive descent parser generating a strongly typed Abstract Syntax Tree.
- Type Checking & Semantic Analysis: Resolves user-defined types, namespaces, and TypeScript-style union types. The unique thing is that this pass is merged with the compilation/codegen phase
- Intermediate Representation (IR): Generates LLVM IR. Target-aware pointer arithmetic is achieved dynamically via target-specific DataLayout queries.
- Codegen: Emits native platform object files (
.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;
}
:externExtern 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);
:foreignThe 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
; ATTQuantumC has 4 special (non-const) storage modifiers.
volatileVolatile 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 andfinal, and before the return type.
volatile void infinite_time() {
while(true) {
}
return;
}
restrict
restrict 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
}outOut 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.
}inoutInout 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, ...);
:foreignQuantumC 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:
- Built-in rotations:
<<<and|>>andLROTandRROT, allowing 1 instruction rotations instead of 6+. -
- and - have lower priority than shifts.
3 << 3 + 2 == 26. This is because the shift expressionl << ris equivalent tol * 2 ^ r. If r was 2 + 2, that would bel * 2 ^ 2 + 2, which would be l * 4 + 2.
- and - have lower priority than shifts.
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 nextand 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<T> _map(fn(T elem) -> T) // preforms an operation on every element
MyClass<T> _collect() // returns the class that this iterator iteratesExample:
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{...};
}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.
Benchmarks are currently unreliable and show significant fluctuations between runs, or things like a 0-millisecond runtime in O1 but 30 in O3. Results should be treated as preliminary rather than definitive. A more robust benchmarking system with better workload scaling and measurement methodology is planned after version x1.0.0.
&& 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