What, Remacs ?!!
Remacs is a project created by Wilfred aimed at porting C part of GNU Emacs to Rust. If you are a programmer and don’t know what GNU Emacs is, you have been missing one of the most powerful development tools. Have a look.
It has been 4 months since my first contribution to Remacs. This post summarises the evolution of primitive lisp function definition in Rust over time. You can find more development reports here.
Evolution of Lisp functions in Rust
Lisp Function Definition in C
In C, lisp function definition has two parts, definition and initialization.
Definition uses a DEFUN
macro and it looks like:
DEFUN ("name-in-lisp", name_in_c, structname, min_args, max_args, intspec,
doc: /*doc string*/) (arguments..)
{
// function body
}
DEFUN
macro creates a static Lisp_Subr
struct and stores function metadata
ie, name-in-lisp
, min_args
, max_args
, intspec
and doc
along with a pointer to
name_in_c
function in its fields.
During initialization, defsubr
function creates Lisp_Object
s corresponding to each
Lisp_Subr
. These defsubr
s are collected in a syms_of_module
function in
each C file and are invoked at startup by the main
function in emacs.c
.
void syms_of_module (void) {
defsubr (&struct_name);
}
Moving To Rust, Early Attempts
Enough C for now. Let’s have a look at the Rust part. The very first attempts to
port lisp function was to literally rewrite the expanded DEFUN
macro in Rust.
As a result, definition in Rust was extremely verbose at the beginning.
fn name_in_c(arguments..) -> LispObject {
// function body
}
lazy_static! {
pub static ref structname: LispSubr = LispSubr {
header: VectorLikeHeader {
size: ((PvecType::PVEC_SUBR as libc::c_int) <<
PSEUDOVECTOR_AREA_BITS) as ptrdiff_t,
},
function: (name_in_c as *const libc::c_void),
min_args: min_args,
max_args: max_args,
symbol_name: ("name-in-lisp\0".as_ptr()) as *const c_char,
intspec: intspec
doc: ("docstring".as_ptr()) as *const c_char,
};
}
And initialization in rust_src/lib.rs
:
pub extern "C" fn rust_init_syms() {
unsafe {
defsubr(&*module::structname);
}
}
For simplicity I keep both of definition and initialization code in a single block in this post.
defun!
macro
Later on, the lazy_static!
part has been replaced by a defun!
macro. Which
shortened the definition to:
fn name_in_c(arguments..) -> LispObject {
// function body
}
defun!("name-in-lisp", name_in_c, structname, min_args, max_args, ptr::null(), "docstring");
pub extern "C" fn rust_init_syms() {
unsafe {
defsubr(&*module::structname);
}
}
Yes, defun!
was a great improvement and syntax was comparable to C. But that was
not the best what Rust can offer. After a couple of months, Jean Pierre Dudey
proposed lisp_fn
procedural macro in PR 181.
lisp_fn
Procedural Macro
Procedural macro a.k.a syntax extension a.k.a compiler plugin is a powerful Rust feature that allows manipulation code on the fly. It enables user to define functions that takes some Rust code, manipulates and produces Rust code as output.
lisp_fn
has made function definitions more concise. It derives name of
function in lisp by replacing underscores with hyphens and calculates minimum
and maximum number of arguments from the function signature. The
definition now looks like:
/// docstring
#[lisp_fn(name="optional_name",min="min_args")]
fn name_in_c(arguments..) -> LispObject {
// function body
}
pub extern "C" fn rust_init_syms() {
unsafe {
defsubr(&*module::structname);
}
}
More magic…
Though lisp_fn
has made defining lisp functions easier, there were some nits.
Rust, by default, does not expose functions to higher scopes. So, functions
defined in the Rust cannot be called from C until the compiler is instructed to
export them.
This has resulted in accumulation of whole list of functions which are ported to
Rust and still referenced from C besides rust_init_syms
function which holds
the entire list of ported functions in rust_src/lib.rs
file.
There were a couple of attempts to tackle some of this mess.
Sean Perry implemented a comprehensive solution
which utilizes Rust’s build script to scan the entire rust code base and export
all the lisp functions. The script also creates a module_exports.rs
file for
each modules that calls defsubr
on lisp functions defined on that module
wrapped in a rust_init_syms
function.
This reduced the size of rust_src/lib.rs
file from around 500 lines to 88
lines with an overhead of single line in module files.
What Next ?
The Rust language is improving day by day so does Remacs. So we can expect more.