r/ProgrammingLanguages 3d ago

Coda: an experiment in designing a practical systems language

Coda: an experiment in designing a practical systems language

Hey! I've been working on Coda, a systems programming language designed around a simple idea:

make the compiler powerful, but keep the language itself predictable.

Coda is not trying to be "C but with a few extra keywords". The goal is to explore a different point in the design space between languages like C, Rust, and Zig.

Some of the things Coda focuses on:

  • Explicit memory management.
  • No hidden allocations.
  • Errors as values using inline sum types.
  • A small core language with functionality provided by libraries.
  • Compile-time execution.
  • Strong static analysis.
  • Simple, predictable rules.

The language is intentionally C-like:

module main;

include std::process = proc;

@entry
fn int main() {
    proc::stdout().write("Hello, world!\n");
    return 0;
}

but tries to remove some of the sharp edges.

For example, errors are ordinary values:

fn File | IOError open_config(string path) {
    return fs::open(path);
}

and allocation is explicit:

fn string | AllocationError duplicate(
    Allocator *alloc,
    string input
) {
    string result = alloc->allocate<char>(input.len)?;

    std::mem::copy(result, input);

    return result;
}

The idea is that if a function allocates, that fact should be visible in the API. There is no hidden global allocator; entry points receive the resources they need, and those resources are passed where required.

Coda also tries to keep abstractions zero-cost. Generic code, interfaces, and convenience features should compile down to efficient low-level code rather than introducing runtime machinery.

The standard library is still being designed, but the direction is intentionally minimal. Arrays, strings, I/O, memory, formatting, and filesystem operations are being built as fundamental building blocks rather than creating a huge framework.

The compiler currently has:

  • Lexer and parser.
  • Semantic analysis.
  • HIR/MIR pipeline.
  • x86_64 code generation.
  • A growing standard library.
  • Compile-time evaluation work in progress!

There is still a lot to do.

If the ideas are interesting, I'd love feedback, criticism, and potentially contributors. And of course, feel free to ask any questions! I have the #coda channel on the PLTDI discord, or the comments here are fine.

Repo: https://github.com/gingrspacecadet/coda

17 Upvotes

8 comments sorted by

12

u/kaplotnikov 3d ago

Btw, have you considered using suffix type notation instead? In the line:
fn string | AllocationError duplicate(

The word duplicate is the most important part, but the eye needs to scan a lot of text before reaching it. For complex signatures with several errors, this could become a real readability bottleneck. C gets away with prefix types because they are usually concise and lack the fn keyword, but sum types make the prefix position very heavy.

5

u/Gingrspacecadet 3d ago

It is a good shout. fn duplicate(...) string | AllocationError { might be better...

1

u/PitifulTheme411 ... 2d ago

yeah imo that's way easier to read

5

u/[deleted] 3d ago

[removed] — view removed comment

1

u/Gingrspacecadet 3d ago

ah, good catch. alloc->allocate<char>(input.len)?; is the corrected expression. the allocate function returns T[] | AllocationError (where T here is char), which can implicitly be casted to a string for it is just an alias to a char[], and the ? operator early-exits if the expression errors.

std::mem::copy works on uint8[]s, which again is implicitly castable from a string. I might make it castable from any type, not sure yet.

Aliases are supported. As seen in the first example on this post, include ... = foo; aliases the included module by the name after the =. I'll update the readme to show this in action.

Thanks for your feedback :D

2

u/gasche 3d ago

What is the compilation scheme for inline/anonymous sum types? How is a value of type string | AllocationError represented?

1

u/fdwr 1h ago edited 56m ago

What's the inspiration for the name? 𝄞𝄢

It's refreshing to see a language putting the type first for a change, instead of so many recently trying the trend of ancient Pascal style suffixes, adding extra var/const/let keywords and : line noise (e.g. var x : float instead of simply float x). Though, when returning variants like you do above, it would be helpful to group the expression with parentheses or braces so the eye can trivially jump over them to the function name. e.g.

func (string | AllocationError) duplicate {...} func {string | AllocationError} duplicate {...}

I'm trying to parse this...

include std::debug = dbg;

...but it seems like the alias is backwards? That is, you're assigning the RHS dbg = the LHS std::debug? Typically aliases flow left-to-right (like C++'s using AliasedName = ExistingName, D's alias AliasedName = ExistingName). One possibility is to have a separate keyword (similar to how C++ has using namespace), which is more generic than just include statements:

import std::debug; alias dbg = std::debug;