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.cpp
contains
#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;
}