Android Rust patterns

This page contains information about Android Logging, provides a Rust AIDL example, tells you how to call Rust from C , and provides instructions for Rust/C++ Interop Using CXX.

Android logging

The following example shows how you can log messages to logcat (on-device) or stdout (on-host).

In your Android.bp module, add liblogger and liblog_rust as dependencies:

rust_binary {
    name: "logging_test",
    srcs: ["src/main.rs"],
    rustlibs: [
        "liblogger",
        "liblog_rust",
    ],
}

Next, in your Rust source add this code:

use log::{debug, error, LevelFilter};

fn main() {
    let _init_success = logger::init(
        logger::Config::default()
            .with_tag_on_device("mytag")
            .with_max_level(LevelFilter::Trace),
    );
    debug!("This is a debug message.");
    error!("Something went wrong!");
}

That is, add the two dependencies shown above (liblogger and liblog_rust), call the init method once (you can call it more than once if necessary), and log messages using the provided macros. See the logger crate for a list of possible configuration options.

The logger crate provides an API for defining what you want to log. Depending on whether the code is running on-device or on-host (such as part of a host-side test), messages are logged using either android_logger or env_logger.

Rust AIDL example

This section provides a Hello World-style example of using AIDL with Rust.

Using the Android Developer Guide AIDL Overview section as a starting point, create external/rust/binder_example/aidl/com/example/android/IRemoteService.aidl with the following contents in the IRemoteService.aidl file:

// IRemoteService.aidl
package com.example.android;

// Declare any non-default types here with import statements

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

Then, within the external/rust/binder_example/aidl/Android.bp file, define the aidl_interface module. You must explicitly enable the Rust backend because it isn't enabled by default.

aidl_interface {
    name: "com.example.android.remoteservice",
    srcs: [ "aidl/com/example/android/*.aidl", ],
    unstable: true, // Add during development until the interface is stabilized.
    backend: {
        rust: {
            // By default, the Rust backend is not enabled
            enabled: true,
        },
    },
}

The AIDL backend is a Rust source generator, so it operates like other Rust source generators and produces a Rust library. The produced Rust library module can be used by other Rust modules as a dependency. As an example of using the produced library as a dependency, a rust_library can be defined as follows in external/rust/binder_example/Android.bp:

rust_library {
    name: "libmyservice",
    srcs: ["src/lib.rs"],
    crate_name: "myservice",
    rustlibs: [
        "com.example.android.remoteservice-rust",
        "libbinder_rs",
    ],
}

Note that the module name format for the AIDL-generated library used in rustlibs is the aidl_interface module name followed by -rust; in this case, com.example.android.remoteservice-rust.

The AIDL interface can then be referenced in src/lib.rs as follows:

// Note carefully the AIDL crates structure:
// * the AIDL module name: "com_example_android_remoteservice"
// * next "::aidl"
// * next the AIDL package name "::com::example::android"
// * the interface: "::IRemoteService"
// * finally, the 'BnRemoteService' and 'IRemoteService' submodules

//! This module implements the IRemoteService AIDL interface
use com_example_android_remoteservice::aidl::com::example::android::{
  IRemoteService::{BnRemoteService, IRemoteService}
};
use binder::{
    BinderFeatures, Interface, Result as BinderResult, Strong,
};

/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyService;

impl Interface for MyService {}

impl IRemoteService for MyService {
    fn getPid(&self) -> BinderResult<i32> {
        Ok(42)
    }

    fn basicTypes(&self, _: i32, _: i64, _: bool, _: f32, _: f64, _: &str) -> BinderResult<()> {
        // Do something interesting...
        Ok(())
    }
}

Finally, start the service in a Rust binary as shown below:

use myservice::MyService;

fn main() {
    // [...]
    let my_service = MyService;
    let my_service_binder = BnRemoteService::new_binder(
        my_service,
        BinderFeatures::default(),
    );
    binder::add_service("myservice", my_service_binder.as_binder())
        .expect("Failed to register service?");
    // Does not return - spawn or perform any work you mean to do before this call.
    binder::ProcessState::join_thread_pool()
}

