Kestrel

What if convenience
didn't cost you control?

scroll to explore
func findUser(id: Int64) -> Optional[User] {
    let user = match users.get(id).tryExtract() {
        .Continue(v) => v,
        .Break(r) => return fromResidual(r)
    };
    if user.age.compare(18) == .Less { return Optional.None };
    user
}

func greet(id: Int64) -> String {
    let user = findUser(id).coalesce(default: { User(name: "guest", age: 0) });
    var s = DefaultStringInterpolation(literalCapacity: 7, interpolationCount: 1);
    s.appendLiteral("Hello, ");
    user.name.format(into: s);
    s.appendLiteral("!");
    String(interpolation: s)
}

func main() {
    var it = Array(arrayLiteral: LiteralSlice("Alice", "Bob", "Carol")).iter();
    loop {
        match it.next() {
            .Some(name) => print(name),
            .None => break
        }
    }
}
func findUser(id: Int64) -> User? {
    let user = try users.get(id);
    if user.age < 18 { return null };
    user
}

func greet(id: Int64) -> String {
    let user = findUser(id) ?? User(name: "guest", age: 0);
    "Hello, \(user.name)!"
}

func main() {
    for name in ["Alice", "Bob", "Carol"] {
        print(name)
    }
}
The Idea

Sugar, not magic

This looks like any modern language. Clean syntax, readable defaults, nothing unusual.

But none of it is special-cased.

Every shortcut is a protocol you can see, touch, and replace.

T?
Optional[T]
try
Tryable
<
Comparable
null
NullLiteral
??
Coalesce
\(...)
Formattable
for...in
Iterable
[...]
ArrayLiteral
Extensible

Same machinery, your types

The standard library uses these protocols to implement Optional, Result, Array, and String. Conform your own types and they get the same syntax — because it's the same machinery.

IteratorImplement next() and your type works in any for loop
struct Countdown: Iterator {
    type Item = Int64
    var current: Int64;

    mutating func next() -> Int64? {
        if self.current <= 0 { return null };
        self.current -= 1;
        self.current + 1
    }
}

for n in Countdown(current: 5) {
    print(n)
}

What you can build, today

import perch.app.(App)
import perch.request.(Request)
import perch.response.(Response)
import perch.middleware.(Logger)
import http.content.(Text, JsonBody)

struct Ctx: Cloneable {
    func clone() -> Ctx { Ctx() }
}

var app = App(Ctx());
app.use(Logger());

app.route(get: "/hello/:name", { (req: Request, ctx: Ctx) in
    let name = req.param("name") ?? "world";
    Response.ok(Text("Hello, \(name)!"))
});

app.listen(8080);
output

Our vision

What we're building next

Help us shape it
Code ContractsBusiness Logic
struct Money {
    @ensures(value >= 0)
    var value: Float64;
}

struct Account {
    var balance: Money;

    mutating func withdraw(amount: Money) -> Money {
        self.balance -= amount.value;
        // error: self.balance must stay above 0
        return amount;
    }
}
Get Started

Take Flight

Install the toolchain and run your first Kestrel program

$ curl -fsSL https://kestrel-lang.com/install | sh

Installs Jessup, the Kestrel version manager, the latest stable toolchain, the VS Code extension, and the Claude Code / Codex plugin. View the script.