Skip to content
This repository was archived by the owner on May 7, 2024. It is now read-only.

RustFields/scala-native-rust-interoperability-example

Repository files navigation

scala-native-rust-interoperability-example

How it works

This example shows how to integrate Rust code in a Scala project thanks to the interoperability of Rust and Scala Native with C. The project is structured in the following way:

  • There are two subprojects: native and core.
  • The native project contains the Rust code and is managed using Cargo.
  • The core project contains Scala code and is managed using SBT.
  • Gradle is used to manage the two subprojects, it calls Cargo and SBT.

The native project

The Rust code exposes some public functions, like the following one:

#[no_mangle]
pub extern "C" fn divide(a: i32, b: i32) -> i32 {
    a / b
}

The extern "C" makes this function adhere to the C calling convention. The no_mangle attribute turns off Rust's name mangling so that it has a well-defined symbol to link to. Then, to compile Rust code as a static library that can be called from C, the following was added to Cargo configuration:

[lib]
crate_type = ["staticlib"]

The Rust library can be compiled using the following command:

./gradlew cargoBuildRelease

cbindgen can be used to generate C headers, this is useful to get the functions' signature, but it is not mandatory. In particular, there is a Gradle task that uses cbindgen to generate the headers. cbindgen can be useful because the Scala code that uses the Rust implementation must comply with the related C functions' signature. To generate C headers, run:

./gradlew generateHeaders

Given the following Rust module:

pub use operations::*;

pub mod operations {
    #[no_mangle]
    pub extern "C" fn divide(a: i32, b: i32) -> i32 {
        a / b
    }

    #[no_mangle]
    pub extern "C" fn generic_operation(
        x: i32,
        fun: fn(i32) -> i32
    ) -> i32 {
        fun(x)
    }
}

The following header will be generated:

/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */

#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>


int32_t divide(int32_t a, int32_t b);

int32_t generic_operation(int32_t x, int32_t (*fun)(int32_t));

The core project

The core project is built using Scala Native and SBT.

During compilation, native code is integrated in the final binary. In the SBT configuration are defined the clang parameters needed to link the Rust library.

nativeLinkingOptions ++= {
  val path = s"${baseDirectory.value.getParentFile}/native/target/release"
  val library = "operations"
  Seq(s"-L$path", "-rpath", path, s"-l$library")
}

Scala Native will identify the library as a dependency that has native code and will unpack the library. Next, it will compile, link, and optimize any native code along with the Scala Native runtime and the application code. No additional information is needed in the build file other than the normal dependency so it is transparent to the library user.

The Rust module is wrapped in the following Scala object:

@extern
object Binding {
  def divide(a: cInt, b: cInt): cInt = extern

  def generic_operation(x: cInt, f: cFunIntToInt): cInt = extern
}

The functions in this object take advantage of the implementation provided in Rust and can be called like common Scala functions.

Run the project

To run the project, use the following commands:

./gradlew cargoBuildRelease
./gradlew sbtRun

It is also possible to run the tests:

./gradlew cargoBuildRelease
./gradlew sbtTest

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published