No garbage collector. No lifetime annotations.
Rask is a systems language that buys both by taking something away. You can borrow a value for a call or an expression, but you can’t keep the borrow: not in a struct field, not as a return value. Nothing outlives what it points at. Keeping track of that is the entire job of the 'a that spreads through a Rust signature, so Rask has no equivalent and no need for one.
Whether that’s pleasant to write is the open question. I think it is, which is a sample size of one.
or install it. It builds from source with cargo build --release and needs a Rust toolchain.
Why another language
There is a way of writing Rust that people call pragmatic. Keep lifetimes out of your structs. Use indices instead of references. Clone when it isn’t hot. Reach for Rc<RefCell<T>> when you have to.
It works. It is also a set of rules the compiler doesn’t know about, that every codebase rediscovers and half-follows. C++ has the same shape: RAII, smart pointers, the Core Guidelines. A whole discipline to get memory safety out of a language that won’t hand it to you, and no way to check that anyone kept it.
When the advice is “don’t use the feature”, the feature is the problem.
That is the bet Rask is making, and it could be wrong. If you want a language to ship something in this year, use Rust or Go.
The rule everything follows from
A reference can’t be stored.
So Rask doesn’t have it. You can borrow a value for a call or an expression. You cannot put the borrow in a struct field, and you cannot return it.
It is a real restriction: a struct that wants to point at something has to own it, or hold a Link into a Rack. What it buys is that nothing spreads. One &'a str in a Rust struct field puts 'a on that struct, then on every struct that holds it, on the traits it implements, and on every function that hands one back. Rask has no 'a to propagate, so none of that happens.
The other rules are consequences: values move, files get closed exactly once, graphs live in a rack.
struct Item {
name: string
price: i64
}
// `cart` is borrowed for the call. The caller still
// owns it afterwards, and no signature says so.
func total(cart: Vec<Item>) -> i64 {
mut sum: i64 = 0
for item in cart { sum += item.price }
return sum
}
func main() {
mut cart = Vec.new()
cart.push(Item(name: "coffee", price: 45))
cart.push(Item(name: "beans", price: 120))
println("{cart.len()} items, {total(cart)} kr")
}
What you get
A signature you read once and know what the call does to its arguments: whether it takes them, changes them, or only looks.
You get
- No lifetime annotations, anywhere
- Signatures you read in one pass
- Allocations visible at the call site
- Deterministic cleanup, no GC pauses
You give up
- References in struct fields
- Returning a reference from a function
- Implicit copies of anything large
- Pointer-chasing a graph you built by hand
Raw pointers are behind unsafe, same as in Rust, and they are there for talking to C. What neither language gives you is a hand-built pointer graph in safe code: in Rask that shape lives in a Rack and you pay an indirection to reach through a Link. C and C++ hand you the pointers with no ceremony at all, and if that is the trade you want, they are the honest answer.
Finding a race with sim mode
Two tasks take money out of one account at the same time and sometimes overdraw it. A normal test run catches this about once in eight runs. rask test --sim catches it within a few seeds and gives you one that reproduces it every time.
Where it actually stands
Runs today
- Native codegen via Cranelift, plus an interpreter that acts as the reference for what the answer should be
- Ownership, moves, borrows, linear resources
RackandLinkfor graphs, on both backends- Structs, enums, generics, traits,
comptime spawn/join, channels, thread pools- Packages, workspaces, watch mode, LSP
Not there yet
- No fibers.
spawnis an OS thread; the M:N scheduler is designed, not built - Panics abort instead of unwinding
- x86-64 only, and SIMD is a stub
Caveats
- Not memory-safe yet. The design is; this implementation isn’t. Codegen still gets memory release wrong in known cases, so a correct program can still corrupt or leak
- It’s a solo project, so fixes land in waves
- No compatibility promise: breaking changes land whenever a design turns out wrong
Should you learn it?
Not yet.
It’s a solo project, the ecosystem is what you see in this repository, and breaking changes land whenever a design turns out wrong.
Ideas and complaints are both welcome on GitHub issues, and the blog has the longer argument.
rask