
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 believeWhat 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.
fn first_word<'a>(s: &'a str) -> &'a str {
match s.find(' ') {
Some(i) => &s[..i],
None => s,
}
}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.
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
}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.
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()
}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
}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.
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.