Async Rust AIDL example

This section provides a Hello World-style example of using AIDL with async Rust.

Continuing on the RemoteService example, the generated AIDL backend library includes async interfaces that can be used to implement an async server implementation for the AIDL interface RemoteService.

The generated async server interface IRemoteServiceAsyncServer can be implemented as follows:

use com_example_android_remoteservice::aidl::com::example::android::IRemoteService::{
    BnRemoteService, IRemoteServiceAsyncServer,
};
use binder::{BinderFeatures, Interface, Result as BinderResult};

/// This struct is defined to implement IRemoteServiceAsyncServer AIDL interface.
pub struct MyAsyncService;

impl Interface for MyAsyncService {}

#[async_trait]
impl IRemoteServiceAsyncServer for MyAsyncService {
    async fn getPid(&self) -> BinderResult<i32> {
        //Do something interesting...
        Ok(42)
    }

    async fn basicTypes(&self, _: i32, _: i64, _: bool, _: f32, _: f64,_: &str,) -> BinderResult<()> {
        //Do something interesting...
        Ok(())
    }
}

The async server implementation can be started as follows:

#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
    binder::ProcessState::start_thread_pool();

    let my_service = MyAsyncService;
    let my_service_binder = BnRemoteService::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder.as_binder())
        .expect("Failed to register service?");

    task::block_in_place(move || {
        binder::ProcessState::join_thread_pool();
    });
}

Note that the block_in_place is needed to leave the async context which allows join_thread_pool to use block_on internally. This is because #[tokio::main] wraps the code in a call to block_on, and join_thread_pool might call block_on when handling an incoming transaction. Calling a block_on from within a block_on results in a panic. This could also be avoided by building the tokio runtime manually instead of using #[tokio::main] and then call join_thread_pool outside of the block_on method.

Further, the rust backend generated library includes an interface that allows implementing an async client IRemoteServiceAsync for RemoteService which can be implemented as follows:

use com_example_android_remoteservice::aidl::com::example::android::IRemoteService::IRemoteServiceAsync;
use binder_tokio::Tokio;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    let binder_service = binder_tokio::wait_for_interface::<dyn IRemoteServiceAsync<Tokio>>("myservice");

    let my_client = binder_service.await.expect("Cannot find Remote Service");

    let result = my_client.getPid().await;

    match result {
        Err(err) => panic!("Cannot get the process id from Remote Service {:?}", err),
        Ok(p_id) => println!("PID = {}", p_id),
    }
}

Call Rust from C

This example shows how to call Rust from C.

Example Rust library

Define the libsimple_printer file inexternal/rust/simple_printer/libsimple_printer.rs as follows:

//! A simple hello world example that can be called from C

#[no_mangle]
/// Print "Hello Rust!"
pub extern fn print_c_hello_rust() {
    println!("Hello Rust!");
}

The Rust library must define headers which the dependent C modules can pull in, so define the external/rust/simple_printer/simple_printer.h header as follows:

#ifndef SIMPLE_PRINTER_H
#define SIMPLE_PRINTER_H

void print_c_hello_rust();


#endif

Define external/rust/simple_printer/Android.bp as you see here:

rust_ffi {
    name: "libsimple_c_printer",
    crate_name: "simple_c_printer",
    srcs: ["libsimple_c_printer.rs"],

    // Define export_include_dirs so cc_binary knows where the headers are.
    export_include_dirs: ["."],
}

Example C binary

Define external/rust/c_hello_rust/main.c as follows:

#include "simple_printer.h"

int main() {
  print_c_hello_rust();
  return 0;
}

Define external/rust/c_hello_rust/Android.bp as follows:

cc_binary {
    name: "c_hello_rust",
    srcs: ["main.c"],
    shared_libs: ["libsimple_c_printer"],
}

Finally, build by calling m c_hello_rust.

Rust-Java interop

