00:00.000 Rune
00:17.082 The Emacs core
00:57.168 Why create this?
01:55.865 How does this compare to other projects?
03:01.315 Multi-threading
03:32.441 Multi-threading elisp
03:47.648 No-GIL method
04:32.638 Actors
04:51.252 Multi-threading elisp (functions)
05:34.680 Caveats
05:57.090 Multi-threading elisp (data)
06:38.249 Copy values to other threads on demands
06:57.884 Multi-threading elisp (buffers)
08:11.903 Would this actually be useful?
08:46.919 Precise garbage collection
09:16.537 How Emacs used to deal with roots
10:38.713 Conservative stack scanning
11:00.157 Movable objects
12:38.829 How Rust makes precise GC easy
14:13.227 Other Rust niceties: proc macro
15:14.560 sum types
16:01.041 Regex
16:16.052 Parsers
16:27.210 Other changes: GUI first, terminal second
16:58.919 Off-screen cursor
17:16.305 Image flow
17:24.440 Testing
18:36.345 Status
19:07.247 Next directions
19:22.739 How to get involved
00:08.500 Q: Have you considered using CRDTs to share buffers between threads and merge any concurrent edits automatically?
01:05.874 Q: Why hosted on GitHub? GitHub is nonfree. Is it possible to report bugs/send patches without using GitHub?
01:22.960 Q: Do you think it's possible to achieve 100% compatibility with current Emacs code?
02:11.913 Q: so you're re-implementing elisp in rust? have you considered using a more modern lisp, such as scheme? [11:03]
04:01.400 Q: Do you have specific features from the Rust compiler that are missing (or are nightly-only) that you would take advantage of?
05:26.880 Q: Can remacs be reused?
07:23.600 Q: What are you thoughts on the GUI layer. Any plans on how to reimplement it?
08:21.240 Q: If money could fix the problem, how much would it cost to ship this with feature parity before 2026?
09:28.350 GObject implementation
09:56.600 Q: elisp is implemented in c, so if you're not implementing elisp in rust, are you using/keeping the c implementation of elisp?
10:42.680 Clarifying rewriting Elisp in Rust
12:57.908 Q: Will your Rust implementation also be able to run Emacs bytecode? Or are you implementing it at the Lisp level?
14:20.100 Q: Is it possible to bootstrap with just the bytecode interpreter?
17:03.960 What would it take to bootstrap Guile in Rune?
An overview and discussion and early prototype of a new Emacs core written in Rust. The talk covers some of the interesting design choices in the GNU Emacs C core, as well as some of the trade-offs made in the Rust core. https://github.com/CeleritasCelery/rune
What is the Emacs core?
How has the core evolved?
Design trade-offs
multi-threading
Precise GC
Being bug compatible with GNU Emacs
Comparison
About the speaker:
Hardware Engineer with interest in low-level programming and the hardware-software boundary.
Discussion
Questions and answers
Q: Have you considered using CRDTs to share buffers between threads
and merge any concurrent edits automatically?
A: While mathematically correct, CRDT does not always produce a
useful answer. With different packages, this will the issue: not
a useful result. https://www.moment.dev/blog/lies-i-was-told-pt-1
This question answer about CRDT's is by itself showing a lot of problem awareness
Q: Why hosted on GitHub? GitHub is nonfree. Is it possible to report
bugs/send patches without using GitHub?
A: Email patches are possible. Github is what the speaker knows.
Q: Do you think it's possible to achieve 100% compatibility with
current emacs code?
A: Most should be compatible (since elisp package is the biggest
emacs strength) but there might be differences on purpose.
Q: troyhinckley: so you're re-implementing elisp in
rust? have you considered using a more modern lisp, such as scheme?
[11:03]
A: No actually. Only trying to reimplementing the C part of
emacs, replacing it by rust. There are two other talks in the
conference to use something else (guile and scheme).
Q:can remacs be reused?
A: Some of the code and some of the ideas (documentation, ideas
and approach to problems) were taken. But different model: in
remacs everything is an external type. Here, instead trying, to
use the builtin type reimplementating the objects instead.
Q: hello, great talk, fascinating topic! I am a
contributor of the compiler team of the Rust prog. language (though
I don't delve in the code myself). Do you have specific features
from the Rust compiler that are missing (or are nightly-only) that
you would take advantage of? 10:05:38
A: Polonius: the new borrow checker. Would solve
lifetime-tracking issues. A better tracking mechanism would be
better.
Q: What are you thoughts on the GUI layer. Any plans on how to
reimplement it?
A: Either GTK+ or direct GUI in Rust, but no concrete plan so
far.
Q: (not a question) Re. the GUI layer, the Gtk project has
automated bindings with a framework called "GObject
Introspection," which is what I am using for my "Gypsum" project.
Probably Rust has a GObject Implementation which you could use.
A: Problem with Rust GUIs very new and mostly demo without
accessibility and test in all environements.
Q: If money could fix the problem, how much would it cost to ship
this with feature parity before 2026?
A: Probably needs a couple people and more than one year of
work. Lots of testing required to fix all bugs.
Q: troyhinckley: elisp is implemented in c, so if
you're not implementing elisp in rust, are you using/keeping the c
implementation of elisp?
A: Idea of the project is to keep the Elisp layer and changing
the C layer underneath that is currently called, replacing it by
rust. Elisp does not change. Plan is to be even be bug
compatible to emacs: Elisp should be perfectly compatible with
both C and rust. Elisp is fixed in this approach.
Q: sorry if you already discussed this, but will your Rust
implementation also be able to run Emacs bytecode? Or are you
implementing it at the Lisp level?
A: Bytecode interpreter already exists (actually bytecode
compiler and interpreter was bootstrapped first as it is
implemented in Elisp).
A: the bytecode is missing some OP-code that have not been
encountered so far.
Q: Cool, so will you also provide bytecode JIT compilation via
"libgcc" the way Emacs currently does?
A: Eventually maybe, but a proper JIT would be better as it
includes type information to better optimize the code.
Q: Is it possible to bootstrap without the interprerter?
A: having only bytecode does not work, as some part are expected
to be interpreted with macros containing functions that are not
yet defined. You really need an interpreter for the lazy macro
expansion.
Q: How would you do the native module system? What would be
different? Sounded like part of an anwer about Rust Emacs modules
got cut off.
A: There is an FFI, so it should be possible to have the same
thing. Could possibley implement FFI in elisp.
Q: That was me who was discussing with you about
bootstrapping Scheme! (Not Guile, R7RS Scheme) Yes, it would be a
whole other thing than what you have done so far. But it would be
cool, because then you would be able to run the "Gypsum" editor on
top of your Emacs engine.
A: I still think that would be really cool to
get working! we will have to collaborate on that more.
Q: elisp is implemented in c, so if you're not implementing elisp in rust, are you using/keeping the c implementation of elisp?
IRC: of course they are implementing elisp in rust (?), what else could they mean?
I think they misunderstood your question
(not yet answered)?
Notes
Getting good vibes from this Rust work
(you can do the offscreen-cursor thing in current emacs
with enough effort. see scroll-in-place.el.)
Oooooh flow images. My last expectation was Rust
making Dslide image animations slick
one big problem with using gtk -- there is a
*two-decade-old* unfixed bug in gtk where it keeps permanent
references to the x server etc (IIRC, its wayland support has
similar problems). this means a gtk emacs --daemon crashes if you
connect to an x server and then close the x server down. this
is.... unfortunate
I had not thought about the fact that we could just mutate functions on the fly just like data. Neat!
really good talk!
Getting good vibes from this Rust work
I'm spending half this talk going OOH and wishing we had this in current emacs
Very cool talk!
So looking forward to a faster multi-threaded Emacs
Well, it does sound like we need some scale to turn this from a multi-year idea to a next-year idea
ruby shows one advantage here -- they're reimplementing bits of the ruby C core in ruby, and speeding it up because their new jitter then sees the insides of it and can optimize the hell out of it. ruby is, of course, famously slow... but not as slow as elisp
one wonders if emacs could benefit similarly...
one big problem with using gtk -- there is a two-decade-old unfixed bug in gtk where it keeps permanent references to the x server etc (IIRC, its wayland support has similar problems). this means a gtk emacs --daemon crashes if you connect to an x server and then close the x server down. this is.... unfortunate
yeah "of course" all new emacs efforts should be able to run elisp code; but it would be awesome if new lispy code could be written in scheme
YouTube comment: Really interesting approach. I like the humility in the beginning, the likes of "this is to try new approaches".
Hello, EmacsConf. My name is Troy Hinckley, and this is mytalk on Rune, a Rust implementation in Emacs. We strive to bebug compatible with Emacs, so you can use the same Elisp.It's still a fairly early stage experimental project, andwe have some basic things implemented.
Before I get started, I want to talk a bit moreabout what the core is.So the Emacs core, it includes the runtime, the interpreter,garbage collector, everything used to run the code.It includes the GUI. It includes all the data structures.If you look underneath all the Elisp data structures,there's C code underneath there,as well as the auxiliary functionsof which there's about 1500. In making this talk, I don'twant to give the impression that I'm saying the core isoutdated or that needs to be replaced or that it can't beevolved on its own, because clearly it has continued toevolve. If we look in just the last few years, we can see thatwe've added native compilation, we've added tree-sittersupport, we've added color emoji, and there's work rightnow to add a new garbage collector to Emacs as well.
Why create this project? Emacs has a long history.It has a lot of users. It needs to support a big community.Because of that, it has to be very conservativeabout what things it can allow into the project.Forks like this create anopportunity to experiment and try new approaches.This is particularly a good use case for Rust because the C core,it's pretty well tested. It's been around for a long time.A lot of the bugs have been ironed out, but when you're doing anew greenfield project, it's very easy to introduce newundefined behavior and memory unsafetyand stuff like that. Rust protects us from most of that,but it also gives us the ability to be fastand has a strong ecosystem behind it.Rust is also really good at multi-threading.Their phrase in the community is fearless concurrency.They should be able to write concurrent programs withouthaving to worry about data races. It's also really highperformance. It has a really good regex engine. It's knownfor its non-copy I/O as well.
How does this compare to otherRust and Emacs projects, whether there's been a couple? Thefirst is Remacs. This project was the first. It took anoutside-in approach. Basically you could take a Cfunction and replace it with a Rust function and build ittogether as one executable. This is pretty easy to dobecause they could both talk over the C ABI. You couldswap out functions once at a time. They made really goodprogress at first, but eventually they ran into the problemthat as you get down to the really core parts of it, you can'tjust replace one function at a time anymore, because some ofthat functionality is connected to other things. Like forexample, you can't replace the garbage collector withoutreplacing the entire garbage collection system. So theprogress really kind of slowed down. Another issue with itwas, is that they were doing a one-to-one rewrite, so theyweren't adding any new features or functionality, justtaking the same code and replacing it in Rust, which doesn'tadd any advantages in and of itself.This spawned Emacs-NG, which was kind of the spiritual successor toRemacs, where they decided to add new functionality,the biggest one being a JavaScript runtime,as well as some new renderers to Emacs.This is no longer actively developed though.
In this project, one of the big focuses we have ison multi-threading. The C core itself is, everything isdesigned around being single-threaded, all the datastructures and everything like that. Rust has a greatconcurrency story. In Rust, everything is intended to bemulti-threaded. That doesn't mean that everything has torun on multiple threads, but you can't write something thatis limited to only running in a single-threadedenvironment. So this makes it really easy to use all theexisting packages and build something that isconcurrency safe. which is what we've done here,and that was relatively easy to do.
But adding it to Elisp is the hard part,because we've got to come up with a good modelfor Lisp, and Elisp is just a giant ballof mutable state. We need to find someway to tame that so we can make workable concurrencyout of it. There's really two ways you can do this.
One is what I call the no-GIL method.This is what Python is doing, whereyou take all of your data structures, you make themconcurrency safe, and then you just leave it up to theprogrammer to decide what they're going to do with it.They've got to build safe abstractions on top of that.One of the big downsides with this is thatit comes with a pretty high cost.The last benchmarks I've seen is that by makingeverything concurrency safe in Python, single-threadedcode is about 20% slower in some benchmarks.Since most code is single-threaded, this has a bigperformance impact for most code that isn't takingadvantage of the multi-threading. The other thing is thisintroduces a lot of nasty concurrency bugs because you canhave anything mutating any part of the data from any thread,even if you can't have memory unsafety per se.
The other option is actors,which are a really known way to approach this,where you trade some of that flexibility that you getwith fully concurrent for more control and. Code andfunctions are shared between all the different threads,but data has to be passed along channels between differentactors.
We want the functions to be shared, and thisshould be pretty easy because we don't mutate functionslike we do data, except when we do. In Lisp, functions arejust lists like anything else. So you can mutate themjust like lists. Even if you're not talking aboutinterpreted code, like if you have a native compiledfunction, you can still mutate the constants inside thefunction. For example, here we have a function returns astring. We take that string out, we mutate that string, andnow the function returns a different string. In Rune, weenforce that all functions, their constants areimmutable. You can't mutate the insides of a function. Youcan still swap out functions and redefine them, but youcan't mutate the inside of a function. This enables themto be safely shared across threads.
However, there are some caveats to this.For example, some functions actually doneed to mutate their own data. The example that we run into iscl-generic. It uses a method cache. So it has to be able toupdate that cache. In this case, we just made a specialcase for this particular situation, but we don't know whatmore of these we're gonna run into the future where this isneeded behavior to be able to mutate a function.
Okay, so functions are pretty easy.They just can be shared betweenthreads, but data can't be immutable, at least not into themodel that Emacs currently has. We have two differentways to handle this. One is we require whenever you'recalling some other code in a different thread, you have tosend all the variables that it's going to need over to thatthread. This is how you traditionally do inside actors.Any data that needs to go to a different actor needs to be sentover a channel. It's relatively easy implementation, butthis is difficult in the Emacs case because everything isgoing to be accessing different variables. That meanswhen you call something, you have to know ahead of time, allthe different variables that are gonna be accessed insidethat other thread and put those in when you call it.
The other option we're using is we're copying values to theother threads on demand. If you're running a thread, ittries to look up a variable. It doesn't have any value forthat variable. It will go back and ask the main thread and itwill copy that value into that thread and it can continueexecution. This is nice because you can just launch somecode and it'll take care of handling all the data transferfor you.
But we don't want to be copying around is buffers,because they can be really large. In this case, we have amutex. Each thread could only have one current buffer thatit has an exclusive lock to. This comes with sometrade-offs, big one being that if the user tries to accesssome buffer, they want to type something, and a backgroundthread is holding onto that buffer, what do we do in thatsituation? And we still need to hold an exclusive lock, evenif we're only going to read a buffer. If you have multiplereaders, they each still need to take turns because we can'tdetermine if at some point a thread is going to try and mutatethe buffer. It has to be an exclusive lock. The other issueis buffer-locals. This is less of a implementation issueas much as it is a technical issue. Because you think aboutwhen we switch to a buffer, it has some buffer-local data andwe have some thread-local data. As we go through, we'remutating everything. Those can get intertwined andpointing to each other. Then we switch away from thatbuffer. We need some quick way to be able to separate thoseout. The buffer-locals can go with the buffer-locals andthe thread data can stay with thread data and make copies ofanything that was pointing to the other side. But we don'thave a good method to determine how to separate those two,like what data belongs to this and what data belongs to this,so that we can do that quickly. We haven't found a goodsolution to that yet, but it's something we're stillworking on.
The question is, would this actually beuseful for doing real work inside Emacs? I would say,yes, there's a lot of things you can do with this. You couldhandle process output in the background. You can do syntaxhighlighting. You can do buffer search in parallel. You cando LSP. You can do fetching your mail in the background. Youcan have a window manager that doesn't block your windowmanager when Emacs is blocked. You could dosomething like a file system watcher that keeps up on fileswithout blocking Emacs. This wouldn't be so great forbuilding concurrent data structures or operating onshared data or building your own abstractions, because of thetrade-offs that we've made here. Okay. That's talkingabout multi-threading.
The other thing we're going to talkabout is precise garbage collection. In Rune, we have asafe, precise garbage collection because of the Rust typesystem. Let's look at what the problem is with garbagecollection in the first place. Really, the tricky partabout garbage collection is rooting. How do we find out whatthe roots are? These are all the values that are on thestack or inside the registers. In this example here, weallocate an object. We call garbage_collect, that object'scollected, and then we try and return it.It's no longer valid.
Let's look at how Emacs used to deal with thisproblem way back in the day. There was a system called gcproor GC Protect, which is basically designed that every time avalue needed to survive past a garbage collection point,you had to try and protect it. In order to do this, you hadto declare a struct, you had to put a macro around it to rootthe object, and then you had to unroot it when you were done--past the garbage collection. Now the value is safe. Youcan see down here, I pulled these eight rules out from areally old version of the Emacs manual about all the thingsyou had to keep track of when you were trying to use thissystem. All right, so there was a special handling fornested GC protects. You had to make sure the memory wasinitialized. You had to make sure that traps couldn't occurbetween allocating and when GC protect would happen. Itcan be tricky because you don't always know when a functionthat's getting called could potentially call garbagecollection. So if you got something wrong, you alsomight not catch it for a long time because garbagecollection may only get called one out of 99 times. The other99 times is just fine. That one time it happens and youcan't reproduce the issue. When you do get this wrong andsome, something doesn't get rooted and it getsoverwritten, it generally doesn't show up right where theproblem is. It gets showed up way later when you actually tryand access the value and the value is invalid. You've gotto track it back to where that thing did not get properlyrooted. It's a huge source of bugs and very hard tomaintain.
Emacs decided to go with a different path,which we call conservative stack scanning. Basically,the garbage collector just looks at the stack and all theregisters and any data inside there that looks like it couldbe a pointer, it treats it as a pointer. This is nice becauseyou get really easy root tracking,but it also comes with some trade-offs,mostly that your objects are no longer movable.
Why would we want movable objects in Emacs?There's a couple of different reasons. One is compaction.You can take all your heap, you can pack that on down becauseyou can coalesce all your objects together. Another is thatit's easy to implement generational garbage collection.You can just copy everything out of your minor heap into yourolder heap. Really, Emacs is kind of uniquely ideal forgenerational collection, because the typical way weinteract with Emacs is as a series of commands. You executesome command, you'd execute the next command, you executea command. It could be happening every key press, it could behappening with M-x. However long that command is, that isthe ideal length for the minor collection generation, thefirst generation. Because once you're done with thatgeneration, anything that's still existing is going to bearound for a very long time. So that works out really wellfor Emacs. We want to make this a generational collector.The other thing is with object layout. We use a lot of listsinside Emacs Lisp. Every time you go to the cdr, you'vegot to be chasing a pointer around the heap and followingthat. That can potentially result in cache misses andall sorts of other things like that. So it can take a longtime. It can be quite slow. But if you have the ability to moveobjects, you can just relocate an entire list and lay it outin an array right next to each other inside memory.So iterating over it is just as fast as iterating over an array.But you can only do that if you have movable objects.I'll point out here too, that with conservative stack scanning,it's not that all objects are immovable. It's only ones thatare pointed to from the stack or from the registers that haveto become immovable.
Let's look at how Rust makes precisegarbage collection easy. Here I have some Rust code toshow kind of how the lifetime system works and what we callXOR mutability, where we can only have one mutablereference or multiple immutable references to the samething. Here we declare a vector, we take a reference to thefirst element of the vector, and then we mutate the vector.Now this could potentially resize the vector and move it to adifferent location in memory, so that reference is nolonger valid. The nice thing is, Rust catches this forus. It says, hey, this is no longer valid. This referencecan't survive past when you mutated it. Okay? That'sexactly what we want for a garbage collector. You can seehere, we take this in a garbage collection context, wecreate a new context object, we add an object, we callgarbage_collect, then we try and access that object. It's nolonger accessible, and Rust will prevent us from trying toaccess that variable. So, how do we solve this? We have aroot macro. We declared this root macro, it lets us take theobject and let it live past garbage collection, andeverything works out. The nice thing is, this root macrowill get dropped when it's out of scope, so we don't have toworry about the un-gc-protect step of this. Statically,Rust will verify and tell us any object that needs to berooted. If we try and access it, it'll tell us it's invalid.We have this root macro and then we can access it. So inthat way, we have safe, precise garbage collection withoutany chance of introducing undefined behavior, which isreally, really powerful. It's really easy because thetype system will catch it all for us.
There's some other Rust niceties I want to kind oftalk through that are useful, butare not, you know, star features. One is proc macros. Youcan see up on the top, you can see how you declare a functioninside the C core. All right. You have to use the macro. Youhave to put the list type, the function type,the struct type, the different types of argumentsor different number of arguments, the doc string,and then you can put your argument listing down inside there.On the Rust side, we just write this like we wouldany other Rust function. And then we putthe defun proc macro on thereand it takes care of everything for us behind the scenes.A couple of cool additional things we can do with thisis that we don't have to make everything just an object.We can actually make thingsmore specific types. Here we have symbols. As well asyou can see subfeature, it's an optional parameter, and wejust make it an option inside Rust and it automatically makeit an optional inside Elisp.This makes them really easy to write.I can't take credit for this is because this issomething that I saw inside Remacs and I stole from them, butit makes the functions really easy to call from each otherand really easy to write as well.
Another thing that's really nice is sum types.In the C core, if I wanted to get astring out of an object, I would first need to check that it'sa string and then dereference it as a string. But if it's not astring, I may introduce undefined behavior. So incomplicated code, I have to make sure that I have alwayschecked what type it is before I try and dereference thattype. We don't have to worry about any of that inside Rustbecause we can untag a value and we can use their some types,basically create an enum and we can match on what thedifferent values can be. Then we only get out the typesthat are viable or are actually there. So we neveraccidentally get something out of an object that we didn'tmean to, or dereference it as something that doesn'treally exist. We can just match on it and we can get out thevalues that we need, which is really, really powerful.
So there's some other Rust niceties as well working with here.One is the regex engine inside Rust is really fast, highperformance. We are using that for the Elixir regexengine to give it high performance and worst-caseguarantees.
The other is that Rust has a lot of really goodparsers for things like JSON that are no copy parsers thatare high performance. We can use those inside Rune aswell.
There's a handful of other changes we're working onthat are not Rust-specific, but we'd like to see. The first isbeing GUI first, terminal second. This means two things.First is that we have all of our key bindings. Right nowinside Emacs, C-i and TAB are bound to the same keybinding by default, because that's how it works inside theterminal. In the GUI, you shouldn't have that limitation.The second is that the GUI should not block when Lisp isblocked. It should be independent of that. Your GUI canstill continue to operate when Lisp is running.
The other is the ability to have an off-screen cursorso that you can be typing on something,you can scroll up and down and the pointdoesn't have to follow you where you lose your place whereyou were before. You don't have to intentionally set a mark.You can just scroll and then start typing and it'll go back upto where it was before, like it works in most applications.And this can be optional.
The other is image flow. We want itso that you can easily flow images and you can have largeimages and scroll past them without jumping and you can flowtext around images.
How are we testing this project? Because there's a lot ofthings that you could get wrong here. One thing we're doingis we're using ERT. Emacs ships with over 7,000 built-intests--Elisp tests. We are using this test suite to testour project as well. We can kind of use this as a dashboardof saying how close are we to getting to parity with GNUEmacs. The other thing that we have is a tool called elprop,which is an external utility that basically tests forcorrectness. Because really, the correctness of Rune iswhatever Emacs does, because there's no official spec onhow things should behave. To do this, we can go look atthe Rust function signature. We know what the argumentsare, we know how many they are, and we know what types theyshould be. Given that information, we can generate awhole bunch of random functions feeding those types in. Andthen we send a copy over to Emacs, we send a copy over to Rune.They each evaluate it and they return the result and we makesure the results are the same. Then you do that forthousands of different implementations of the function.And it helps us find corner cases really easy without havingto handwrite a whole bunch of different cases to test thingsand say, where are these two functions different?
So the current status: we already have a multi-threaded Elixirinterpreter and bytecode engine inside there. There's noactual text editor in there yet, but the primitives arethere. Like you can insert text, move point around,delete text, do different things like that. But we don'thave a GUI hooked up to different key bindings to actuallytype on. There's just a REPL to operate in. We have about250 of the 1500 built-in functions already implementedinside there. There's a lot of low-hanging fruit inside thisarea to still be implemented.
The next directions we'reworking on is we're optimizing the GC. We want to make itgenerational. Like I said, right now, it's just a simplesemi-spaced copying GC. We want to add a proper GUI. We needto implement text properties, overlays, process and jobcontrol, all that goodness right there.
How can you get involved? This is hosted on GitHub.You can come on over.If you have any ideas about how to implement something orsomething you'd like to see done, go ahead and just open anissue so we can have a discussion about it. We've had lots ofinteresting discussions with different people coming into the GitHub repo. If you're interested in contributing,the easiest way is probably to run elprop, pick somefunction, run elprop on it. I promise it won't take long tofind some issues, some discrepancy between Emacs and Rune,and that lets you dive into the Rust code and figure out, andthe C code, and figure out what the difference is between thetwo. or come along and help implement your favoritefunctionality. This has been a really interesting projectso far, and we've had a handful of different contributors onit who just kind of want to learn Rust or get more intosystems-level programming. Thank you.
Captioner: sachac
Q&A transcript (unedited)
Okay, so I'm going to look at some of the questions showing upin the etherpad we got here.
[00:00:08.500]Q: Have you considered using CRDTs to share buffers between threads and merge any concurrent edits automatically?
It says, have you consideredusing a CRDT to share buffers between threads and merge anyconcurrent edits automatically? So I have looked at that.And the problem with CRDTs is that even though they give you amathematically correct answer when you're trying to mergetwo conflicts, it's not always a useful answer. Like, it'snot coherent. If you have two things trying to edit the samething, there's no good way to resolve that. And so theyreally work well when you have two people working live, bothediting the same document, because they can fix anyparticular issues like that, like you would with GoogleDocs. But you have different packages that aren't aware ofeach other, and you're going to run into problems. And sothis is something, if you read from the Xi editor, which wasone of the first ones to use CRDTs, in the retrospective, hetalks about how they had this problem, where the CRDTs Theygive you an answer, but it's not always an answer that'suseful. And so I feel like locks at least are going to make it.It's not going to be as efficient if you have a whole bunch ofpackages, but I don't imagine there's going to be a ton ofthose. It can actually, I think it'll be more useful inpractice.
[00:01:05.874]Q: Why hosted on GitHub? GitHub is nonfree. Is it possible to report bugs/send patches without using GitHub?
I host on GitHub because that's what I know.If there's a way to host it on somewhere else, I'd beinterested in doing that. If you're interested in settingpatches without using GitHub, you could always send anemail. I'm more than happy to accept email patches.
[00:01:22.960]Q: Do you think it's possible to achieve 100% compatibility with current Emacs code?
Do you think it's possible to achieve with the current Emacscode? I do. I think, I think you can do that. Um, like I said,there's a couple things inside there that are intentionalbreaks with existing Emacs code. And some of those beinglike functions are immutable. As well as having data sharedswitch between different threads, which means there'sgoing to be some copying going on. So there's going to besubtle things that are going to be different. And we'vereally got to think about those intentionally, but I'mreally going for bug compatibility with GNU Emacs so thatyou can take an existing Elisp package and just run it and itjust works, 'cause I think that's one of the big strengths ofthe Emacs ecosystem is the millions of lines of Elisp thatpeople have written.Um, So I'm not, okay...
[00:02:11.913]Q: so you're re-implementing elisp in rust? have you considered using a more modern lisp, such as scheme? [11:03]
So since you're re-implementing Elisp andRust, have you considered using more modern Lisp such asScheme? So I'm not re-implementing Elisp and Rust. I'mre-implementing the C in Rust. In fact, I would like to makemore of the core that's written in C in Elisp instead of C orRust, because then it's actually introspectible. There'sa talk by Tom Tromney that he gave a while ago about Emacsshould be Emacs Lisp. I kind of like that philosophy,that as much of it as should be Elisp as possible, and weshould only have C or Rust or some systems level language forreally low-level stuff. Using a more modern LISP such asScheme. I know there's, I mean, there's two talks, I think,in this conference about using Scheme inside Emacs. And Ilooked at this at one point about what if you wrote it insideCommon LISP, because that's also has some really low leveldetails. And then you could go from Elisp down to CommonLisp. But the problem is, is under the hood, you still need asystems language. You can't, you still need either C or Rustor something underneath the Common Lisp to implement theprimitives. And so it's not going to give you just twolanguages, you know, you'll have three. You'll have theelisp, common lisp, and C under the hood. And so in this casewe just have the two. We have the Elisp and the rest.All right that's all the questions I see there. Letme go look at... Okay,so I see into the chat.
[00:04:01.400]Q: Do you have specific features from the Rust compiler that are missing (or are nightly-only) that you would take advantage of?
Does it features from the Rust compiler that are missingthat way you would take advantage of? Oh, that is a greatquestion. Um, there's a handful of them. Uh, I should'vewritten down a list of these. One of them is Polonius, whichis the new borrow checker because we're trying to be usedlifetime to track our objects. We often run into situationswhere we've kind of got a hack around things because thelimitations with the borrow checker. And so I have a wholebunch of like notes inside there about where. A betterPolonius would help inside there and help ease some of theissues. Another thing is enum variances types, becauseright now we have an object which is defined as a big enum thathad all the possible objects, but if we want to have a subsetof those objects or just pass in one of those objects, we'vegot to define a new struct. And so we have a whole bunch ofboilerplate code to define that all out. And if we hadvariances types, that would make the code a lot easier.Another one is the allocator API. Right now we're kind ofworking around it, but ultimately we're going to need ourown allocator. And the allocator API is still nightly only.So there's a couple more. I'll look at that more, but that's agreat question.Let's see.
Okay. I see a question you might not have noticed just askingabout reuse of Remacs. Oh, yes, so I have reused some of Remacscode, and some taken, like I mentioned, taken some of theirideas, but ultimately we're using a different modelbecause under the hood in Remacs, everything is just definedas an opaque external type that's defined inside Emacs andso it just pulls those in interacts with those and passesthem back into C. We're trying to see what we can do if wesay okay we're not going to take the same... So they're bound tothe implementation details of Emacs, and we don't want to dothat. We've re-implemented all the core typesourselves. So that means that we can't just take theRemacs code one for one and use it in our project, but we cantake a lot of their ideas. I've spent some time readingthrough their documentation, different things about howthey approached strings and GCand different stuff like that.Looks like all the questions.
[00:07:23.600]Q: What are you thoughts on the GUI layer. Any plans on how to reimplement it?
Okay, so another question. What are your thoughts on the GUIlayer? Any plans on how to reimplement it? This is somethingI've thought a lot about, but I still don't have a solid planfor. I'm not really a GUI person. I mostly work withlow-level. And so my two thoughts is you can go the GTK route.There's Rust bindings for that. That's well understood.It's got a good support. But there's also some interestingprojects to try and do GUI in Rust, native Rust, and have ituse Rust idioms and stuff like that. And so those are thingslike Druid and there's eGUI and a bunch of those that are thatway. And I've never used one of them, but I'd be interested totry that out first and see how well does this work and how wellsupported is this doing a Rust-first GUI.
[00:08:21.240]Q: If money could fix the problem, how much would it cost to ship this with feature parity before 2026?
If money could fix the problem, how much would it cost to shipthis with feature parity before 2026? Ooh, in a year. Uh,that's a good question. Even if we had the money, it wouldtake more than just me, even if I was working on it full time.Um, I don't know. That's a good question.I would think it would take a teamof at least a handful of people to get thisshipping within one year. Because there's still a lot ofwork to do. And even once you have everything implemented,there's a lot of bug finding and smoothing it out so that itruns as well as Emacs, which has been, you know, battletested for a long time.Um, okay, so this might be a good moment for me to break in andjust say that we've got about 10 minutes left before weresume new talks on on both tracks. Of course, we're happy tokeep this. This chat open and keep the recording going here,which will share after the conference as long as as long asthere's discussion here. Thank you.
GTK project hasautomatic binding with a framework called GObjectintrospection, which is what I'm using for gypsum project.Probably Rust has a G object implementation, which you canuse. Yeah, I know it has some GTK bindings. Um, I'm not sure ifit's specifically GObject, but that's a good pathbecause I feel like the problemwith the Rust GUIs is that they're allvery new. And so, you know, everything works in a demo, butyou need something that can work across all differentdevices and all different platforms and have really goodsupport and good accessibility and stuff like that.
[00:09:56.600]Q: elisp is implemented in c, so if you're not implementing elisp in rust, are you using/keeping the c implementation of elisp?
All right, Elisp is implemented in C. So if you're notimplementing Elisp in Rust, we're keeping the Cimplementation of Elisp. So let me see if I can do a better jobof explaining this. So inside Emacs, you have about amillion lines of Elisp. And underneath that, you have the C,which is the primitives everything's implemented yet. Andso we're keeping all of the Elisp, and we're just taking thatC layer and replacing it with Rust. And so when you call abuilt-in function, it's calling into a Rust functioninstead of a C function under the hood. So all the Elisp staysElisp, but the C becomes Rust.
So looking at the IRC chat, it feels to me like maybe there's alittle bit of confusion around what do we mean when we sayrewriting Elisp in Rust, right? I think there are somepeople that are like, A, we're reinventing ELISP, andthere's other people that are like, no, we're trying to bebyte-for-byte compatible with Rust.So some people are questioning your no answer on that.Aren't you really, maybe, is what I'm reading in there.Do you want to respond to that?Yeah, I'm trying to think about how I can make thisclearer. So the Elisp stays Elisp. We're not changing theElisp at all, or at least very minimally. We want to be able totake, like I said, bug compatible. So whatever works insideexisting Emacs, you can take all the Lisp and you can run it inRune and it works the same. So the Elisp stays the same. It'sjust the under the hood core that is getting replaced. Andthis in turn adds some new features such asmulti-threading. So it's not exactly compatible, butyou should be able to use your existing code and the Elisp willstay Elisp. So the idea is that anything that I've written asan Emacs user, my config, my custom packages, whatever itmay be, that's all going to be valid code. If you take, youknow, the Elisp implementation as being the C codeand the parts of Elisp written in Elispthat represent opportunity spacethinking about Rust as an implementation language...Okay fine. You know, you can make a semantic argument, okaywe're re-implementing, we're creatingan alternate implementation of Elispbut what Elisp is isn't the problemspace here. That's a fixed, a given, if you will.Is that all right?That's a good way of saying it. Okay. Yeah, what yousaid makes sense. I was kind of responding to some comments,like I'm not sure it connected for everybody. Makes a lot ofsense. Yeah, I wasn't sure how much I needed to expand on thatand explain that, but I appreciate you jumping in.Um, okay. So if I were just going to, but...
[00:12:57.908]Q: Will your Rust implementation also be able to run Emacs bytecode? Or are you implementing it at the Lisp level?
Will your Rust implementation also be able to run Emacs bytecode or theimplemented at the Lisp level? So I already have a bytecodeinterpreter inside there that runs the existing Elispbytecode. And so that was one of the first things I did wasbootstrap the interpreter and then bootstrap the bytecodeengine. And so we compile, we use the... because the bytecodecompiler is written in Emacs Lisp. So you bootstrap thatand it gives you the Emacs bytecode. I have a bytecodeengine that runs the bytecode. So that's already done. Andyou can potentially, on top of that, do something like thedata compilation or a JIT. But we have both aninterpreter and a bytecode compiler.And I'll just break in one more time to say with about fiveminutes left in our live time with this Q&A session, whichwe're happy to keep going as long as there are questions.Coming up in five minutes, we'll have a talk on color on thegen track. And then right here, we'll have the p-searchtalk.Thank you.
[00:14:20.100]Q: Is it possible to bootstrap with just the bytecode interpreter?
Is it possible to bootstrap with just the bytecodeinterpreter? So I'll have to put in a link to one of my blogposts. So that was my original idea was to say, I don't want tohave an interpreter, a bytecode interpreter and a nativecompiler. I want to just have just one.So I'm only going to have the bytecode.And so that's what I did initially.The problem with that is, is that a bunch of the early bootstrapEmacs code is written with the assumptionthat it's going to be interpreted.This is especially true with macros,where you'll have a function defined,you'll evaluate part of the function.The other half of the function has macros in itthat are not defined yet, but it doesn't matter because theydon't get used. But with the bytecode interpreter, itexpands all macros when it gets the function definition.And so those weren't macros when the function was expanded,and therefore they got instantiated as functions, butthey're not functions, they're macros. And so I initiallyspent a bunch of time trying to work around this, trying tomove code around, work stuff around, refactor the code totry and get it to work with only bytecode interpreter. Andeventually I just gave up. I said, you know what, I'm justgoing to write an actual interpreter to handle this becausetrying to handle all these lazy macros is too much work. Andeverything in the bootstrap is built with the assumptionthat you have lazy macro expansion.I'm guessing the Emacs bytecode interpreter isn'tcomplete.So it's mostly complete. There's a handful of opcodes thataren't implemented that are pretty easy to add that Ihaven't run into. And there's some of them that aredeprecated that aren't implemented, but it's essentiallycomplete.We also provide a bytecode JIT compilation via libgcc theway Emacs currently does it. Eventually I would like to...I'm more inclined to have a proper runtime JIT than anahead-of-time compiler like libgcc, like the currentEmacs native compilation, because it allows you to taketype information and actually apply that to the code, whichcan let you do more aggressive optimizations to it.
[00:17:03.960]What would it take to bootstrap Guile in Rune?
He said, we may either get a new Emacs with an ancient C corewith a modern Lisp, or an Emacs with modern core, but stuckwith ancient Elisp. So there was another project I wastalking to, one of the Guile implementations, about how wecould potentially, he was like, what would it take tobootstrap Guile in Rune, where you have both, you could haveElisp and Guile running inside this project. And so we'vestarted that discussion, which I think would be, whichwould be interesting. But it's tricky too, becausefundamentally Elisp and Guile are two differentlanguages. They have different semantics. They havedifferent ways of handling things. You've really got toconsider both of those when you're trying to make them worktogether.How would you do the native module system? What would bedifferent? We can do the same thing. We have an FFI. So Ihaven't looked into it a ton, but I feel like it could besimilar. And I'm actually interested, there's a coupleprojects on GitHub right now to have an FFI written in Elisp.So you don't even need to create a separate C or Rust module,because you can actually write native modules in Rust or C.And so you can just have direct bindings to a C FFI written inElisp. You don't need any C code inside there. And I thinkthat would be an interesting approach to look at as well.Oh, Ramin. Yeah, that's right. We were talking about that,about bootstrapping Scheme.And at this point, we have broken away from, uh, from thistalk, but we're continuing to record and this will all bepublished. I'd say, go ahead and keep going as long as you'dlike in here. And, um, thanks once again for the awesomediscussion. Thank you so much. So I'll just pay attention tothe ether pad and the chat and see.That sounds good. I'll keep an eye on IRC. And if there aremore questions here, I'll bounce people toward the etherpad or this, uh, chat room. All right, thank you.