flapigen is tool for connecting programs or libraries written in Rust with other languages. Currently implemented support for C++ and Java, but you can write support for any language of your choice. It is designed to run by cargo during via cargo's build script mechanism.

At first you choose what part of your Rust API will be "exported" to other programming language, and how. At the second step in cargo's build script you describe where get API description and where put resulted Rust and other language code that generated after processing API description.

Cooperation with other programming languages (in general) is done via C API, so generated Rust's code is wrapper around you code to provide C API. Generated code in other programming language is the way to make your API as easy as possible to use in other language.

In the first step you should choose "main" crate that would be the bridge between Rust and other language. It should have crate-type cdylib or staticlib, see suitable Rust book's section for a detailed description.

For example:

[lib]
name = "cpp_example_rust_part"
crate-type = ["cdylib"]

Then you should create build.rs inside this crate. For example:

//build.rs
use flapigen::{CppConfig, CppOptional, CppStrView, CppVariant, LanguageConfig};
use std::{env, path::Path};

fn main() {
    let out_dir = env::var("OUT_DIR").expect("no OUT_DIR, but cargo should provide it");
    //ANCHOR: cpp_config
    let cpp_cfg = CppConfig::new(
        // ANCHOR: cpp_output
        Path::new("..").join("cpp-part").join("rust-api"),
        // ANCHOR_END: cpp_output
        "rust".into(),
    )
    .cpp_optional(CppOptional::Boost)
    .cpp_variant(CppVariant::Boost)
    .cpp_str_view(CppStrView::Boost);
    //ANCHOR_END: cpp_config
    let swig_gen = flapigen::Generator::new(LanguageConfig::CppConfig(cpp_cfg));
    swig_gen.expand(
        "c++-api-for-rust",
        // ANCHOR: rust_input
        Path::new("src/cpp_glue.rs.in"),
        // ANCHOR_END: rust_input

        // ANCHOR: rust_output
        &Path::new(&out_dir).join("cpp_glue.rs"),
        // ANCHOR_END: rust_output
    );
    println!(
        "cargo:rerun-if-changed={}",
        Path::new("src").join("cpp_glue.rs.in").display()
    );
}

Here you instruct flapigen to generate C++/Rust code from "src/cpp_glue.rs.in" as input:

        Path::new("src/cpp_glue.rs.in"),

and we specify our output, Rust file:

        &Path::new(&out_dir).join("cpp_glue.rs"),

directory for C++ files:

        Path::new("..").join("cpp-part").join("rust-api"),

You can find a detailed description of code generation in the suitable Cargo book section.

Then you should create a file for the "foreign" language API description. For example:

use super::{f2, Foo};

foreign_class!(class Foo {
    self_type Foo;
    constructor Foo::new(_: i32) -> Foo;
    fn Foo::set_field(&mut self, _: i32);
    fn Foo::f(&self, _: i32, _: i32) -> i32;
    fn f2(_: i32) -> i32;
});

And connect the generated code with your crate exactly how as in the Cargo book :

// src/lib.rs
mod cpp_glue;
pub use crate::cpp_glue::*;
// src/cpp_glue.rs
include!(concat!(env!("OUT_DIR"), "/cpp_glue.rs"));

Do not forget to add flapigen as a dependency into [build-dependencies] section of your crate's Cargo.toml file and you are ready to go.

C++

To run the cpp-example from here you need boost, cmake and a C++11 compatible compiler.

Project Structure

The projects consists of two parts: cpp-part and rust-part. The Rust part is compiled as shared library and linked into executable described by CMakeLists.txt. The cpp-part is the main part so its build system (cmake) invokes cargo to build the Rust part.

Building

It is a normal CMake project, so you can build it as an ordinary CMake project. By default it requires C++11 and boost, but if your compiler is modern enough you can use C++17 and then you don't need boost at all.

Just delete all mentions of boost here:

// build.rs
    let cpp_cfg = CppConfig::new(
        Path::new("..").join("cpp-part").join("rust-api"),
        "rust".into(),
    )
    .cpp_optional(CppOptional::Boost)
    .cpp_variant(CppVariant::Boost)
    .cpp_str_view(CppStrView::Boost);

The main functionality

This project demonstrates how to export Rust in the form of a class to C++.

Rust code:

// src/lib.rs
pub struct Foo {
    data: i32,
}

