Question

Using C, at runtime, I can:

  1. Create the source code of a function,
  2. Call out to gcc to compile it to a .so (Linux) (or use llvm, etc.),
  3. Load the .so, and
  4. Call the function.

Is a similar thing possible in Rust?

In particular I want to use Algebraic Data Types, so using a C subset of Rust's features is not sufficient.

Was it helpful?

Solution

Not yet, officially, though it should be at least possible with not too much hacking. The biggest obstacle is that the libraries do not have any ability to do dynamic loading yet. Here's a potential strategy to make it work (on Rust's incoming branch).

  • Link to the rustc crate to drive the compiler programmatically. Be aware that the compiler is not threadsafe so only run one in-process build at a time.
  • Mark the function you want to call with #[no_mangle]. This should (I haven't tried it) produce an unmangled symbol name so it's easy to find.
  • Create minimal bindings to dlopen/dlsym
  • Find the function pointer and unsafely convert it to a Rust closure type (currently defined in sys::Closure).
  • Call the closure.

Rust also has a minimally-tested JIT that can be used for this type of thing, but it has some major bugs.

OTHER TIPS

There might be some other possible solution but, I had used below solution recently and it has been working well for my specific requirement.

Hope it will be useful to you too OR it can provide start point to you to reach to more complex and robust solution.

main.rs

use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::process::Command;
use libloading::{Library, Symbol};

/// signature of function which should be called from plugin
type AddFunc = unsafe fn(isize, isize) -> isize;

/// Create a plugin file at runtime which will be converted to shared library
fn write_file()  -> std::io::Result<()> {
    let mut file = File::create("plugin.rs")?;
    file.write_all(b"fn main() {\n")?;
    file.write_all(b"\t#[no_mangle]\n")?;
    file.write_all(b"\tpub extern \"C\" fn add(a: isize, b: isize) -> isize {\n")?;
    file.write_all(b"\t\ta + b\n")?;
    file.write_all(b"\t}\n")?;
    file.write_all(b"}\n")?;
    Ok(())
}
/// compile plugin code file to shared library
/// todo 1) should allow to pass file path. 
///      2) should return path to generated shared library
fn compile_file() {
    let mut compile_file = Command::new("cmd");
    compile_file.args(&["/C", "rustc", "--crate-type", "cdylib", "plugin.rs"]).status().expect("process failed to execute");
}
/// call function from shared library
/// todo suffix should be selected based on OS.
fn call_plugin(a: isize, b: isize) -> isize {
    let lib = Library::new("plugin.dll").unwrap();
    unsafe {
        let func: Symbol<AddFunc> = lib.get(b"add").unwrap();
        let answer = func(a, b);
        answer
    }
}
fn main(){
    let args: Vec<String> = env::args().collect();
    if args.len() == 3 {
        write_file();
        compile_file();
        /// get argument from commandline to pass to function
        let a: isize = args[1].trim().parse().expect("number required");
        let b: isize = args[2].trim().parse().expect("number required");
        println!("{}+{}:{}",a,b,call_plugin(a,b));
    }
    else {
        println!("USAGE: main.exe NUM NUM");
    }
}

Cargo.toml

[package]
name = "runtime_plugin"
version = "0.1.0"
authors = ["Manthan R Tilva"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
libloading = "0.5.2"

You can also find this code in github

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top