Coming from Rust

What's the same, what's different, and what you can stop worrying about.

What's Similar

If you know Rust, you already get the philosophy. Loon is built on the same core beliefs: ownership is better than garbage collection, types should be algebraic, null is a mistake, and the compiler should catch as much as possible. Specifically, these ideas carry over directly:

  • Ownership and move semantics: values have one owner, moves are the default.
  • Pattern matching: exhaustive, first-class, with destructuring.
  • Algebraic data types: enums with data, used instead of class hierarchies.
  • No null: Option replaces null, Result replaces exceptions.
  • Immutable by default: mutation is explicit and controlled.
  • Zero-cost abstractions: high-level code compiles to efficient output.

If these ideas are already second nature to you, Loon will feel familiar at the conceptual level. The differences are mostly about what the compiler handles for you versus what you have to spell out.

What's Different

No lifetime annotations

This is the big one. Loon's compiler infers all borrows and lifetimes automatically. You never write 'a, &, or &mut. Consider the classic "longest string" example:

; Rust:
; fn longest<'a>(x: &'a str, y: &'a str) -> &'a str

; Loon:
[fn longest [x y]
  [if [> [len x] [len y]] x y]]

The compiler figures out the borrowing. You just write the logic. This is not a simplification that sacrifices safety; the same ownership rules apply, they are just inferred rather than annotated.

Bracket syntax

Loon uses Lisp-style bracket syntax instead of C-style curly braces. Every call is [function arg1 arg2]. There are no operators with special syntax, no precedence rules to remember, and no ambiguity about what is a function call versus what is a keyword.

This is a genuine tradeoff. Bracket syntax is more uniform and much easier to parse (both for compilers and for macros), but less familiar visually. Most Rust programmers adjust within a day or two.

Hindley-Milner inference

Rust has local type inference, meaning it can figure out types within a function but requires annotations at function boundaries. Loon uses global Hindley-Milner inference. You never annotate a function signature unless you want to for documentation purposes. The compiler infers everything, including generic type parameters.

Effects instead of traits for IO

Where Rust uses traits like Read, Write, and Future, Loon uses algebraic effects. Effects serve a similar purpose (abstracting over behavior) but with built-in support for resumption and handler composition. Think of them as a more structured version of dependency injection that the type system understands.

Syntax Mapping

Rust
Loon
let x = 42;
[let x 42]
let mut v = vec![1,2,3];
[let mut v #[1 2 3]]
fn add(a: i32, b: i32) -> i32 { a + b }
[fn add [a b] [+ a b]]
if x > 0 { "pos" } else { "neg" }
[if [> x 0] "pos" "neg"]
match opt { Some(x) x, None 0 }
[match opt [Some x] x None 0]
v.iter().map(|x| x * 2).collect()
[map [fn [x] [* x 2]] v]
x.foo().bar().baz()
[pipe x [foo] [bar] [baz]]
println!("hello {}", name);
[println [str "hello " name]]
struct / enum
type / ADT
impl Trait for Type
[effect Name ...]

What You Can Stop Worrying About

If you have spent time fighting the Rust compiler, this section will feel like a weight off your shoulders. These are the things Loon handles for you.

Lifetimes

No 'a, no 'static, no "does not live long enough" errors with cryptic suggestions about adding lifetime bounds. The compiler infers all of this. You will never see a lifetime annotation in Loon code because the syntax for them does not exist.

Trait bounds

No where T: Clone + Send + Sync + 'static cascades. Loon's type inference and effect system replace the need for most trait bounds. When you write a generic function, the compiler figures out what capabilities the type parameter needs.

Turbofish

No :: syntax anywhere. The compiler always infers type parameters from context. If you have ever debugged a turbofish issue, you will appreciate this.

The borrow checker fight

Loon's ownership model is designed so that the compiler resolves borrowing questions silently. You will not find yourself restructuring perfectly good code just to make the borrow checker happy. If the code is logically correct, it should compile.

Async coloring

No async/.await coloring problem. Concurrency is an effect, handled at the boundary. Functions do not need to declare themselves async, and you do not need separate sync and async versions of your APIs.

New Things to Learn

Loon is not just "Rust with less syntax." There are a few genuinely new concepts that are worth spending time with.

Effects

Algebraic effects replace traits for IO, error handling, and state management. The mental model is: declare what operations you need, perform them wherever you want, and let a handler at the boundary decide the actual implementation. This is powerful for testing (swap the handler, not the code) and for composition (effects compose without the "monad transformer" headaches you might know from Haskell).

If you think of effects as "dependency injection that the type system tracks," you will have the right intuition.

Bracket syntax

Every call is [f x y]. There is no operator precedence, so [+ 1 [* 2 3]] is completely unambiguous. This feels weird for about a day, then it becomes invisible, and eventually you start to appreciate that there is only one syntax for "call this thing with these arguments."

Pipe

Where Rust uses method chains, Loon uses pipe. The result of each step is passed as the last argument to the next function. It is surprisingly natural once you get used to it, and it works with any function, not just methods on a type.

[pipe data
  [filter even?]
  [map [fn [x] [* x 2]]]
  [take 10]]