R ust UN der E macs
This project is an experimental Emacs core written in Rust. The project is still at a very early phase but has the following goals:
- Bring multi-threaded elisp to Emacs
- Be “bug-compatible” with existing Emacs Lisp packages (everything should still work)
- Enable performance improvements (including faster GC, regex, and JSON) by leveraging the Rust Ecosystem.
See the design doc for more details.
The current goal of this project is to create an editor MVP. We have a basic elisp runtime, and we are working on adding basic editing functionality in a minimal GUI. This will include:
- buffer
- text insertion/deletion
- cursor
- line wrapping
- scrolling
- file IO
- display tables
If you want to contribute or have ideas for things to add, please open an issue.
Lisp files are currently pulled from
https://github.com/emacs-mirror/emacs/tree/emacs-29.1/lisp
Any modification for bootstrapping contain the tag RUNE-BOOTSTRAP
.
cargo run --release -- --repl
- Load the bootstrapped elisp and open the REPL
cargo run --release -- --no-bootstrap --repl
- Open the REPL with only the builtin functions loaded
cargo run --release
- Load the bootstrapped elisp and exit
Run the test suite with MIRI
MIRIFLAGS='-Zmiri-strict-provenance' cargo +nightly miri test
The project is defined by a main package rune
, which depends on the crates included in the crates
directory. One of those is the rune-macros
crate, which defines the defun
proc macro for defining builtin functions. The rest of the code is contained in src/
. The modules are described below.
- objects
- The basic objects used in the interpreter. These are modeled after Emacs objects using tagged pointers with inline fixnums. Conversion between different primitives and object types is also found here.
- reader
- The Emacs lisp reader that translates a string to a cons cell. Due to the simple nature of lisp syntax, the reader is hand rolled and does not rely on any parsing libraries.
- env
- The global obarray. Currently, function bindings are global and immutable and value bindings are thread-local and mutable. When the ability is added to share data between threads, this will enable new threads to safely run functions without the need to copy them.
- gc
- Contains the allocator and garbage collector. All code for rooting and managing objects lives here as well.
- bytecode
- The bytecode VM. This uses the same opcodes as Emacs and uses the bytecomp.el to compile.
- interpreter
- The basic elisp interpreter. This is used only to bootstrap the elisp byte-compiler.
- fns, data, alloc
- These modules contain definitions of builtin in functions. Some of these are just stubbed out until the functionality is actually needed.
See the architecture doc for more info on the structure of Rust Emacs internals.
This project is moved forward by trying to load new elisp files and seeing what breaks. The best way to do that is with cargo run
, which will load the currently bootstrapped files. The bootstrapped files are located in main.rs as part of the load
function.
Usually what is needed is to implement more primitive functions. This is done with the defun macro. For example, if we wanted to implement the substring
function, we would first look at the lisp signature.
(substring STRING &optional FROM TO)
Then we would translate the types to their Rust equivalent. If the correct type is not known we can use Object
. In this example we would write our Rust signature as follows:
#[defun]
fn substring(string: &str, from: Option<i64>, to: Option<i64>) -> String {...}
To load a new elisp file, run cargo run --release -- --load <file>
. Files that are not bootstrapped are not yet included in this repo, but are part of Emacs. Once the file is bootstrapped it can be added to the lisp directory.
Rune comes with a “elisp property tester” (elprop) located at elprop/elrop
. Run the utility with a regex matching the names of Rune functions to test against the Emacs implementation. The tool will generate random inputs and send them to both rune and Emacs and report if the outputs are ever different. If you implement a new function or modify one, run elprop on it to ensure it behaves like Emacs. Cases that it finds make good unit tests.
- tagged pointers in Rust
- My initial approach to creating tagged pointers in Rust. It serves as in intro to this project.
- implementing a safe garbage collector
- An overview of the garbage collector used in this project and how Rust enables safe GC abstractions.
- A vision of a multi-threaded Emacs :: Some ideas about how to add multi-threading to the existing language.
- Design of Emacs in Rust
- Some of the unique benefits that Rust could bring to Emacs.
- Remacs
- The original Rust and Emacs project. Remacs took the approach of enabling interop between Emacs C core and Rust, enabling them to replace parts of Emacs piecemeal. The project is currently unmaintained but is a big inspiration for Rune.
- emacs-ng
- The spiritual successor to remacs. This project integrates the Deno runtime into emacs, allowing you to write extensions in elisp or javascript. Which sounds cool if you happen to be a web developer. It really shows the power of integrating Emacs with a more modern ecosystem (which is part of the promise of Rust).
- helix
- A fast modern text editor written in Rust.
- crafting interpreters
- This was a big inspiration for this project, and it’s probably one of the best introductions to programming language implementations.