Idiomatic high-level Janet bindings for Rust
| .vscode | ||
| csrc | ||
| examples | ||
| src | ||
| .envrc | ||
| .gitignore | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| flake.lock | ||
| flake.nix | ||
| LICENSE | ||
| README.md | ||
| update-janet.sh | ||
janet-binds
Idiomatic Rust bindings for the Janet programming language.
Status
Active development. Core functionality and basic collections are implemented.
What Works
- Janet VM initialization and cleanup
- Code evaluation via
VM::run() - Value type inspection and conversion
- Basic types: nil, boolean, number, string
- Symbols and keywords
- Collections: arrays, tuples, tables, structs
- Collection iteration with Rust iterators
- Safe memory management via RAII
- Calling Janet functions from Rust via
Value::call(),VM::call_function() - Function type checking via
is_function(),is_cfunction() - Registering Rust functions as Janet CFunctions via
janet_fn!,janet_fns!macros - Prefix-based function registration, group related functions under namespaces
What's Missing
- Fibers (coroutines)
- Abstract types
- Error stack traces (partially done, error messages included)
- Module system (
dofile,import)
Usage
use janet_binds::{VM, Value, Array, Table, Symbol, Keyword, janet_array, janet_tuple};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let vm = VM::new()?;
// Evaluate Janet code
let result = vm.run("(+ 1 2 3)")?;
assert_eq!(result.as_i32()?, 6);
// Call Janet functions from Rust
vm.run("(defn square [x] (* x x))")?;
// Method 1: Get function value and call it
let square_fn = vm.get_global("square").unwrap();
let result = square_fn.call(&[5.into()])?;
assert_eq!(result.as_i32()?, 25);
// Method 2: Call by name
let result = vm.call_function("square", &[7.into()])?;
assert_eq!(result.as_i32()?, 49);
// Call built-in functions
let result = vm.call_function("length", &[Value::array(&[1, 2, 3, 4, 5])])?;
assert_eq!(result.as_i32()?, 5);
// Type checking
let func = vm.get_global("square").unwrap();
assert!(func.is_function());
// Automatic conversions from Rust types
let values: Vec<Value> = vec![
42.into(), // i32
3.14.into(), // f64
"hello".into(), // &str
true.into(), // bool
];
// Ergonomic collection creation
let numbers = Value::array(&[1, 2, 3, 4, 5]); // No Value::number() required!
let mixed = janet_array![1, "hello", true, 3.14]; // Macro for mixed types
// Direct array/vec conversion
let from_vec: Value = vec![10, 20, 30].into();
let from_array: Value = [1, 2, 3].into();
// Ergonomic table operations
let mut table = Table::new();
table.set("name", "Janet"); // No Value::string() needed
table.set("version", 139); // No Value::number() needed
table.set(42, "answer"); // Mixed key types work
// Work with collections
let result = vm.run("@[1 2 3 4 5]")?;
let array = result.as_array()?;
println!("Array length: {}", array.len());
for (i, value) in array.iter().enumerate() {
println!("[{}] = {} (as i32: {})", i, value, value.as_i32()?);
}
// Symbols and keywords
let symbol = Symbol::new("test-symbol");
let keyword = Keyword::new("test-keyword");
let mut table = Table::new();
table.set(symbol, "symbol value");
table.set(keyword, "keyword value");
// Janet-created symbols/keywords
let janet_symbol = vm.run("'my-symbol")?;
let janet_keyword = vm.run(":my-keyword")?;
println!("Symbol: {}", janet_symbol.as_symbol()?);
println!("Keyword: {}", janet_keyword.as_keyword()?);
Ok(())
}
Ergonomic API
The bindings prioritize ergonomics and idiomatic Rust:
Automatic Conversions
// No need for explicit Value::number(), Value::string(), etc.
let val: Value = 42.into(); // i32 -> Value
let val: Value = "hello".into(); // &str -> Value
let val: Value = vec![1,2,3].into(); // Vec<i32> -> Value (array)
// Generic constructors accept any convertible type
let num = Value::number(42); // Works with i32, i64, f32, f64
let arr = Value::array(&[1, 2, 3]); // Works with any Into<Value>
Collection Macros
let array = janet_array![1, "hello", true, 3.14];
let tuple = janet_tuple!["name", "value", 42];
Ergonomic Methods
let mut arr = Array::new();
arr.push(42); // No Value::number() wrapping
arr.push("test"); // No Value::string() wrapping
let mut table = Table::new();
table.set("key", 123); // Automatic conversions
table.set(42, "value"); // Mixed types work naturally
Function Calling
// Define Janet function
vm.run("(defn greet [name] (string \"Hello, \" name \"!\"))")?;
// Call it from Rust
let result = vm.call_function("greet", &["World".into()])?;
println!("{}", result.as_str()?); // "Hello, World!"
// Or get the function value first
let greet_fn = vm.get_global("greet").unwrap();
let result = greet_fn.call(&["Rust".into()])?;
Registering Rust Functions
use janet_binds::{janet_fn, janet_fns, VM, Value, Result};
// Define a Rust function
fn rust_add(args: &[Value]) -> Result<Value> {
let a = args.get(0).and_then(|v| v.as_i32().ok()).unwrap_or(0);
let b = args.get(1).and_then(|v| v.as_i32().ok()).unwrap_or(0);
Ok((a + b).into())
}
let vm = VM::new()?;
// Register individual function
janet_fn!(vm, "rust-add", rust_add);
// Call from Janet
let result = vm.run("(rust-add 10 32)")?;
assert_eq!(result.as_i32()?, 42);
// Register multiple functions with prefix
fn fs_read(args: &[Value]) -> Result<Value> { /* ... */ }
fn fs_write(args: &[Value]) -> Result<Value> { /* ... */ }
janet_fns!(vm, "fs", {
"read" => fs_read,
"write" => fs_write,
});
// Janet sees fs/read and fs/write
vm.run("(fs/read \"/etc/os-release\")")?;
Building
This crate bundles Janet 1.39.1 as an amalgamated C source and compiles it automatically.
cargo build
cargo run --example showcase # Comprehensive API demonstration
cargo run --example functions # Calling Janet from Rust
cargo run --example cfunctions # Registering Rust functions in Janet
License
This library is licensed under MIT license.