Skip to main content

Crate scheme_rs

Crate scheme_rs 

Source
Expand description

scheme-rs is an implementation of the R6RS specification of the Scheme programming language that is designed to embedded within sync and async Rust.

§Feature flags:

  • async: Enables support for async functions. Requires the tokio feature flag.
  • tokio: Enables support for the tokio async executor.
  • load-libraries-from-fs: Enables automatically loading libraries from the file system. The library name specifies its location on the filesystem relative to the currently running process.

§Getting started

To get started using scheme-rs in your project, create a Runtime:

let runtime = Runtime::new();

The Runtime struct initializes the garbage collector and handles the memory of JIT compiled functions. The Runtime struct is automatically garbage collected so you only need it for as long as you’re creating new scheme procedures.

§Running Scheme code from Rust

The simplest way to run scheme code from Rust is to use the TopLevelEnvironment::eval function which evaluates a string and returns the evaluated scheme values. Before you can call eval, you need to create a TopLevelEnvironment which defines the set of imports provided to the scheme code.

let env = TopLevelEnvironment::new_repl(&runtime);
env.import("(library (rnrs))".parse().unwrap());

Now that you have an environment, you can call eval on it. The first argument to eval determines whether or not the code is allowed to import external packages. If you are running untrusted user code, be sure to pass false and think careful of the functions you provide.

let vals = env.eval(
    false,
    "
    (define (fact n)
      (if (= n 1)
          1
          (* n (fact (- n 1)))))
    fact
    "
)
.unwrap();
let factorial = vals[0].cast_to_scheme_type::<Procedure>().unwrap();

§Procedures

Evaluating the previous code example returns a factorial Procedure which can be called from Rust. To do so, use the Procedure::call method. Procedures are automatically garbage collected and implement Send and Sync and are 'static so you can hold on to them for as long as you want and put them anywhere.

let [result] = factorial
    .call(&[Value::from(5)])
    .unwrap()
    .try_into()
    .unwrap();
let result: u64 = result.try_into().unwrap();
assert_eq!(result, 120);

§Running Rust code from Scheme

The simplest way to create Rust functions that are callable from Scheme is with the bridge procedural macro. The bridge proc allows one to write Scheme functions in a direct style in Rust that are automatically registered into a given library:

#[bridge(name = "add-five", lib = "(add-five-lib)")]
fn add_five(num: &Value) -> Result<Vec<Value>, Exception> {
    let num: usize = num.clone().try_into()?;
    Ok(vec![Value::from(num + 5)])
}

Bridge functions can also optionally automatically type-check their arguments at run-time, so the following definition is also valid:

#[bridge(name = "add-five", lib = "(add-five-lib)")]
fn add_five(num: usize) -> Result<Vec<Value>, Exception> {
    Ok(vec![Value::from(num + 5)])
}

Once you’ve defined a bridge function it can be imported and called from scheme:

let val = env.eval(
  true,
  "
  (import (add-five-lib))
  (add-five 12)
  "
)
.unwrap();
assert_eq!(val[0].cast_to_scheme_type::<u64>().unwrap(), 17);

It is also possible to implement bridge functions in a continuation-passing style for greater flexibility and control. See the cps_bridge proc macro for more information.

§Values

Scheme Values can be created from most primitives and std library objects simply by using From:

let pi = Value::from(3.14159268);
let pair = Value::from((Value::from(1), Value::from((Value::from(2), Value::from(())))));

Rust objects that implement SchemeCompatible can be converted using the from_rust_type function:

#[derive(Debug, Trace)]
struct Vec3 {
    x: f64,
    y: f64
}

impl SchemeCompatible for Vec3 {
    fn rtd() -> Arc<RecordTypeDescriptor> {
        rtd!(
            name: "vec3",
            fields: ["x", "y"],
            constructor: |x, y| {
                Ok(Vec3 {
                    x: x.try_to_scheme_type()?,
                    y: y.try_to_scheme_type()?,
                })
            }
        )
    }
    
    fn get_field(&self, k: usize) -> Result<Value, Exception> {
        match k {
            0 => Ok(self.x.into()),
            1 => Ok(self.y.into()),
            _ => unreachable!(),
        }
    }
}

let pos = Value::from_rust_type(Vec3 { x: 1.0, y: 2.0 });

Values can be converted back to Rust types with the

The cast_* functions convert values to Option<_>, and the try_* functions provide more detailed error conditions of the conversion failure.

assert_eq!(pi.cast_to_scheme_type::<f64>(), Some(3.14159268));

See the value module for more information.

§Error handling

All scheme-rs functions that return an error return an Exception which adhere to the scheme condition system. See the exceptions module for more information.

Modules§

env
Scheme lexical environments.
eval
Dynamic evaluation.
exceptions
Exceptional situations and conditions.
gc
Garbage collected smart pointers.
hashtables
Scheme compatible hashtables
lists
Scheme pairs and lists.
num
Scheme numerical tower.
ports
Input and Output handling.
proc
Scheme Procedures.
records
Records (also known as structs).
registry
Global collection of libraries associated with a Runtime
runtime
Scheme-rs core runtime.
strings
String builtins and data types.
symbols
Interned symbols.
syntax
Rust representation of S-expressions.
value
Scheme values.
vectors
Growable mutable vectors.