The jni crate provides Rust interoperability with Java through the Java Native Interface (JNI). It defines the necessary type definitions for Rust to produce a Rust cdylib library that plugs directly into Java’s JNI (JNIEnv, JClass, JString, and so on). Unlike C++ bindings that perform codegen through cxx, Java interoperability through the JNI doesn't require a code-generation step during a build. Therefore it doesn't need special build-system support. The Java code loads the Rust-provided cdylib like any other native library.

Usage

Usage in both Rust and Java code is covered in the jni crate documentation. Please follow the Getting Started example provided there. After you write src/lib.rs, return to this page to learn how to build the library with Android's build system.

Build definition

Java requires the Rust library to be provided as a cdylib so that it can be loaded dynamically. The Rust library definition in Soong is the following:

rust_ffi_shared {
    name: "libhello_jni",
    crate_name: "hello_jni",
    srcs: ["src/lib.rs"],

    // The jni crate is required
    rustlibs: ["libjni"],
}

The Java library lists the Rust library as a required dependency; this ensures it's installed to the device alongside the Java library even though it's not a build-time dependency:

java_library {
        name: "libhelloworld",
        [...]
        required: ["libhellorust"]
        [...]
}

Alternatively, if you must include the Rust library in an AndroidManifest.xml file, add the library to uses_libs as follows:

java_library {
        name: "libhelloworld",
        [...]
        uses_libs: ["libhellorust"]
        [...]
}

Rust–C++ interop using CXX

The CXX crate provides safe FFI between Rust and a subset of C++. The CXX documentation gives good examples of how it works in general and we suggest reading it first to become familiar with the library and the way it bridges C++ and Rust. The following example shows how to use it in Android.

To have CXX generate the C++ code that Rust calls into, define a genrule to invoke CXX and a cc_library_static to bundle that into a library. If you plan to have C++ call Rust code, or use types shared between C++ and Rust, define a second genrule (to generate a C++ header containing the Rust bindings).

cc_library_static {
    name: "libcxx_test_cpp",
    srcs: ["cxx_test.cpp"],
    generated_headers: [
        "cxx-bridge-header",
        "libcxx_test_bridge_header"
    ],
    generated_sources: ["libcxx_test_bridge_code"],
}

// Generate the C++ code that Rust calls into.
genrule {
    name: "libcxx_test_bridge_code",
    tools: ["cxxbridge"],
    cmd: "$(location cxxbridge) $(in) > $(out)",
    srcs: ["lib.rs"],
    out: ["libcxx_test_cxx_generated.cc"],
}

// Generate a C++ header containing the C++ bindings
// to the Rust exported functions in lib.rs.
genrule {
    name: "libcxx_test_bridge_header",
    tools: ["cxxbridge"],
    cmd: "$(location cxxbridge) $(in) --header > $(out)",
    srcs: ["lib.rs"],
    out: ["lib.rs.h"],
}

The cxxbridge tool is used above to generate the C++ side of the bridge. The libcxx_test_cpp static library is used next as a dependency for our Rust executable:

rust_binary {
    name: "cxx_test",
    srcs: ["lib.rs"],
    rustlibs: ["libcxx"],
    static_libs: ["libcxx_test_cpp"],
}

In the .cpp and .hpp files, define the C++ functions as you wish, using the CXX wrapper types as desired. For example, a cxx_test.hpp definition contains the following:

#pragma once

#include "rust/cxx.h"
#include "lib.rs.h"

int greet(rust::Str greetee);

While cxx_test.cppcontains

#include "cxx_test.hpp"
#include "lib.rs.h"

#include <iostream>

int greet(rust::Str greetee) {
  std::cout << "Hello, " << greetee << std::endl;
  return get_num();
}

To use this from Rust, define a CXX bridge as below in lib.rs:

#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("cxx_test.hpp");
        fn greet(greetee: &str) -> i32;
    }
    extern "Rust" {
        fn get_num() -> i32;
    }
}

fn main() {
    let result = ffi::greet("world");
    println!("C++ returned {}", result);
}

fn get_num() -> i32 {
    return 42;
}