Introduction
Safety without the pain.
Rask is a systems programming language that sits between Rust and Go:
- Rust’s safety guarantees without lifetime annotations
- Go’s simplicity without garbage collection
Status: Design phase with working interpreter (no compiler yet)
Quick Look
func search_file(path: string, pattern: string) -> () or IoError {
const file = try fs.open(path)
ensure file.close()
for line in file.lines() {
if line.contains(pattern): println(line)
}
}
No lifetime annotations. No borrow checker fights. No GC pauses.
Core Ideas
- Value semantics - Everything is a value, no hidden sharing
- Single ownership - Deterministic cleanup, no GC
- Scoped borrowing - Temporary access that can’t escape
- Handles over pointers - Validated indices for graphs and cycles
- Linear resources - Files and sockets must be explicitly consumed
- No function coloring - I/O just works, no async/await split
Get Started
- Try in Playground - Run Rask code in your browser
- Installation
- First Program
- Language Guide
- Examples
Design Philosophy
Want to understand the “why” behind Rask’s design choices?
- Design Principles
- Formal Specifications
- Blog - Development updates and design discussions
Getting Started
Welcome to Rask! This section will help you get started with the language.
Note: Rask is in early development. Currently only the interpreter is available (no compiler yet).
What You’ll Learn
- Installation - Build Rask from source
- Your First Program - Hello world and basic syntax
Status
Rask is in the design phase with a working interpreter. Three of the five litmus test programs run:
- Grep clone ✓
- Game loop ✓
- Text editor ✓
- HTTP server (blocked on network I/O)
- Embedded sensor (blocked on SIMD)
Next Steps
After completing this section, continue to the Language Guide to learn core concepts.
Installation
Note: Rask is in early development. Currently only the interpreter is available.
Prerequisites
- Rust toolchain (for building from source)
- Git
Building from Source
git clone https://github.com/dritory/rask.git
cd rask/compiler
cargo build --release
The rask binary will be in compiler/target/release/rask.
Verify Installation
Run a test to verify the interpreter works:
./target/release/rask --version
Running Examples
The repository includes working examples:
./target/release/rask ../examples/hello_world.rk
Next Steps
Your First Program
Create a file called hello.rk:
func main() {
println("Hello, Rask!")
}
Run it:
rask hello.rk
Output:
Hello, Rask!
What’s Happening?
func main()is the program entry pointprintln()is a builtin for printing with newline
Variables
Let’s try variables:
func main() {
const name = "Rask"
const year = 2025
println(format("Hello from {} in {}!", name, year))
}
constcreates an immutable bindingletcreates a mutable binding (for values you’ll reassign)- Types are inferred, but you can write them explicitly:
const year: i64 = 2025
Functions
func greet(name: string) {
println(format("Hello, {}!", name))
}
func main() {
greet("World")
}
Functions that return values need explicit return:
func add(a: i32, b: i32) -> i32 {
return a + b
}
func main() {
const result = add(2, 3)
println(format("2 + 3 = {}", result))
}
Next: Explore the Guide
Language Guide
This guide covers the essential features of Rask.
Note: Rask is in active design. This guide covers what’s currently implemented in the interpreter. For detailed specifications, see the Formal Specifications.
Contents
- Basic Syntax - Variables, functions, control flow
- Ownership and Memory - Single ownership, moves, borrows
- Collections and Handles - Vec, Map, Pool
- Error Handling - Result types, try operator
- Concurrency - Tasks, threads, channels
Learning Path
- Start with Basic Syntax to understand the fundamentals
- Learn Ownership - this is the key insight that makes Rask work
- Explore Collections and Handles for working with data structures
- Master Error Handling for robust programs
- Discover Concurrency for parallel execution
Examples
Prefer learning by example? See the Examples section for complete working programs.
Basic Syntax
Placeholder: Minimal content for now. See examples and formal specs for details.
Variables
const x = 42 // Immutable binding
let y = 0 // Mutable binding
y = 5 // Reassignment
When to use:
const- binding won’t be reassigned (even if value is mutated via methods)let- binding will be reassigned (e.g.,let x = 0; x = 1)
Functions
func add(a: i32, b: i32) -> i32 {
return a + b // Explicit return required
}
Functions require explicit return for values (unlike Rust’s implicit returns).
Control Flow
if x > 0 {
println("positive")
} else {
println("zero or negative")
}
// Inline if (expression context)
const sign = if x > 0: "+" else: "-"
for i in 0..10 {
println(i)
}
match result {
Result.Ok(v) => println(v),
Result.Err(e) => println("Error: {}", e),
}
Types
const a: i32 = 42 // Signed integers: i8, i16, i32, i64
const b: u64 = 100 // Unsigned: u8, u16, u32, u64
const c: f64 = 3.14 // Floats: f32, f64
const d: bool = true // Boolean
const s: string = "hi" // String (lowercase!)
Next Steps
Ownership and Memory
Placeholder: Brief overview. For detailed specifications, see the ownership and borrowing specs.
Core Principles
Rask’s memory model is built on three principles:
- Single ownership - Every value has one owner
- Move semantics - Assigning/passing transfers ownership
- Scoped borrowing - Temporary access that can’t escape
Ownership Transfer
const s1 = string.new("hello")
const s2 = s1 // s1 moved to s2, s1 is now invalid
// println(s1) // Error: s1 has been moved
Borrowing
Borrowing gives temporary access without transferring ownership:
func print_len(s: string) {
println(s.len()) // Borrows s
}
const text = string.new("hello")
print_len(text) // text is borrowed
println(text) // text still valid here
The borrow lasts only for the function call - text remains valid after.
Copy vs Move
Small types (≤16 bytes) copy implicitly:
const x: i32 = 42
const y = x // Copy (i32 is small)
println(x) // x still valid
Large types move:
const v1 = Vec.new()
const v2 = v1 // Move (Vec is large)
// println(v1.len()) // Error: v1 has been moved
To keep access, explicitly clone:
const v1 = Vec.new()
const v2 = v1.clone() // Explicit copy
println(v1.len()) // Both valid
println(v2.len())
Why No Storable References?
Rask doesn’t allow storing references in structs or returning them. This eliminates lifetime annotations:
- ✗ No
'alifetime parameters - ✗ No borrow checker fights
- ✓ Simple ownership rules
- ✓ Predictable behavior
For graphs and cycles, use handles instead of references.
Next Steps
Collections and Handles
Placeholder: Brief overview. For detailed specifications, see the collections and pools specs.
Three Collection Types
Vec
const v = Vec.new()
try v.push(1)
try v.push(2)
const first = v[0] // Copy out (if T: Copy)
Map<K,V> - Key-value lookup:
const m = Map.new()
try m.insert("key", "value")
const val = m.get("key") // Returns Option<V>
Pool<T> - Handle-based storage for graphs:
const pool = Pool.new()
const h1 = try pool.insert(Node.new())
const h2 = try pool.insert(Node.new())
h1.next = h2 // Store handle, not reference
Why Handles?
References can’t be stored in Rask (no lifetime annotations). For graphs, cycles, and entity systems, use Pool\<T\> with handles:
struct Node {
value: i32,
next: Option<Handle<Node>>,
}
const pool = Pool.new()
const h1 = try pool.insert(Node { value: 1, next: None })
const h2 = try pool.insert(Node { value: 2, next: Some(h1) })
Handles are validated at runtime:
- Pool ID check (right pool?)
- Generation check (still valid?)
- Index bounds check
Iteration
for i in vec {
println(vec[i]) // Index iteration
}
for h in pool {
println(pool[h].value) // Handle iteration
}
for i in 0..10 {
println(i) // Range iteration
}
Next Steps
Error Handling
Placeholder: Brief overview. For detailed specifications, see the error types spec.
Result Types
Operations that can fail return Result<T, E>, or using shorthand: T or E
func parse_number(s: string) -> i64 or ParseError {
// Implementation
}
const result = parse_number("42")
match result {
Result.Ok(n) => println("Parsed: {}", n),
Result.Err(e) => println("Error: {}", e),
}
Error Propagation with try
The try operator extracts the success value or returns early with the error:
func process() -> () or Error {
const file = try fs.open("data.txt") // Returns Error if fails
const data = try file.read() // Returns Error if fails
process_data(data)
}
Without try, you’d need nested matches for every fallible operation.
Resource Cleanup with ensure
func read_file(path: string) -> string or IoError {
const file = try fs.open(path)
ensure file.close() // Runs at scope exit, even on error
const data = try file.read() // Can use try after ensure
return data
}
The ensure keyword guarantees cleanup runs even if try returns early.
Optional Values
Option<T> or T? for values that may be absent:
const m = Map.new()
try m.insert("key", 42)
const val = m.get("key") // Returns Option<i64>
if val is Some(v) {
println("Found: {}", v)
} else {
println("Not found")
}
// Or use the ?? operator for defaults:
const v = m.get("key") ?? 0
Next Steps
Concurrency
Placeholder: Brief overview. For detailed specifications, see the concurrency specs.
No Function Coloring
Functions are just functions - no async/await split:
func fetch_user(id: u64) -> User {
const response = try http_get(url) // Pauses task, not thread
return parse_user(response)
}
Spawning Tasks
func main() {
with multitasking {
const h = spawn { fetch_user(1) }
const user = try h.join()
println(user.name)
}
}
Three spawn constructs:
spawn { }- Green task (requireswith multitasking)spawn_thread { }- Thread from pool (requireswith threading)spawn_raw { }- Raw OS thread (no requirements)
Channels
with multitasking {
const chan = Channel.buffered(10)
spawn {
try chan.sender.send(42)
}.detach()
const val = try chan.receiver.recv()
println("Received: {}", val)
}
Channels transfer ownership - no shared mutable state between tasks.
Thread Pools
with threading(4) {
const results = Vec.new()
for i in 0..100 {
const h = spawn_thread { compute(i) }
try results.push(h)
}
for h in results {
const val = try h.join()
println(val)
}
}
Next Steps
Examples
Real Rask programs that demonstrate practical patterns.
All example code is in the repository’s examples/ folder and runs on the current interpreter.
Available Examples
- Grep Clone - File search with pattern matching
- Game Loop - Entity system with handles
- Text Editor - Undo/redo with resource management
Running Examples
git clone https://github.com/dritory/rask.git
cd rask/compiler
cargo build --release
./target/release/rask ../examples/grep_clone.rk --help
What These Demonstrate
Each example showcases key Rask concepts:
Grep Clone
- CLI argument parsing
- File I/O with error handling
- String operations
- Resource cleanup with
ensure
Game Loop
- Entity-component system using
Pool<T> - Handle-based indirection
- Game state management
Text Editor
- Command pattern for undo/redo
- File I/O and resource management
- State transitions
Grep Clone
A command-line tool for searching files with pattern matching.
Full source: grep_clone.rk
Key Concepts Demonstrated
- CLI argument parsing
- File I/O with error handling
- String operations (split, contains, trim)
- Resource cleanup with
ensure - Pattern matching with enums
Highlights
Resource Management
func search_file(path: string, pattern: string) -> () or IoError {
const file = try fs.open(path)
ensure file.close() // Guaranteed cleanup
for line in file.lines() {
if line.contains(pattern): println(line)
}
}
The ensure keyword guarantees file.close() runs even on early returns or errors.
Error Handling
enum GrepError {
NoPattern,
NoFiles,
FileError(string),
}
func parse_args(args: Vec<string>) -> Options or GrepError {
// Returns Result type, caller must handle errors
}
String Processing
for line in file.lines() {
if case_insensitive {
if line.to_lowercase().contains(pattern.to_lowercase()) {
println(line)
}
} else {
if line.contains(pattern) {
println(line)
}
}
}
Running It
rask grep_clone.rk "pattern" file1.txt file2.txt
rask grep_clone.rk -i "case-insensitive" *.txt
What You’ll Learn
- How to parse command-line arguments in Rask
- Error handling patterns with
Resulttypes - Resource management with
ensure - String manipulation and iteration
Game Loop
An entity-component system demonstrating handle-based indirection.
Full source: game_loop.rk
Key Concepts Demonstrated
- Entity-component system with
Pool<T> - Handle-based references (no pointers!)
- Game state management
- Frame-based update loop
Highlights
Entity Storage
struct Entity {
pos: Vec2,
vel: Vec2,
health: i32,
target: Option<Handle<Entity>>, // Handle, not reference!
}
const entities = Pool.new()
const player = try entities.insert(Entity.new())
const enemy = try entities.insert(Entity.new())
// Enemy targets player using handle
entities[enemy].target = Some(player)
Update Loop
func update(delta: f32) with entities: Pool<Entity> {
for h in entities {
entities[h].pos.x += entities[h].vel.x * delta
entities[h].pos.y += entities[h].vel.y * delta
// Handle AI, collision, etc.
}
}
Each entities[h] access is expression-scoped - the borrow ends at the semicolon. This allows mutation between accesses.
Why Handles Work
Unlike references, handles:
- Can be stored in structs
- Can form cycles (entity targets another)
- Are validated at runtime (pool ID + generation)
- Don’t need lifetime annotations
Running It
rask game_loop.rk
What You’ll Learn
- How to use
Pool<T>for entity systems - Handle-based indirection patterns
- Expression-scoped borrowing for collections
- Game loop structure in Rask
Text Editor
A text editor with undo/redo functionality.
Full source: text_editor.rk
Key Concepts Demonstrated
- Command pattern for undo/redo
- File I/O and resource management
- State transitions
- Vec usage for history
Highlights
Command Pattern
enum Command {
Insert(usize, string),
Delete(usize, usize),
Replace(usize, usize, string),
}
struct Editor {
content: string,
history: Vec<Command>,
position: usize,
}
Undo/Redo
func undo(editor: Editor) {
if editor.position > 0 {
editor.position -= 1
const cmd = editor.history[editor.position]
reverse_command(editor, cmd)
}
}
func redo(editor: Editor) {
if editor.position < editor.history.len() {
const cmd = editor.history[editor.position]
apply_command(editor, cmd)
editor.position += 1
}
}
File Operations
func save(editor: Editor, path: string) -> () or IoError {
const file = try fs.create(path)
ensure file.close()
try file.write(editor.content)
}
func load(path: string) -> Editor or IoError {
const file = try fs.open(path)
ensure file.close()
const content = try file.read_to_string()
return Editor { content, history: Vec.new(), position: 0 }
}
Running It
rask text_editor.rk
What You’ll Learn
- Command pattern for undo/redo
- Resource management with files
- State management in Rask
- Vec operations for history tracking
Reference
Detailed technical documentation for Rask.
For Users vs Implementers
This book provides high-level user-facing documentation. For formal specifications and implementation details, see:
- Formal Specifications - Detailed specs in
specs/folder
Coming Soon
Once the language stabilizes:
- Generated API documentation
- Standard library reference
- Compiler command-line reference
- Error code index
Current Status
Rask is in the design phase. The formal specifications are actively being developed and are the canonical source of truth for the language.
For now, refer to:
- Formal Specifications for detailed language semantics
- Language Guide for high-level concepts
- Examples for practical code
Formal Specifications
The formal language specifications are maintained in the repository’s specs/ directory. These are detailed technical documents for language implementers and those who want deep understanding.
Organization
Specs are organized by topic:
- Types - Type system, generics, traits
- Memory - Ownership, borrowing, resources
- Control - Loops, match, comptime
- Concurrency - Tasks, threads, channels
- Structure - Modules, packages, builds
- Stdlib - Standard library APIs
Quick Access
Key specifications:
| Topic | Link |
|---|---|
| Ownership | ownership.md |
| Borrowing | borrowing.md |
| Collections | collections.md |
| Pools | pools.md |
| Error Types | error-types.md |
| Concurrency | async.md |
For Users vs Implementers
- This Book - User-facing documentation (“How do I use Rask?”)
- Specs - Formal specifications (“How does Rask work internally?”)
Most Rask users won’t need the specs. If you’re:
- Building applications → This book is for you
- Building compilers/tools → Read the specs
- Curious about internals → Specs provide complete detail
See Also
- CORE_DESIGN.md - Design philosophy and rationale
- METRICS.md - How the design is validated
Playground
Try Rask directly in your browser with our interactive playground!
Features
The playground provides:
- ✨ Online editor with syntax highlighting
- ⚡ Instant execution - no setup required
- 🔗 Shareable code snippets via URL
- 📚 Example programs to explore
- 🎮 Quick experimentation without installation
How to Use
- Write code in the left editor pane
- Click “Run” or press
Ctrl+Enterto execute - View output in the right pane
- Load examples from the dropdown menu
- Share your code with the “Share” button
Limitations
The browser-based playground has some limitations compared to local execution:
- ❌ No file I/O -
fsmodule is disabled - ❌ No networking -
netmodule is disabled - ❌ No stdin - interactive input not supported
- ✅ Most features work - math, collections, json, pattern matching, etc.
Try These Examples
Click the examples dropdown in the playground to try:
- Hello World - Basic println and output
- Collections - Working with Vec, structs, and pattern matching
- Pattern Matching - Demonstrating match expressions
- Math Demo - Mathematical operations and calculations
Local Development
For full language features including file I/O and networking:
- Install Rask locally
- Run the examples
- Build real applications
Technical Details
The playground compiles the Rask interpreter to WebAssembly using wasm-pack. Code executes entirely in your browser with no server-side processing.
WASM bundle size: ~200KB gzipped Supported browsers: Chrome, Firefox, Safari (latest versions)
Source Code
The playground is open source:
Feedback
Found a bug or have a suggestion? Open an issue on GitHub!
Contributing
Rask is in active design and development. Contributions welcome!
How to Help
- Try the interpreter - Run examples, report bugs
- Review specs - Provide feedback on language design
- Implement features - Check TODO.md for open tasks
- Documentation - Improve this book
Getting Started
- Read the Design Process to understand Rask’s philosophy
- Check out the formal specifications
- Explore the CORE_DESIGN.md document
- Look at TODO.md for what needs work
Repository
Ways to Contribute
Bug Reports
Found a bug? Open an issue.
Include:
- Code that demonstrates the bug
- Expected behavior
- Actual behavior
- Interpreter output
Feature Suggestions
Have an idea? Open an issue for discussion.
Consider:
- Does it align with core principles?
- What’s the tradeoff?
- How does it affect the litmus tests?
Code Contributions
- Fork the repository
- Create a feature branch
- Make your changes
- Run tests:
cd compiler && cargo test - Submit a pull request
Documentation
Improvements to this book are welcome:
- Fix typos and clarity issues
- Add examples
- Improve explanations
- Expand placeholder sections
Community Guidelines
- Be respectful and constructive
- Focus on technical merit
- Consider tradeoffs and design constraints
- Test your changes
Questions?
Open a discussion or issue on GitHub.
Design Process
Rask’s design is guided by clear principles and measured against specific metrics.
Design Principles
- Safety Without Annotation - Memory safety without lifetime markers
- Value Semantics - No hidden sharing or aliasing
- No Storable References - References can’t escape scope
- Transparent Costs - Major costs visible in code
- Local Analysis Only - No whole-program inference
- Resource Types - I/O handles must be consumed
- Compiler Knowledge is Visible - IDE shows inferred information
Full details: CORE_DESIGN.md
Validation
Rask is validated against test programs that must work naturally:
- HTTP JSON API server
- grep clone ✓ (implemented)
- Text editor with undo ✓ (implemented)
- Game loop with entities ✓ (implemented)
- Embedded sensor processor
Litmus test: If Rask is longer/noisier than Go for core loops, fix the design.
Metrics
Design decisions are evaluated using concrete metrics:
- Clone overhead (% of lines with .clone())
- Handle access cost (nanoseconds)
- Compile times (seconds per 1000 LOC)
- Binary size
- Memory usage
See METRICS.md for the scoring methodology.
Specs and RFCs
Language features are documented as formal specifications in specs/.
Major changes follow an RFC process:
- Open an issue for discussion
- Draft a specification
- Implement in interpreter
- Validate against litmus tests
- Update metrics
- Merge if it improves the design
Tradeoffs
Every design has tradeoffs. Rask makes these intentional choices:
- More
.clone()calls - Better than lifetime annotations (our view) - Handle overhead - Better than raw pointers with manual tracking
- No storable references - Simpler mental model, requires restructuring some patterns
- Explicit costs - Better than hidden complexity
See CORE_DESIGN.md § Tradeoffs for full discussion.
Contributing to Design
When proposing changes:
- Explain the problem - What use case is difficult today?
- Show the tradeoff - What does this cost?
- Test against litmus tests - Does it make real programs better or worse?
- Measure the impact - Update relevant metrics
- Consider alternatives - What other approaches exist?
The goal is ergonomics without hidden costs. If a feature hides complexity or breaks transparency, it probably doesn’t belong.
Philosophy
“Safety is a property, not an experience.”
Users shouldn’t think about memory safety—they should just write code. The type system and scope rules make unsafe operations impossible by construction.
“If Rask needs 3+ lines where Go needs 1, question the design.”
Ceremony should be minimal. Explicit costs are good; boilerplate is bad.
“Local analysis only.”
Compilation should scale linearly. No whole-program inference, no escape analysis. Function signatures tell the whole story.
Learn More
- CORE_DESIGN.md - Complete design rationale
- METRICS.md - How we measure success
- TODO.md - What’s being worked on
- Formal Specifications - Detailed technical specs