Expand description
Define a Binder interface as a Rust trait — no .aidl, no build.rs.
use rsbinder::{interface, BinderResult, Strong, Uri};
#[interface(descriptor = "com.example.IHello")]
pub trait IHello {
fn echo(&self, msg: &str) -> BinderResult<String>;
fn fill(&self, out: &mut Vec<i32>) -> BinderResult<()>; // `&mut` = out
#[oneway] fn ping(&self) -> BinderResult<()>;
}
rsbinder::serve(Uri::kernel())?.add("hello", BnHello::new_binder(Impl))?.run()?;
let h: Strong<dyn IHello> = rsbinder::connect(Uri::kernel().with_service("hello"))?;§Relationship to .aidl
.aidl stays the canonical path (plan 2-17 D3): it is what Android tooling
reads, what other languages generate from, and the only path that carries
AIDL-only semantics. This macro is for rsbinder ↔ rsbinder interfaces,
where the wire is private to both ends.
It is not a re-implementation. The macro fills the very same
rsbinder_aidl::render structs the AIDL front-end fills and runs the same
templates, so a trait here and the equivalent .aidl produce identical
generated code — moving from one to the other never touches a call site.
What it cannot express (unions, constants, @VintfStability,
@EnforcePermission, ParcelableHolder, generics, nested types) is what
needs AIDL semantics; write those in .aidl.
§What the signature means
| Signature | Meaning |
|---|---|
x: i32 — an AIDL scalar, or a bare name that may be an enum (asserted Copy) | in argument, by value |
x: &Cfg, x: &str, x: &[T] — every other in type borrows | in argument |
x: &mut T | out argument |
#[inout] x: &mut T | written and read back |
#[deprecated] / #[deprecated = "…"] | AIDL’s @deprecated, on a trait or a method |
Option<T> | nullable |
#[oneway] on a method | no reply; must return BinderResult<()> |
Option<T> is AIDL’s @nullable, which AIDL allows only on the types that
have a null representation on the wire. A #[derive(BinderEnum)] enum is
carried as its repr scalar and is not one of them: Option<Mode> fails to
compile inside the generated code, because the enum has no SerializeOption
— the same shape .aidl rejects at the AIDL level. A primitive has no null
either, so Option<i32> is refused outright, wherever it sits.
AIDL’s scalars are a fixed set. bool, i8 (byte), i32 (int),
i64 (long), f32 (float), f64 (double) and u16 (char) are the
only scalar spellings .aidl renders, joined by u8 as the element of a
byte[] (&[u8]). byte is the one type whose spelling moves with the
place — i8 as a scalar, u8 as an array element — and neither is
accepted in the other’s place. Any other Rust scalar — u32, u64, i16,
usize, u128, Rust’s own char, or a u8 outside an array — is refused
wherever it sits, argument, return or #[derive(Parcelable)] field: the
nearest .aidl renders a different Rust type, so moving the interface to
.aidl would break every call site, which is the one thing this macro
promises it will not do. u128 is worse than a mismatch — it writes
sixteen bytes no conforming AIDL peer can decode.
Not everything can be an out parameter. AIDL passes a primitive, a
String, an enum, an IBinder and an interface in only, and a
ParcelFileDescriptor in or inout (AOSP GetArgumentAspect). Each
&mut in a direction AIDL does not permit is refused — &mut String even
as &mut Option<String>, because @nullable does not widen the direction,
and a &mut ParcelFileDescriptor unless it is #[inout] — except for the
enum: the macro sees only its name and cannot tell
&mut Mode from a parcelable’s &mut Config, which is a legal out
parameter. Never take an enum by &mut; no .aidl can express it. An
array of any of these takes every direction.
A binder object (Strong<dyn IFoo>, SIBinder) and a
ParcelFileDescriptor have no Default for the callee to start from, so
.aidl wraps them in Option<_> wherever there is nothing to start from —
a #[derive(Parcelable)] field and the slots of an array the callee must
default. The table below has the exact spelling for every one of those;
what matters here is that the Option is .aidl’s own and not a
@nullable you wrote, and that an out fd array left None fails the
call with UNEXPECTED_NULL, a null having no valid fd encoding. A
ParcelableHolder is a parcelable field type only and cannot appear in a
signature at all.
An out vector is an array, never a List. An out &mut Vec<T> is
.aidl’s out T[], and &mut Option<Vec<T>> its out @nullable T[]: the
proxy sends the caller’s current length (null for None) and the service
starts from a vector pre-sized to it. .aidl’s out List<T> renders the
very same signature but sends no length and starts the service from an
empty vector (None when @nullable), so the two are not wire-compatible.
out List<T> has no spelling here; it needs .aidl.
A @nullable array wraps its elements unless the element is a
primitive or an enum, in every direction and at every arity. A
non-@nullable array wraps them only where the slot starts empty: an out
array, or a fixed-size parcelable field, of a binder, an interface or a fd.
Every cell of the rule is in the table below, which is generated from the
rules and checked against them.
One cell no table can decide for you. A #[derive(BinderEnum)] element
counts as a primitive and stays bare; a parcelable element takes an
Option<_>. The macro sees a name and not which of the two it is, so it
accepts either and cannot tell you which is right — spell an enum element
bare and a parcelable element Option<_>, as .aidl renders them.
A Box appears in one place only. .aidl boxes a parcelable field
that is @nullable(heap=true), or a bare @nullable one that closes a
reference cycle — Option<Box<T>> — and nothing else, so a Box anywhere
else is refused, including every argument and return: a cycle is judged
against the declaration being generated, and an interface is never part of
one. Which .aidl field that spelling stands for depends on annotations and
declarations the macro cannot see, so it accepts it on any parcelable field.
Paths resolve inside the generated module. The body lands in a
{Trait}_binder module one level below where the macro was written, and it
reaches the surrounding scope through a use super::*;. A bare name is
therefore the user’s own — but super::X names the module the macro was
written in, not its parent, so a signature that means the parent has to say
crate::X. self:: is rejected outright for the same reason; super:: is
left legal because it is the only way to name a type the generated module
shadows (BnFoo, BpFoo, transactions, the trait’s own name). Use the
macro at module scope: inside a function body super::* skips the block,
so items declared in that block are not visible to the signature.
The generated body names prelude items by absolute path, so a parent item
called Ok, Option or Default reaches only the signatures that name
it. Two names still resolve in the module: Box, which async-trait’s
expansion uses bare under the async feature, and the owned String /
Vec<T> the stub declares for a &str / &[T] argument. The async half
declares its own type parameters and helper struct next to the signatures,
all named with the __Rsb prefix, so the macro refuses a trait name that
starts with it and a signature path whose first segment does (crate::…
still reaches such an item). A parent item named rsbinder takes over the
generated code’s rsbinder:: paths; the last paragraph of
What it emits says why.
Spell types directly. A proc macro cannot see through a type alias or
a use … as rename, and every check above reads the spelling. &mut Ids
for type Ids = Vec<i32> reads as an opaque named type: it loses the
length word .aidl writes for an out array (T[]), and an aliased
ParcelFileDescriptor element loses the null guard that keeps a null fd
off the wire. Likewise a renamed ParcelableHolder or an alias of
Option<i32> slips past the check that refuses it and generates the code
that check exists to prevent, and a renamed Strong or
ParcelFileDescriptor escapes the out-parameter rule above. Path
qualification is fine — std::vec::Vec<i32> is matched structurally — but
an alias or a rename is not.
A doc comment on the trait or a method is accepted but not carried: the
render layer has no doc field, so the emitted IFoo, its methods and
BnFoo/BpFoo carry no rustdoc from here.
§What it emits
The generated items — the trait, BnFoo, BpFoo, IFooDefault, and with
the async feature the IFooAsync halves — land in the module where the
macro was written, so they are usable without a path. They physically live
in a #[doc(hidden)] mod {Trait}_binder that is glob re-exported: the
generated body reuses .aidl’s fixed internal names (transactions,
on_transact, DEFAULT_IMPL), which would collide if two interfaces were
emitted side by side. That module is an implementation detail — never name
it. IFoo and Foo both name their halves BnFoo/BpFoo (AOSP
ClassName), so one module holds at most one of them: a second fails with
E0428 on __rsbinder_one_interface_per_stem_Foo. Put them in separate
modules.
Transaction codes are assigned by declaration order
(FIRST_CALL_TRANSACTION + i), exactly as .aidl does without explicit
codes — so reordering methods is a wire break. The descriptor defaults
to the bare trait name; pass descriptor = "…" for anything shared across
crates.
The generated code names rsbinder:: directly, so the dependency has to
keep that name — a package = "rsbinder" rename will not resolve. The
path is relative, not ::rsbinder::: the generated module glob-imports the
module the macro was written in, so a mod rsbinder or use … as rsbinder
there is what every generated rsbinder:: path resolves to.
§What .aidl renders
The spelling the AIDL compiler produces for each kind of type, by position. One scalar stands for its kind: long, float, double and char follow the int rows as i64, f32, f64 and u16. List<T> and generic parcelables are not listed. A trait written with these is a trait an .aidl port reproduces exactly; any other spelling of these types the macro refuses, naming the cell you wanted — except between the Cfg and Mode rows, which are both a bare name to the macro, and a field’s Option<Box<Cfg>>, which .aidl writes for a @nullable(heap=true) parcelable field and for a @nullable one that closes a reference cycle.
— marks a combination AIDL itself rejects.
| AIDL type | in | out | #[inout] | return | field |
|---|---|---|---|---|---|
int | i32 | — | — | i32 | i32 |
int[] | &[i32] | &mut Vec<i32> | &mut Vec<i32> | Vec<i32> | Vec<i32> |
@nullable int[] | Option<&[i32]> | &mut Option<Vec<i32>> | &mut Option<Vec<i32>> | Option<Vec<i32>> | Option<Vec<i32>> |
int[N] | &[i32; N] | &mut [i32; N] | &mut [i32; N] | [i32; N] | [i32; N] |
@nullable int[N] | Option<&[i32; N]> | &mut Option<[i32; N]> | &mut Option<[i32; N]> | Option<[i32; N]> | Option<[i32; N]> |
boolean | bool | — | — | bool | bool |
boolean[] | &[bool] | &mut Vec<bool> | &mut Vec<bool> | Vec<bool> | Vec<bool> |
@nullable boolean[] | Option<&[bool]> | &mut Option<Vec<bool>> | &mut Option<Vec<bool>> | Option<Vec<bool>> | Option<Vec<bool>> |
boolean[N] | &[bool; N] | &mut [bool; N] | &mut [bool; N] | [bool; N] | [bool; N] |
@nullable boolean[N] | Option<&[bool; N]> | &mut Option<[bool; N]> | &mut Option<[bool; N]> | Option<[bool; N]> | Option<[bool; N]> |
byte | i8 | — | — | i8 | i8 |
byte[] | &[u8] | &mut Vec<u8> | &mut Vec<u8> | Vec<u8> | Vec<u8> |
@nullable byte[] | Option<&[u8]> | &mut Option<Vec<u8>> | &mut Option<Vec<u8>> | Option<Vec<u8>> | Option<Vec<u8>> |
byte[N] | &[u8; N] | &mut [u8; N] | &mut [u8; N] | [u8; N] | [u8; N] |
@nullable byte[N] | Option<&[u8; N]> | &mut Option<[u8; N]> | &mut Option<[u8; N]> | Option<[u8; N]> | Option<[u8; N]> |
String | &str | — | — | String | String |
@nullable String | Option<&str> | — | — | Option<String> | Option<String> |
String[] | &[String] | &mut Vec<String> | &mut Vec<String> | Vec<String> | Vec<String> |
@nullable String[] | Option<&[Option<String>]> | &mut Option<Vec<Option<String>>> | &mut Option<Vec<Option<String>>> | Option<Vec<Option<String>>> | Option<Vec<Option<String>>> |
String[N] | &[String; N] | &mut [String; N] | &mut [String; N] | [String; N] | [String; N] |
@nullable String[N] | Option<&[Option<String>; N]> | &mut Option<[Option<String>; N]> | &mut Option<[Option<String>; N]> | Option<[Option<String>; N]> | Option<[Option<String>; N]> |
Cfg | &Cfg | &mut Cfg | &mut Cfg | Cfg | Cfg |
@nullable Cfg | Option<&Cfg> | &mut Option<Cfg> | &mut Option<Cfg> | Option<Cfg> | Option<Cfg> |
Cfg[] | &[Cfg] | &mut Vec<Cfg> | &mut Vec<Cfg> | Vec<Cfg> | Vec<Cfg> |
@nullable Cfg[] | Option<&[Option<Cfg>]> | &mut Option<Vec<Option<Cfg>>> | &mut Option<Vec<Option<Cfg>>> | Option<Vec<Option<Cfg>>> | Option<Vec<Option<Cfg>>> |
Cfg[N] | &[Cfg; N] | &mut [Cfg; N] | &mut [Cfg; N] | [Cfg; N] | [Cfg; N] |
@nullable Cfg[N] | Option<&[Option<Cfg>; N]> | &mut Option<[Option<Cfg>; N]> | &mut Option<[Option<Cfg>; N]> | Option<[Option<Cfg>; N]> | Option<[Option<Cfg>; N]> |
Mode | Mode | — | — | Mode | Mode |
Mode[] | &[Mode] | &mut Vec<Mode> | &mut Vec<Mode> | Vec<Mode> | Vec<Mode> |
@nullable Mode[] | Option<&[Mode]> | &mut Option<Vec<Mode>> | &mut Option<Vec<Mode>> | Option<Vec<Mode>> | Option<Vec<Mode>> |
Mode[N] | &[Mode; N] | &mut [Mode; N] | &mut [Mode; N] | [Mode; N] | [Mode; N] |
@nullable Mode[N] | Option<&[Mode; N]> | &mut Option<[Mode; N]> | &mut Option<[Mode; N]> | Option<[Mode; N]> | Option<[Mode; N]> |
ParcelFileDescriptor | &rsbinder::ParcelFileDescriptor | — | &mut rsbinder::ParcelFileDescriptor | rsbinder::ParcelFileDescriptor | Option<rsbinder::ParcelFileDescriptor> |
@nullable ParcelFileDescriptor | Option<&rsbinder::ParcelFileDescriptor> | — | &mut Option<rsbinder::ParcelFileDescriptor> | Option<rsbinder::ParcelFileDescriptor> | Option<rsbinder::ParcelFileDescriptor> |
ParcelFileDescriptor[] | &[rsbinder::ParcelFileDescriptor] | &mut Vec<Option<rsbinder::ParcelFileDescriptor>> | &mut Vec<rsbinder::ParcelFileDescriptor> | Vec<rsbinder::ParcelFileDescriptor> | Vec<rsbinder::ParcelFileDescriptor> |
@nullable ParcelFileDescriptor[] | Option<&[Option<rsbinder::ParcelFileDescriptor>]> | &mut Option<Vec<Option<rsbinder::ParcelFileDescriptor>>> | &mut Option<Vec<Option<rsbinder::ParcelFileDescriptor>>> | Option<Vec<Option<rsbinder::ParcelFileDescriptor>>> | Option<Vec<Option<rsbinder::ParcelFileDescriptor>>> |
ParcelFileDescriptor[N] | &[rsbinder::ParcelFileDescriptor; N] | &mut [Option<rsbinder::ParcelFileDescriptor>; N] | &mut [rsbinder::ParcelFileDescriptor; N] | [rsbinder::ParcelFileDescriptor; N] | [Option<rsbinder::ParcelFileDescriptor>; N] |
@nullable ParcelFileDescriptor[N] | Option<&[Option<rsbinder::ParcelFileDescriptor>; N]> | &mut Option<[Option<rsbinder::ParcelFileDescriptor>; N]> | &mut Option<[Option<rsbinder::ParcelFileDescriptor>; N]> | Option<[Option<rsbinder::ParcelFileDescriptor>; N]> | Option<[Option<rsbinder::ParcelFileDescriptor>; N]> |
IFoo | &rsbinder::Strong<dyn IFoo> | — | — | rsbinder::Strong<dyn IFoo> | Option<rsbinder::Strong<dyn IFoo>> |
@nullable IFoo | Option<&rsbinder::Strong<dyn IFoo>> | — | — | Option<rsbinder::Strong<dyn IFoo>> | Option<rsbinder::Strong<dyn IFoo>> |
IFoo[] | &[rsbinder::Strong<dyn IFoo>] | &mut Vec<Option<rsbinder::Strong<dyn IFoo>>> | &mut Vec<rsbinder::Strong<dyn IFoo>> | Vec<rsbinder::Strong<dyn IFoo>> | Vec<rsbinder::Strong<dyn IFoo>> |
@nullable IFoo[] | Option<&[Option<rsbinder::Strong<dyn IFoo>>]> | &mut Option<Vec<Option<rsbinder::Strong<dyn IFoo>>>> | &mut Option<Vec<Option<rsbinder::Strong<dyn IFoo>>>> | Option<Vec<Option<rsbinder::Strong<dyn IFoo>>>> | Option<Vec<Option<rsbinder::Strong<dyn IFoo>>>> |
IFoo[N] | &[rsbinder::Strong<dyn IFoo>; N] | &mut [Option<rsbinder::Strong<dyn IFoo>>; N] | &mut [rsbinder::Strong<dyn IFoo>; N] | [rsbinder::Strong<dyn IFoo>; N] | [Option<rsbinder::Strong<dyn IFoo>>; N] |
@nullable IFoo[N] | Option<&[Option<rsbinder::Strong<dyn IFoo>>; N]> | &mut Option<[Option<rsbinder::Strong<dyn IFoo>>; N]> | &mut Option<[Option<rsbinder::Strong<dyn IFoo>>; N]> | Option<[Option<rsbinder::Strong<dyn IFoo>>; N]> | Option<[Option<rsbinder::Strong<dyn IFoo>>; N]> |
IBinder | &rsbinder::SIBinder | — | — | rsbinder::SIBinder | Option<rsbinder::SIBinder> |
@nullable IBinder | Option<&rsbinder::SIBinder> | — | — | Option<rsbinder::SIBinder> | Option<rsbinder::SIBinder> |
IBinder[] | &[rsbinder::SIBinder] | &mut Vec<Option<rsbinder::SIBinder>> | &mut Vec<rsbinder::SIBinder> | Vec<rsbinder::SIBinder> | Vec<rsbinder::SIBinder> |
@nullable IBinder[] | Option<&[Option<rsbinder::SIBinder>]> | &mut Option<Vec<Option<rsbinder::SIBinder>>> | &mut Option<Vec<Option<rsbinder::SIBinder>>> | Option<Vec<Option<rsbinder::SIBinder>>> | Option<Vec<Option<rsbinder::SIBinder>>> |
IBinder[N] | &[rsbinder::SIBinder; N] | &mut [Option<rsbinder::SIBinder>; N] | &mut [rsbinder::SIBinder; N] | [rsbinder::SIBinder; N] | [Option<rsbinder::SIBinder>; N] |
@nullable IBinder[N] | Option<&[Option<rsbinder::SIBinder>; N]> | &mut Option<[Option<rsbinder::SIBinder>; N]> | &mut Option<[Option<rsbinder::SIBinder>; N]> | Option<[Option<rsbinder::SIBinder>; N]> | Option<[Option<rsbinder::SIBinder>; N]> |
Attribute Macros§
- interface
- A binder interface from a plain Rust trait — the
.aidl-freeinterface.
Derive Macros§
- Binder
Enum - Parcel codec for a plain Rust enum, carried as its
#[repr(..)]scalar. - Parcelable
- Parcel codec for a plain Rust struct — the
.aidl-freeparcelable. - Service
Specific Error - Binder service-specific error codes for a plain Rust enum.