impl Foo {
    fn new(val: i32) -> Foo {
        Foo { data: val }
    }

    fn f(&self, a: i32, b: i32) -> i32 {
        self.data + a + b
    }

    fn set_field(&mut self, v: i32) {
        self.data = v;
    }
}

Described as class:

// src/cpp_glue.rs.in
use super::{f2, Foo};

foreign_class!(class Foo {
    self_type Foo;
    constructor Foo::new(_: i32) -> Foo;
    fn Foo::set_field(&mut self, _: i32);
    fn Foo::f(&self, _: i32, _: i32) -> i32;
    fn f2(_: i32) -> i32;
});

Usage from C++:

// main.cpp
    Foo foo(5);
    int res = foo.f(1, 2);

Java/Android

This example shows off how to use rust to build a native library from Android and use it through an automatically generated JNI wrapper.

Android Studio can be used to work with Rust via its Rust plugin. So it's not a bad idea to integrate invocations of cargo into gradle, so you can build and run Rust inside an ordinary Java/Kotlin Android application.

Project Structure

The file build.rs defines how flapigen generates wrapper code:

// build.rs
    let swig_gen = flapigen::Generator::new(LanguageConfig::JavaConfig(
        JavaConfig::new(
            Path::new("app")
                .join("src")
                .join("main")
                .join("java")
                .join("net")
                .join("akaame")
                .join("myapplication"),
            "net.akaame.myapplication".into(),
        )
        .use_null_annotation_from_package("android.support.annotation".into()),
    ))
    .rustfmt_bindings(true);

The file src/lib.rs contains real code that will be invoked from Java:

// src/lib.rs
struct Session {
    a: i32,
}

impl Session {
    pub fn new() -> Session {
        #[cfg(target_os = "android")]
        android_logger::init_once(
            android_logger::Config::default()
                .with_max_level(log::LevelFilter::Debug)
                .with_tag("Hello"),
        );
        log_panics::init(); // log panics rather than printing them
        info!("init log system - done");
        Session { a: 2 }
    }

    pub fn add_and1(&self, val: i32) -> i32 {
        self.a + val + 1
    }

    // Greeting with full, no-runtime-cost support for newlines and UTF-8
    pub fn greet(to: &str) -> String {
        format!("Hello {} ✋\nIt's a pleasure to meet you!", to)
    }
}

And the file src/java_glue.rs.in contains descriptions for flapigen to export this API to Java:

// src/java_glue.rs.in
foreign_class!(class Session {
    self_type Session;
    constructor Session::new() -> Session;
    fn Session::add_and1(&self, val: i32) -> i32;
    fn Session::greet(to: &str) -> String;
});

Then the app/build.gradle contains rules to invoke cargo to build a shared library from Rust code, and then build it into apk:

// app/build.gradle
def rustBasePath = ".."
def archTriplets = [
    'armeabi-v7a': 'armv7-linux-androideabi',
    'arm64-v8a': 'aarch64-linux-android',
]

// TODO: only pass --release if buildType is release
archTriplets.each { arch, target ->
    // execute cargo metadata and get path to target directory
    tasks.create(name: "cargo-output-dir-${arch}", description: "Get cargo metadata") {
        new ByteArrayOutputStream().withStream { os ->
            exec {
                commandLine 'cargo', 'metadata', '--format-version', '1'
                workingDir rustBasePath
                standardOutput = os
            }
            def outputAsString = os.toString()
            def json = new groovy.json.JsonSlurper().parseText(outputAsString)

            logger.info("cargo target directory: ${json.target_directory}")
            project.ext.cargo_target_directory = json.target_directory
        }
    }
    // Build with cargo
    tasks.create(name: "cargo-build-${arch}", type: Exec, description: "Building core for ${arch}", dependsOn: "cargo-output-dir-${arch}") {
        workingDir rustBasePath
        commandLine 'cargo', 'build', "--target=${target}", '--release'
    }
    // Sync shared native dependencies
    tasks.create(name: "sync-rust-deps-${arch}", type: Sync, dependsOn: "cargo-build-${arch}") {
        from "${rustBasePath}/src/libs/${arch}"
        include "*.so"
        into "src/main/libs/${arch}"
    }
    // Copy build libs into this app's libs directory
    tasks.create(name: "rust-deploy-${arch}", type: Copy, dependsOn: "sync-rust-deps-${arch}", description: "Copy rust libs for (${arch}) to jniLibs") {
        from "${project.ext.cargo_target_directory}/${target}/release"
        include "*.so"
        into "src/main/libs/${arch}"
    }

    // Hook up tasks to execute before building java
    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn "rust-deploy-${arch}"
    }
    preBuild.dependsOn "rust-deploy-${arch}"

    // Hook up clean tasks
    tasks.create(name: "clean-${arch}", type: Delete, description: "Deleting built libs for ${arch}", dependsOn: "cargo-output-dir-${arch}") {
        delete fileTree("${project.ext.cargo_target_directory}/${target}/release") {
            include '*.so'
        }
    }
    clean.dependsOn "clean-${arch}"
}

