Skip to content

felipenoris/JuliaPackageWithRustDep.jl

master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
src
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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.

use std::ffi::CStr;
use std::os::raw::c_char;

#[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 is 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.

use std::ffi::CString;
use std::os::raw::c_char;

#[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 unsafe extern fn rustdylib_free_rust_owned_string(s: *mut c_char) {
    if !s.is_null() {
        drop(CString::from_raw(s)) // retakes ownership of the CString and drop
    }
}

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