Inference first
Types, borrows, lifetimes, and error sets are inferred globally when the source stays silent. Annotations become hard constraints when you add them.
Crisp v1.8.0 · public release
.crp source; crisp infers types, ownership, and errorsrustc stays the soundness boundaryInstall now → Start the tutorial →
cargo install crisp-lang --locked crisp run .
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)))
}
Write less ceremony than Rust when inference is enough; annotate when APIs need precision. Emit stays auditable Rust.
Types, borrows, lifetimes, and error sets are inferred globally when the source stays silent. Annotations become hard constraints when you add them.
Crisp is a front end that produces Rust. Memory safety and data races remain rustc’s job — not a parallel soundness story.
reveal reconstructs signatures, ownership modes, lifetime overlays, and the error slice a function can produce.
Publish a frozen pub API in crisp.lock. Downstream code analyzes against the lockfile, not re-inferred internals.
Popular Rust domains — with less surface noise when Crisp’s inference fits the problem.
Compact snippets from the repo examples — more in the tutorial.
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"
}
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()}")
}
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)}")
}
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
}
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
}
-- 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))
}
parse_port(s) ! = {
if s == "" then throw "empty"
-- …
}
pub main() = {
p := parse_port("8080") catch { e => 0 }
log("port={p}")
}
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
}
}
pub main() = async {
sleep_ms(1)
print("async-ok")
}
Source → CIR → Rust → native. The emitted crate is an ordinary Cargo project under target/rust/.
.crp → resolve → typeck → ownership → CIR → Rust → rustc → native