Building

To build the demo, you will need the latest version of Cargo, Android NDK and install the proper Rust toolchain targets:

rustup target add arm-linux-androideabi
rustup target add aarch64-linux-android

To link Rust code into a shared library you need add the path to the proper clang binary into your PATH environment variable or change the path to the linker here:

[target.aarch64-linux-android]
linker = "aarch64-linux-android21-clang++"
runner = "./run-on-android.sh"

[target.armv7-linux-androideabi]
linker = "armv7a-linux-androideabi21-clang++"
runner = "./run-on-android.sh"

Invocation

Gradle will take care of building and deploying the Rust sources. Thus, to build the project in release mode, simply call ./gradlew androidRelease.

To build only the rust libraries for a specific target, call cargo as usual, e.g. cargo build --target arm-linux-androideabi.

Testing

It is possible to run Rust unit tests on Android phone via run-on-android.sh script mentioned in .cargo/config, there are also instrumentation unit tests in Java that invoke Rust code.

Java/Other

TODO

Foreign Language API Description

flapigen provides several methods to describe how your Rust code can be used from a "foreign" programming language. The main building block is foreign_class!. This is the way to describe entities that will be visible for the "foreign language" as classes. It is also possible to export C-like enums via foreign_enum!, plus it is possible to describe the way to pass a "callback" into your Rust code from the foreign language via foreign_callback!. And of course it is possible to extend or rewrite existing type conversions rules via foreign_typemap!

foreign_class!

foreign_class! is the way to describe an entity that will be visible for the "foreign language" as a class.

The basic example is:

use super::{f2, Foo};

foreign_class!(class Foo {
    self_type Foo;
    constructor Foo::new(_: i32) -> Foo;
    fn Foo::set_field(&mut self, _: i32);
    fn Foo::f(&self, _: i32, _: i32) -> i32;
    fn f2(_: i32) -> i32;
});

Here Foo may be struct or enum or a more complex type (see below). The self_type can be omitted if there are no constructors and all fn do not accept self.

self_type

The self_type is used as a "neat" way to call methods of types inside "smart pointers". For example sometimes you can not use your struct or enum explicitly, because of the semantic of the "foreign language" does not allow it, then you can write:

foreign_class!(class CircularDepsA {
    self_type CircularDepsA;
    constructor CircularDepsA::java_new(_: &str) -> Arc<Mutex<CircularDepsA>>;
    fn CircularDepsA::a(&self, b: &CircularDepsB) -> String;
});

And flapigen will be instructed to generate code to call each method listed above with lock().unwrap().method syntax.

Also maybe you want export a trait implementation as foregin_class. The flapigen cannot work with Box<Trait>, because it requires unstable/unsafe APIs to convert the boxed trait into something that can be transferred over the FFI border, but it can work with Box<Box<Trait>>, so via self_type you can represent a boxed trait as class:

pub trait Interface {
    fn f(&self, _: i32) -> i32;
    fn set(&mut self, _: i32);
}
struct InterfaceImpl {
    base: i32,
}

impl Interface for InterfaceImpl {
    fn f(&self, x: i32) -> i32 {
        self.base + x
    }
    fn set(&mut self, x: i32) {
        self.base = x;
    }
}

fn create_interface() -> Box<Box<dyn Interface>> {
    Box::new(Box::new(InterfaceImpl { base: 17 }))
}

foreign_class!(class Interface {
    self_type dyn Interface;
    constructor create_interface() -> Box<Box<dyn Interface>>;
    fn Interface::f(&self, _: i32) -> i32;
    fn Interface::set(&mut self, x: i32);
});

