What if references can't be stored?

A research language exploring memory safety
without lifetime annotations or garbage collection.

The idea

Most of Rust's complexity comes from allowing references to be stored in structs and returned from functions. Tracking those references requires lifetime annotations that spread through your codebase.

Rask's experiment: What if references are always temporary — they exist only within a block or expression? When you need longer-lived relationships, use handles (opaque identifiers) instead.

The cost

Explicit indirection for graphs. Some .clone() calls for shared data.

The benefit

No lifetime annotations. No borrow checker fights. Simple mental model.

How it works

Block-scoped borrowing

References can't be stored or returned. They're temporary views, valid only within their block. This eliminates lifetime tracking.

borrowing.rk
// References are temporary
func process(data: Data) {
    const name = data.user.name  // temporary borrow
    // name is gone at end of block
}

Handles for relationships

For graphs and long-lived relationships, use Pool<T> + Handle<T>. Handles are checked at runtime—no lifetimes needed.

handles.rk
// Graphs without lifetimes
struct Entity {
    target: Handle<Entity>?
}
const entities = Pool<Entity>.new()
entities[enemy].target = player_handle

Resource types

Files and system resources must be explicitly consumed. The compiler enforces cleanup on all exit paths.

resources.rk
func backup(src: string, dst: string) -> () or Error {
    const file = try fs.open(src)
    ensure file.close()  // runs on all exits
    try write(file)
}

No function coloring

I/O operations pause green tasks automatically. No async/await splitting your ecosystem.

concurrency.rk
func fetch_user(id: u64) -> User or Error {
    const resp = try http_get(url)  // pauses task
    return parse(resp)
}

The tradeoffs

Here's what you give up and what you get:

You give up You get
Storable references No lifetime annotations
Implicit string sharing Explicit .clone() when needed
Direct pointer traversal Handles with runtime checks

For pointer-level optimization, use Rust or C++. I'm betting that most application code can live with these tradeoffs — but that's still being tested.

What I'm going for

I'm exploring whether no-storable-references can cover the 80% of systems code that's really application code — servers, tools, games — with less friction than Rust. The language draws from Rust's ownership, Zig's comptime, Erlang's supervision, and Vale's generational references. Whether it all holds together is still an open question.

Where we are

Rask is in early development (pre-0.1). There's a working compiler (Cranelift backend) — rask run compiles and executes, rask check type-checks. Expect breaking changes, bugs, and design pivots.

This is a hobby project, but feedback and ideas are welcome on GitHub issues.