#scheme #scheme-interpreter

rust-guile

a library which provides a Scheme runtime in your Rust program

4 releases

0.1.6 Dec 15, 2021
0.1.5 Mar 23, 2021

#524 in Programming languages


Used in rust-guile-client-example

AGPL-3.0-only

530KB
19K SLoC

Table of Contents

  1. Introduction
  2. Usage
    1. Function registration
    2. Usage TLDR
  3. Example
  4. Caveats

Introduction

rust-guile provides bindings for guile in the Rust language.

With it, you can embed a Scheme interpreter in your Rust program, write functions in Rust that become available to Scheme scripts, and launch a full Scheme repl that allows you to call Rust, C, and Scheme functions with no further effort.

Usage

The entirety of libguile is reexported, but this library provides some quality-of-life helper functions and macros to make going from zero to Scheme a breeze.

Your Rust program with an embedded Scheme interpreter will have three phases:

  1. Before Scheme initialization
  2. After Scheme initialization
  3. After Scheme launch

Between stage 1 and stage 2, the Guile bootstrapping process begins and the global structures used in Scheme are set up.

You get from stage 1 to stage 2 by calling the init_scm() function defined by this library.

In stage 2, the Scheme runtime exists and you can interact with it in your Rust code. You can register functions, modify state, etc.

At stage 3, the Scheme repl launches and replaces your running Rust process. At this point, no further Rust code will be executed (although previously compiled functions registered with the Scheme runtime can still be called!). The user is presented with a Guile Scheme repl and can hack away at their pleasure.

You get from stage 2 to stage 3 by calling the run_scm(argc, argv) function.

Function registration

To make a Rust function available to the Guile Scheme runtime, this library provides the macro register_void_function. You need to give this macro two arguments: first, a binary string (that is, a &[u8]) containing the name you would like the function to appear under in the Scheme runtime. Second, the name of a defined Rust function. Note that this is not a string, just put the actual function name. The function you register has to be defined as extern "C" or Scheme won't know how to call it, and it has to return an SCM type object (which is also exported by this library).

register_void_function creates a function that takes no parameters. It is possible to define a function with parameters, and even varargs, but that hasn't been implemented as a helpful wrapper yet. You can register your own functions using the scm_c_define_gsubr function, which is unsafe and requires five arguments:

  1. The function name as a binary string, like before
  2. The number of required parameters
  3. The number of optional parameters
  4. An int which will be interpreted as a boolean: 1 = function has varargs, 0 = function does not have varargs
  5. The function label (like the macro)

Usage TLDR

  1. Call init_scm()
  2. Write your Rust functions and register them with register_void_function!() or scm_c_define_gsubr
  3. Call run_scm(argc, argv) to head to the moon

Example

Here's an example client program which defines a simple hello world function and then spawns the Scheme repl. You can run the defined Rust function from within the Repl by typing (hello-from-rust) and hitting enter. You'll see it prints the message into the Guile repl, and returns 0!

use rust_guile::*;
use std::os;

extern "C" fn hello_from_rust() -> SCM {
    // an example function demonstrating that Rust functions work in Scheme
    println!("Hello from Rust!");
    unsafe {
	scm_from_int8(0)
    }
}



fn main() {
    let mut ___args = std::env::args().map(|mut arg| arg.as_mut_ptr() as *mut os::raw::c_char).collect::<Vec<*mut os::raw::c_char>>();
    let argc = ___args.len() as os::raw::c_int;
    let argv: *mut *mut os::raw::c_char = ___args.as_mut_ptr();

    init_scm();

    register_void_function!(b"hello-from-rust\0", hello_from_rust);

    run_scm(argc, argv);
}

Source code for this example can be found at https://gitlab.com/slondr/rust-guile-client-example, and is also on crates.io.

Caveats

Guile 3.0 is a hard requirement for this library. Most distros package ancient versions of Guile and I don't know why they do this. If you're on Arch, install Guile 3 from the AUR. If you're not, see if your platform provides binary releases or just compile it from source. This library requires libguile-3.0 to be in the load path at compile time or it will fail.

If your system uses a weird location for libguile, which I know a lot of them do, just recompile this crate from source and pkg-config should figure it out. If your system doesn't support pkg-config, this library isn't for you.

Finally, this library is in an early stage and seeing rapid development. So far I've gotten pretty good results defining fun Rust programs and loading them in Scheme, but I haven't done anything super substantial with it so your mileage may very. That being said, all the basic functionality is here.

Pull requests are appreciated.

Footnotes

Note that this string follows Scheme naming rules, not Rust naming rules. That means, for example, you can have - in function names here.

No runtime deps