created: 16.MAR.2025
I love to learn new programing languages, it help to open the mind to new possibilities and compare different approaches. For instance, I learned Ruby and Scala in 2010, Clojure and Haskell in 2011, Go in 2015, Kotlin 2016, Rust in 2018 and Idris, TypeScript in 2019, 2020 Pandemic strike did a bunch of pocs but not with new langs(crazy year), Zig in 2021, 2022(coding in lots of langs but nothing new) - in 2023 I'm learning Nim and V. Learn at least one lang per year. This post is not complain, it's just to share some toughts, notes and impressions.
- Created in 2021 by Christoffer Lernö
- Evolution not Revolution
- Based on C / Full C ABI Compatibility
- Generics
- Safe Arrays
- Zero Overhead Errors
- Slices
- Runtime and Compile Reflection
- LLVM backend for industrial strength optimisations.
- Fun
- Fast
- Nice C-Based Lang
- Easier than Zig and Rust
- More simple than Odin
- Syntax is very Go-like
- I feel C3 as a mix of Go and Zig.
My POCs with C3: https://github.com/diegopacheco/c3-playground
Like Go, Zig, Rust, Java, Python and many other languages we can easily do a foreach. Forearch can be with or wihout index.
import std::io;
fn int main(String[] args){
int[5] arr = {1, 2, 3, 4, 5};
foreach(int i : arr){
io::printfn("%d",i);
}
foreach (int idx, int* &v : arr){
io::printfn("index: %d value: %d",idx,*v);
}
return 0;
}C3 has structs, you can add functions to structs. Liek you would do in C++ or Zig.
struct Config{
int port;
}
fn void Config.dump(Config* this){
io::printfn("Port: %d",this.port);
}
fn int main(String[] args){
Config config = {8080};
config.dump();
return 0;
}It's like a try/cacth except that just catch errors and you can assign to variables. This is clean and useful, so you can do in a if-style like c, zig, go error handling similar styles.
int! result = goodLuck();
if (catch anyfault error = result){
io::printfn("Error: %s",error);
}It's also possible to do the opposite and we can do the If based on the try.
fn int main(String[] args){
int! x = maybe();
if (try int result = x){
io::printfn("%d",result);
}else{
io::printfn("No value");
}
return 0;
}
fn int! maybe(){
return 42;
}Besides the macro here, this is pretty cool. Being able to run reflections in compile time is great. Zig has something similar(IMHO zig is better).
struct Person{
long id;
int age ;
String name;
}
macro print_fields($Type){
$foreach ($field : $Type.membersof)
io::printfn("Field %s, offset: %s, size: %s, type: %s",
$field.nameof, $field.offsetof, $field.sizeof, $field.typeid.nameof);
$endforeach
}
fn void main(){
print_fields(Person);
}Like Java, Kotlin, Scala, Rust, Zig(with comptime) we can do generics in C3. Here is a generic stack implementation using generics in C3.
module stack(<Type>);
import std::io;
struct Stack{
usz capacity;
usz size;
Type* elems;
}
fn void Stack.push(Stack* this, Type element){
if (this.capacity == this.size){
this.capacity *= 2;
io::printfn("resizing");
this.elems = realloc(this.elems, Type.sizeof * this.capacity);
}
this.elems[this.size++] = element;
}
fn Type Stack.pop(Stack* this){
assert(this.size > 0);
return this.elems[--this.size];
}
fn bool Stack.empty(Stack* this){
return !this.size;
}Like Go, Zig and Rust, we can have slices in C3 lang.
fn int main(String[] args){
int[5] a = { 1, 20, 50, 100, 200 };
int[] b = a[0..4]; // The whole array as a slice.
printArray(b,5);
int[] c = a[0:5]; // The whole array as a slice.
printArray(c,5);
int[] d = a[2..3]; // 50, 100
printArray(d,2);
return 0;
}
fn void printArray(int[] a,int size) {
for (int i = 0; i < size; i++) {
io::printfn("%d",a[i]);
}
}C3 has support for contarcts, which is Design-By-Contracts(DBC) some languages hava frameworks/libs for DBC like Java.
fn int main(String[] args){
int[] arr = {1, 2, 3};
getLastElement(arr, 3);
testFoo(-1);
return 0;
}
/**
* @param foo "the number of foos"
* @require foo > 0, foo < 1000
* @return "number of foos x 10"
* @ensure return < 10000, return > 0
**/
fn int testFoo(int foo){
return foo * 10;
}
/**
* @param array "the array to test"
* @param length "length of the array"
* @require length > 0
**/
fn int getLastElement(int* array, int length){
return array[length - 1];
}- Rust: https://gist.github.com/diegopacheco/4b7dfeb781ad3455ae2a6b090d9deaa7
- Scala: https://gist.github.com/diegopacheco/1b5df4287dd1ce4276631fd630267311
- Zig: https://gist.github.com/diegopacheco/7d7c8110db68352d58a18b0e3e3c2bb0
- Kotlin: https://gist.github.com/diegopacheco/f6beabf1451cfe1ec2dc89a19a78fdc5
- Clojure: https://gist.github.com/diegopacheco/9453877378f007e8903a359f298a0afa
- Haskell: https://gist.github.com/diegopacheco/057087dc7ae236bdd0700014a31c88ef
- Nim Lang: https://gist.github.com/diegopacheco/0fb84d881e2423147d9cb6f8619bf473
- V Lang: https://gist.github.com/diegopacheco/3d0b176eb83e569da582a0770209e22f
- Gleam: https://gist.github.com/diegopacheco/2fdb5be0446ccb8f07d02105a46aab75
- Misc https://gist.github.com/diegopacheco/49329d726d0e2bd1c709ba1187a92c97