Example of a Julia Package with Rust dependency.
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
contrib fix memory leak introduced by 9bb3640 Sep 28, 2018
deps Auto-generate api.jl Sep 27, 2018
src update readme Sep 28, 2018
test fix memory leak introduced by 9bb3640 Sep 28, 2018
.gitignore Auto-generate api.jl Sep 27, 2018
.travis.yml better solution for Rust owned string Sep 25, 2018
LICENSE Update LICENSE Feb 16, 2019
Project.toml fix memory leak introduced by 9bb3640 Sep 28, 2018
README.md update badges Sep 28, 2018

README.md

JuliaPackageWithRustDep.jl

License travis

This is a set of examples on how to embed a Rust library in a Julia package. Interfacing between Julia and Rust library is done using Rust's FFI: the Rust library is exposed as a C dynamic library, and Julia will call Rust functions using ccall.

The build script deps/build.jl uses cargo to build the Rust library deps/RustDylib. Julia bindings to the Rust API are implemented in src/api.jl file.

If the Rust library build is successful during Pkg.build, the file deps/deps.jl is generated, and the package __init__ function will call check_deps to check if the Rust dynamic library is callable. This follows the same convention used by BinaryProvider.jl.

Requirements

  • Julia v1.0

  • Rust Stable

Installation

julia> using Pkg

julia> pkg"add https://github.com/felipenoris/JuliaPackageWithRustDep.jl.git"

julia> Pkg.test("JuliaPackageWithRustDep")

Primitive Type Correspondences

Julia Rust
Int32 i32
Int64 i64
Int64 i64
Float32 f32
Float64 f64
Bool bool

Passing a Julia Owned String to Rust

A Julia String is converted to a Cstring and passed to Rust, which will receive it as a pointer to char.

function rustdylib_inspect_string(s::String)
    ccall((:rustdylib_inspect_string, librustdylib), Cvoid, (Cstring,), s)
end

In Rust, the pointer to thar *const c_char is converted to a CStr, which is a reference to a C String. From a CStr, you can convert it to a regular &str.

#[no_mangle]
pub extern fn rustdylib_inspect_string(cstring: *const c_char) {
    let cstr = unsafe { CStr::from_ptr(cstring) };

    match cstr.to_str() {
        Ok(s) => {
            // `s` is a regular `&str`
            println!("Rust read `{:?}`.", s);
        }
        Err(_) => {
            panic!("Couldn't convert foreign Cstring to &str.");
        }
    }
}

Returning a Rust Owned String to Julia

In this example, the Rust generates a owned string with rustdylib_generate_rust_owned_string and the ownership it transfered to the Julia process.

After being consumed, the Julia process must transfer the ownership back to Rust by calling rustdylib_free_rust_owned_string, to let the memory be freed.

#[no_mangle]
pub extern fn rustdylib_generate_rust_owned_string() -> *mut c_char {
    let rust_string = String::from("The bomb: 💣");
    let cstring = CString::new(rust_string).unwrap();
    cstring.into_raw() // transfers ownership to the Julia process
}

#[no_mangle]
pub extern fn rustdylib_free_rust_owned_string(s: *mut c_char) {
    unsafe {
        if s.is_null() { return }
        CString::from_raw(s) // retakes ownership of the CString
    };
}

In the Julia process, the pointer to string is copied to a new String instance using unsafe_string function. Then, Julia asks Rust to free the string.

function rustdylib_free_rust_owned_string(s::Cstring)
    ccall((:rustdylib_free_rust_owned_string, librustdylib), Cvoid, (Cstring,), s)
end

function rustdylib_generate_rust_owned_string()
    ccall((:rustdylib_generate_rust_owned_string, librustdylib), Cstring, ())
end

function read_rust_owned_string() :: String
    cstring = rustdylib_generate_rust_owned_string()
    result = unsafe_string(cstring) # copies the contents of the string
    rustdylib_free_rust_owned_string(cstring) # ask Rust to free the memory
    return result
end

Resources