Introduction

Lune is a language that transpiles to TypeScript. It uses a Rust-like syntax but uses TypeScript's type system. All the joy of Rust with all of the pain of garbage collection.

Why?

In summary: a garbage collected Rust-like language is a good idea to try out, so we're trying that out here.

I got tired of having no convenient way to write match expressions or similar in TypeScript. I got tired of having to null check everywhere. I got tired of null overall. Tired of the footgun that is ?. and how junior developers end up using it like a shotgun that comes back to bite me with undefined showing up everywhere in a UI. We need a better option.

ECMAScript (usually known as JavaScript) is, whether we like it or not, the primary language of the web for the foreseeable future. Once one is used to using a more modern language designed to be kind to the developer, it is hard to settle for an ecosystem and language that was not designed for the needs of development in the 2020s.

It is an explicit goal of this project to be compatible with TypeScript libraries through the creative (mis)use of extern blocks to declare wrappers around TypeScript libraries. Maybe an enterprising and clever developer will create a generator for .d.ts files that resolves to an extern. (Do you want to do this? Here's the tracking issue.)

Why not WebAssembly?

Making websites with WebAssembly is still quite a pain at the moment. If you want to do that, you can just use actual Rust for that and target the wasm targets.

Therefore, this project seeks to, at least initially, transpile a Rust-like syntax and related functionality to TypeScript.

Why not ReasonML/ReScript/Elm/xyz?

Sure, if you like those, use them. I don't like those. And trust me, I've tried to like them all. :)

Differences from Rust

Lune:

  • uses the file extension .lu for source files
  • is garbage collected, therefore no reference types or pointers
  • supports format strings:
let x = "format";
f"This is a {x} string!";
  • No Vec type, just Array<T>, and can be declared in a literal:
let array = [1, 2, 3, 4];
let mut other_array = [];
other_array.push("potato");
  • No variations of maps or sets, just Map<K, V> and Set<V>, and can be declared literally similarly to in Python:
let map = { "a": 1, "b": 2 };
let set = { 1, 2, 42 };
  • has immediate support for generators (functions that return an iterator and can use yield), through the addition of a soft keyword iter:
iter fn count_to_x(x: u32) -> Option<u32> {
    let mut count = 0;

    while count < x {
        yield Some(count);
        count += 1;
    }

    None
}
  • has immediate support for asynchronous streams via iter keyword:
async iter fn example() -> Option<u32> {
    for x in some_other_stream.recv().await {
        yield Some(x);
    }
    None
}
  • supports anonymous enums to maintain compatibility with TypeScript's sum types:
fn example(input: u32 | String) -> bool | () {
    match input {
        u32 => input > 100,
        String => if input.contains("a") {
            true
        } else {
            ()
        }
    }
}
  • Supports JSX syntax (when file extension is .lux), which is incredibly helpful when using React:
use react::{default as React, use_state, ReactElement};

pub fn App() -> ReactElement {
    let [count, set_count] = use_state(0);

    let element = <>
        <div class_name="App">
            <div class_name="card">
                <button on_click={|| set_count(|count| count + 1)}>
                    count is {count}
                </button>
            </div>
        </div>
    </>;

    element
}
  • has UTF-16 strings. I hate it too, but the String type has all the functions you know and love from Rust, hiding the pain of having to think about what the internal representation of a string actually is.
  • bindings around JavaScript types where feasible
  • extensions to extern blocks to allow declaring an interface to TypeScript code
  • no unsafe. There are no pointers, so there is no unsafety in the Rust definition of the word.
  • Generates human-readable TypeScript that can be consumed by other TypeScript libraries -- it is a specific goal that two-way compatibility exists.
  • No Drop, instead has a Finalize trait. Deref still exists however.
  • Runtime reflection. All types and traits are known at runtime. This means derives are implemented as plain functions in a trait!
  • No macros for now.
  • Traits support async functions.
  • Type system uses TypeScript's type system for now. Maybe we will implement Rust's type system in totality before 1.0, let's see how it goes.
  • Support for weak references at the language level, with the Weak<T> type.
  • Regex is built into the standard library
  • No distinction between str and String -- there is only String, and can be declared literally without .to_string() or other things.
  • Varargs are supported using the spread operator (...). Use sparingly or I'll take it from you.

Differences from TypeScript

  • Futures are lazy like in Rust, so when transpiled to a Promise they do not run eagerly. You must await them.
  • Integers are supported properly
  • Byte strings are supported
  • No support for heterogenous arrays or similar containers. Use the Any type if this is necessary
  • No hijacking the import system to implement things like .css injection -- this is sorted out properly as part of the bundler instead.
  • Lune comes with a runtime -- a chunk of code injected into the project to enable behaviour at runtime, such as trait registration, finalize registration, object construction and symbols necessary for injecting Lune-specific data into objects
  • Maps are preferred over objects, and there is no way to construct an anonymous object in Lune, only via a struct
  • Most language items are expressions like in Rust, which means there's no ternaries or similar since if expressions can return values
  • No more surprise nulls popping up in awkward places
  • Nicer standard library for common tasks, like getting first or last item in an array
  • Traits. There are no interfaces on this planet, but escape hatches exist for interoperability with TypeScript when required using attributes.
  • Variable shadowing is explicitly supported and encouraged.
  • Operators can be implemented on any Lune type
  • No function overloading

Current status

The lexer can handle 100% of the Rust language with some extensions for Lune, and without the parts not used like unsafe.

Parser is coming along, with the AST partially written. Standard library is partially written. Lune TypeScript runtime library is partially written. TypeScript output generator is partially written.

Roadmap

  • 0.1: First 'usable' release. "lunedoc" generated by the lune CLI using lune with React, a rough starting point at the std library for the web.
  • 0.2: A usable bundler workflow for making real web applications
  • 0.3: Language server support and syntax highlighting
  • 0.4: Integrated code formatting
  • 0.5: Integrated package management