Public release v1.8.0 · Spec v0.2.0-draft · Star on GitHub
Crisp

Crisp v1.8.0 · public release

Explicit on demand, implicit by default

  • Compact .crp source; crisp infers types, ownership, and errors
  • Lowers to ordinary Rust — rustc stays the soundness boundary
  • Whole-program inference inside sealed crates
  • Built for systems work with a Rust-hosted bootstrap compiler

Install now → Start the tutorial →

cargo install crisp-lang --locked
crisp run .
Hello worldexamples/hello
shape Named = {
    name: str
}

type Guest = {
    name: str = "world"
}

id(x: T) = x

greet(who: Named) = "hello {who.name}"

pub main() = {
    world := Guest {}
    print(id(greet(world)))
}
Output "hello world"

Why Crisp?

Write less ceremony than Rust when inference is enough; annotate when APIs need precision. Emit stays auditable Rust.

Inference first

Types, borrows, lifetimes, and error sets are inferred globally when the source stays silent. Annotations become hard constraints when you add them.

Rust as the back end

Crisp is a front end that produces Rust. Memory safety and data races remain rustc’s job — not a parallel soundness story.

Reveal what was inferred

reveal reconstructs signatures, ownership modes, lifetime overlays, and the error slice a function can produce.

Sealed crates

Publish a frozen pub API in crisp.lock. Downstream code analyzes against the lockfile, not re-inferred internals.

Use Crisp for

Popular Rust domains — with less surface noise when Crisp’s inference fits the problem.

Browse use cases → Learning paths →

Language features

Compact snippets from the repo examples — more in the tutorial.

Enums + matchexamples/enums
type Color =
    | Red
    | Green
    | Custom(int, int, int)

describe(color) = match color {
    Color.Red -> "red"
    Color.Custom(r, g, b) -> "rgb({r},{g},{b})"
    _ -> "other"
}
Inherent methodsexamples/vec2_methods
type Vec2 = {
    x: float
    y: float
}

impl Vec2 = {
    pub new(x: float, y: float) = Vec2 {
        x: x
        y: y
    }
    pub magnitude(self) =
        (self.x ** 2.0 + self.y ** 2.0) ** 0.5
}

pub main() = {
    v := Vec2.new(3.0, 4.0)
    log("mag={v.magnitude()}")
}
Traitsexamples/show_trait
trait Show = { show(self) -> str }

type Point = {
    x: int
    y: int
}

impl Show for Point = {
    show(self) = "({self.x},{self.y})"
}

label(x: T) = x.show()

pub main() = {
    p := Point { x: 3, y: 4 }
    log("p={p.show()} l={label(p)}")
}
Shapesexamples/shapes
shape HasPosition = {
    x: float
    y: float
}

distance(a: HasPosition, b: HasPosition) -> float = {
    dx := a.x - b.x
    dy := a.y - b.y
    dx * dx + dy * dy
}
Genericsexamples/generics_implicit
type Pair = { left: A, right: B }
id(x: T) = x
first(p: Pair<A, B>) = p.left

shape HasPosition = { x: T, y: T }
distance(a: HasPosition<T>, b: HasPosition<T>) = {
    dx := a.x - b.x
    dy := a.y - b.y
    dx * dx + dy * dy
}
Rust crate importexamples/rust_import
-- crisp.toml: serde_json = { version = "1", rust = true }
use serde_json { from_str, to_string }

pub main() = {
    v := from_str("[1, true, \"crisp\"]")
    print(to_string(v))
}
Fallible + catchexamples/fallible
parse_port(s) ! = {
    if s == "" then throw "empty"
    -- …
}

pub main() = {
    p := parse_port("8080") catch { e => 0 }
    log("port={p}")
}
Loopsexamples/loops
sum_to(n) = {
    total mut:= 0
    i mut:= 0
    while i < n {
        i = i + 1
        total = total + i
    }
    total
}

countdown_stop(start, stop) = {
    n mut:= start
    loop {
        if n == stop then break n
        if n <= 0 then break 0
        n = n - 1
    }
}
Asyncexamples/async_hello
pub main() = async {
    sleep_ms(1)
    print("async-ok")
}

Full tutorial → Language summary →

Pipeline

Source → CIR → Rust → native. The emitted crate is an ordinary Cargo project under target/rust/.

.crp → resolve → typeck → ownership → CIR → Rust → rustc → native

Install and run hello →