A systems language that
gets out of your way.

Memory safety, deterministic cleanup, and compile-time data race prevention. No garbage collector. No lifetime annotations. A mental model small enough to hold in your head.

What we believe

What Quack is for

Every design decision flows from these principles.

The language should disappear

If you're thinking about Quack instead of your problem, the language has failed. Every syntax, every rule, every diagnostic is evaluated against this.

Reject, don't tax

When the compiler can't prove safety, it rejects the code and explains why. It never silently inserts allocations. But it also doesn't demand ceremony that carries no information.

One obvious way

One ownership model. One error model. One concurrency model. Measured from the programmer's intent, not the compiler's internals. If a newcomer can't read it, redesign the syntax.

Diagnostics are part of the language

Every rule must produce a clear, actionable error message. If a rule can't explain itself simply, the rule is too complex and must be redesigned.

Make costs visible, not ceremony

If it costs something and you have a choice, it's visible in syntax. If it's free, or if there's no alternative, it's silent. Cognitive cost counts too.

What that means in practice

Capabilities, not lifetimes

Three words describe how every function uses its arguments: read for shared read-only access, mut for exclusive mutation, take for ownership transfer. The compiler checks the rest. You write zero lifetime annotations — not fewer, none.

Errors are return types

Config or IOError or ParseError — the return type is the error contract. return for success, error for failure. No wrappers, no exceptions, no hidden control flow.

Any function, any thread

Any function can run concurrently — you don't mark functions as special. No async/await, no splitting your codebase into two worlds. A work-stealing green thread scheduler handles the rest.

Words over symbols

and/or/not instead of &&/||/!. Square brackets for generics. Dot-paths for modules. Readable without prior Quack knowledge.

No null

Absence is T or None — a union type. The compiler forces narrowing before use. No null pointer dereferences, ever.

Native performance

Compiled to native code via LLVM. Memory is freed the moment ownership ends — no garbage collector, no pauses, no latency spikes. Platform-native async I/O under the hood.

What it looks like

Ownership without lifetime annotations

Rust needs lifetime parameters to track how long references live. Quack infers it.

Rust
fn first_word<'a>(s: &'a str) -> &'a str {
    match s.find(' ') {
        Some(i) => &s[..i],
        None => s,
    }
}
Quack
func first_word(s: String) -> read String {
    match s.find(' ') {
        is Int(i) -> return s.slice(0, i)
        is None -> return s
    }
}

Error handling without boilerplate

Go checks errors explicitly but the pattern is repetitive. Quack's return unions make errors part of the type, with otherwise for propagation.

Go
func loadConfig(path string) (Config, error) {
    text, err := os.ReadFile(path)
    if err != nil {
        return Config{}, err
    }
    var cfg Config
    err = toml.Unmarshal(text, &cfg)
    if err != nil {
        return Config{}, err
    }
    return cfg, nil
}
Quack
func load_config(path: Path)
    -> Config or IOError or ParseError
{
    let text = read_file(path)
        otherwise pass back
    let config = Toml.parse[Config](text)
        otherwise pass back
    return config
}

Concurrency without async/await

Rust marks functions async and splits your codebase in two. Go gives you goroutines but no compile-time data race prevention. Quack has green threads with structured concurrency — any function can run concurrently, and the compiler rules out data races at compile time.

Rust
async fn fetch_all(urls: Vec<String>)
    -> Result<Vec<Response>, Error>
{
    let futs = urls.into_iter()
        .map(|u| tokio::spawn(
            fetch(u)
        ));
    let results =
        join_all(futs).await;
    results.into_iter()
        .map(|r| r?
            .map_err(Error::from))
        .collect()
}
Go
func fetchAll(urls []string) (
    []Response, error,
) {
    var wg sync.WaitGroup
    var mu sync.Mutex
    results := make([]Response, 0)
    var firstErr error
    for _, u := range urls {
        wg.Add(1)
        go func(url string) {
            defer wg.Done()
            r, err := fetch(url)
            mu.Lock()
            defer mu.Unlock()
            if err != nil {
                firstErr = err
            } else {
                results = append(
                    results, r)
            }
        }(u)
    }
    wg.Wait()
    return results, firstErr
}
Quack
func fetch_all(urls: Vec[String])
    -> Vec[Response] or Error
{
    let results = Mutex.new(Vec[Response]())
    parallel {
        for url in urls {
            let r = fetch(url)
                otherwise pass back
            results.lock().push(r)
        }
    }
    return results.into_inner()
}

Quack narrows expressiveness to shrink the mental model. Some patterns are intentionally inexpressible. The tradeoff: you can hold the entire language in your head, and the compiler handles the verification.

No use-after-free No data races No null No GC No lifetime annotations No function colouring Deterministic cleanup

Where we are

The bootstrap compiler (written in Rust) compiles Quack programs to native binaries via LLVM. The core language works — ownership, capabilities, error handling, pattern matching, generics, interfaces. We're fixing edge cases in ownership codegen and building out the standard library.

The runtime will use platform-native I/O (io_uring on Linux, kqueue on macOS, IOCP on Windows) with a work-stealing green thread scheduler. The compiler, specification, and all documentation will be open-sourced when the repo goes public.

Get in touch