Also it should be noted that flapigen generates code that uses "Universal Function Call Syntax", so methods can be fake methods and declared outside of impl blocks:

pub struct TestMethodNotMethod;

impl TestMethodNotMethod {
    fn new() -> Self {
        TestMethodNotMethod
    }
}

fn method_not_method(_this: &TestMethodNotMethod) {}

foreign_class!(class TestMethodNotMethod {
    self_type TestMethodNotMethod;
    constructor TestMethodNotMethod::new() -> TestMethodNotMethod;
    fn method_not_method(&self);
});

Access modifiers

By default, methods in generated class are public. You can change access level via protected and private keywords.

foreign_class!(
    class Foo {
        self_type Foo;
        constructor Foo::new() -> Foo;
        private constructor Foo::from_int(_: i32) -> Foo;
        private fn Foo::private_f();
        fn Foo::public_f();
        protected fn Foo::protected_f();
    }
);

Inline methods

It is also sometimes convenient to write short methods just inside foreign_class!, for example to add a way to access fields of a struct or to export a result of a Rust macro call:

foreign_class!(class TestFnInline {
    fn int_to_str(a: i32) -> String {
        format!("{}", a)
    }
});

To reference self in inline methods you should use the this variable, because the inline method is not the real one, it is just code block that gets included into the generated code:

pub struct Session {
    name: String,
}

foreign_class!(
    #[derive(SmartPtrCopy)]
    class Session {
        self_type Session;
        constructor session_init(name: &str) -> Rc<RefCell<Session>> {
            Rc::new(RefCell::new(Session {
                name: name.into(),
            }))
        }
        fn name(&self) -> &str {
            &this.name
        }
    }
);

Methods aliases

Also you can create an alias for a function name:

    fn Foo::f(&self, _: i32, _: i32) -> i32;  alias calcF;

So it would be called by path with fn in Rust code, and you can call it via name with alias in foreign language.

This may be useful for example if you want to name functions in Java in camel case style, while you want to use snake case style in Rust.

Constructors

Constructors are Rust methods that mapped to constructors in terms of the "foreign" language. Also constructors, more precisely, the return type of constructors can be used to ask flapigen to simplify calls of methods, see the self_type section for more details. Sometimes you need a constructor, but you don't want allow the user to construct objects, then you can use an empty constructor:

foreign_class!(
    class GamepadId {
        self_type GamepadId;
        private constructor = empty;
        fn GamepadId::value(&self) -> usize;
    }
);

foreigner_code

flapigen also supports bypassing code generation:

foreign_class!(class TestPathAndResult {
    self_type TestPathAndResult;
    constructor TestPathAndResult::empty() -> Result<TestPathAndResult, String>;
    constructor TestPathAndResult::new(path: &Path) -> Result<TestPathAndResult, String>;
    fn TestPathAndResult::get_path(&self) -> String; alias getPath;
    fn TestPathAndResult::get_boo(&self) -> Rc<RefCell<Boo>>; alias getBoo;
    foreign_code "    public int javaFunc() { return 17; }\n";
    foreign_code r#"
    public Boo[] testHandArrayReturn() { return do_testHandArrayReturn(this.mNativeObj); }
    private static native Boo[] do_testHandArrayReturn(long me);
"#;
    fn TestPathAndResult::get_foo_list(&self) -> Vec<Foo>;
    fn TestPathAndResult::get_result_foo_list(generate_err: bool) -> Result<Vec<Foo>, String>;
});

After that you can implement the Java_com_example_TestPathAndResult_do_1testHandArrayReturn function yourself, useful for when flapigen cannot handle something automatically, or you want to do something special.

Doc comments

You can also add comments to generated code with Rust doc comments:

foreign_class!(
/// Class comment description for Foo.
#[derive(Clone)]
class Foo {
    self_type Foo;
    /// some text about the new function
    ///
    /// ```
    /// some markdown example in the text
    /// ```
    ///
    /// @param val - some number
    /// @param name - more information
    constructor Foo::new(val: i32, name: &str) -> Foo;

Derives

You can use "derive" syntax on the declaration of classes, in a similar way to the usage on "Rust" structs:

/// Class comment description for Foo.
#[derive(Clone)]
class Foo {
    self_type Foo;

Usage of derives changes generated code in various ways. For example, you can use Clone,Copy to force the generation of a copy constructor and operator= in C++ case. You can also use camelCaseAliases to change names of all methods to camel case.

foreign_enum!

Through the usage of the foreign_enum! macro you can "export" enums (C like enum) to a foreign language:

#[derive(Clone, Copy)]
enum MyEnum {
    Item1,
    Item2,
    Item3,
}

foreign_enum!(
    enum MyEnum {
        ITEM1 = MyEnum::Item1,
        ITEM2 = MyEnum::Item2,
        ITEM3 = MyEnum::Item3,
    }
);

This allows you to use it as input or output types for foreign_class! methods.

foreign_callback!

You can also use a trait to describe a callback from Rust to Java/C++:

trait EnumObserver {
    fn on_state_changed(&self, item: MyEnum, is_ok: bool);
}

foreign_callback!(callback EnumObserver {
    self_type EnumObserver;
    onStateUpdate = EnumObserver::on_state_changed(&self, item: MyEnum, is_ok: bool);
});

foreign_class!(class TestEnumClass {
    self_type Moo;
    constructor Moo::default() -> Moo;
    fn Moo::f1(&mut self, v: MyEnum) -> i32;
    fn Moo::next_enum(v: MyEnum) -> MyEnum;
    fn call_cb(cb: Box<dyn EnumObserver>) {
        let mut state = false;
        for e in &[MyEnum::Item1, MyEnum::Item2, MyEnum::Item3] {
            cb.on_state_changed(*e, state);
            state = !state;
        }
    }
});

As a result of flapigen processing foreign_callback! it generates an interface for Java and an abstract class for C++, so you can implement methods in Java/C++ and pass a pointer/reference to Rust, and for Rust it would be represented as a trait implementation.

foreign_typemap!

flapigen works with a graph where vertices are types and edges are conversion rules between types. To extend or rewrite some rules you can use the foreign_typemap! macro. WARNING this syntax is experimental and subject to change.

Let's take a look at an example:

foreign_typemap!(
    ($p:r_type) DateTime<Utc> => jlong {
        $out = $p.timestamp_millis();
    };
    ($p:f_type, option = "NoNullAnnotations", unique_prefix = "/*chrono*/")
        => "/*chrono*/java.util.Date" "$out = new java.util.Date($p);";
    ($p:f_type, option = "NullAnnotations", unique_prefix = "/*chrono*/")
        => "/*chrono*/@NonNull java.util.Date" "$out = new java.util.Date($p);";
);

The (r_type) part of the rule describes a conversion from any Rust type to a Rust type that can cross the FFI (Foreign Function Interface) border. For example, in the case of connection Rust and C++, types that can cross the FFI border are nearly all primitive types, plus types marked with repr(C) and some others. After conversion to the type that can cross the FFI border, you sometimes want to describe how this type should be converted to "foreign" type. This is done with the (f_type) part of the rule. Returning to our example, first we describe how to convert DateTime<Utc> (Rust type) to jlong (Rust type). Then we describe how to convert long (Java type) to java.util.Date (Java type).

There is one nuance. As you can see the Java type is called /*chrono*/java.util.Date instead of just java.util.Date. This is done because you don't want two edges to one vertex in our graph mentioned above. There is already a rule for std::time::SystemTime (Rust type) to java.util.Date (Java type), and it is better not have two rules (edges) that lead to the same vertex java.util.Date, so we create new unique vertex /*chrono*/java.util.Date != java.util.Date.

Almost all parts of foreign_typemap! are optional. In the example below we define that jlong (Rust type) is corresponding to long (Java type), and there is no need to convert anything to get long from jlong and vise versa.

foreign_typemap!(
    (r_type) jlong;
    (f_type) "long";
);

It is also possible to use foreign_typemap! for generic type conversion. In the example below we define the rule to convert any Java type that looks like X [] to Vec<T> if type T implements the traits SwigForeignClass + Clone.

foreign_typemap!(
    ($p:r_type) <T: SwigForeignClass + Clone> Vec<T> <= internal_aliases::JForeignObjectsArray<T> {
        $out = jobject_array_to_vec_of_objects(env, $p);
    };
    ($p:f_type, option = "NoNullAnnotations") <= "swig_f_type!(T) []";
    ($p:f_type, option = "NullAnnotations")
                  <= "@NonNull swig_f_type!(T, NoNullAnnotations) []";
);