FAQ
- How to use a struct as a dictionary?
- How do I use zbus from synchronous code?
- Why do async tokio API calls from interface methods not work?
- I’m experiencing hangs, what could be wrong?
- Why aren’t property values updating for my service that doesn’t notify changes?
- How do I use
Option<T>with zbus? - How do enums work?
- How do I use bit flags?
- How do I connect over VSOCK?
How to use a struct as a dictionary?
Since the use of a dictionary, specifically one with strings as keys and variants as value (i-e
a{sv}) is very common in the D-Bus world and use of HashMaps isn’t as convenient and type-safe as
a struct, you might find yourself wanting to use a struct as a dictionary.
It’s possible to do so, using either of the following methods:
- Using the
SerializeDictandDeserializeDictderive macros fromzbus. This is the best option for simple cases. - Using the
SerializeandDeserializederive macros from theserdecrate. This is the best option for more complex cases, where you need more fine-grained control over the serialization and/or deserialization process.
Here is a simple example using SerializeDict and DeserializeDict:
use zbus::{
proxy, interface, fdo::Result,
DeserializeDict, SerializeDict, Type,
};
#[derive(DeserializeDict, SerializeDict, Type)]
// `Type` treats `dict` is an alias for `a{sv}`.
#[zbus(signature = "dict")]
pub struct Dictionary {
field1: u16,
#[zbus(rename = "another-name")]
field2: i64,
optional_field: Option<String>,
}
#[proxy(
interface = "org.zbus.DictionaryGiver",
default_path = "/org/zbus/DictionaryGiver",
default_service = "org.zbus.DictionaryGiver",
)]
trait DictionaryGiver {
fn give_me(&self) -> Result<Dictionary>;
}
struct DictionaryGiverInterface;
#[interface(interface = "org.zbus.DictionaryGiver")]
impl DictionaryGiverInterface {
fn give_me(&self) -> Result<Dictionary> {
Ok(Dictionary {
field1: 1,
field2: 4,
optional_field: Some(String::from("blah")),
})
}
}
Now let’s say you want to grab all the extra entries that are not explicitly defined in the
struct. You can not do that with DeserializeDict but you can with serde::Deserialize:
use std::collections::HashMap;
use zbus::{as_variant::{self, optional}, OwnedVariant, Type};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Type)]
#[zbus(signature = "dict")]
pub struct Dictionary {
#[serde(with = "as_variant")]
field1: u16,
#[serde(rename = "another-name", with = "as_variant")]
field2: i64,
#[serde(
with = "optional",
skip_serializing_if = "Option::is_none",
default,
)]
optional_field: Option<String>,
#[serde(flatten)]
the_rest: HashMap<String, OwnedVariant>,
}
Since the fields have to be transformed from/into zbus::Variant, make sure to use the
with attribute with the appropriate helper module from the zbus::as_variant module.
Moreover, since D-Bus does not have a concept of nullable types, it’s important to ensure that
skip_serializing_if and default attributes are used for optional fields. Fortunately, you can
make use of the default container attribute if your struct can implemented the Default trait:
use zbus::{as_variant::{self, optional}, Type};
use serde::{Deserialize, Serialize};
#[derive(Default, Deserialize, Serialize, Type)]
#[zbus(signature = "dict")]
#[serde(default)]
pub struct Dictionary {
#[serde(with = "as_variant")]
field1: u16,
#[serde(with = "optional", skip_serializing_if = "Option::is_none")]
optional_field1: Option<i64>,
#[serde(with = "optional", skip_serializing_if = "Option::is_none")]
optional_field2: Option<String>,
}
Nested dictionaries
D-Bus interfaces frequently return dicts whose values are themselves dicts — for example
a{sa{sv}} (the body of org.freedesktop.DBus.ObjectManager.GetManagedObjects and similar APIs).
You can mirror that shape by nesting one *Dict struct inside another. The outer struct’s
signature advertises the nested form; its fields name the outer keys and have an inner *Dict
struct as their type:
use zbus::{DeserializeDict, SerializeDict, Type};
#[derive(DeserializeDict, SerializeDict, Type, PartialEq, Debug, Default, Clone)]
#[zbus(signature = "a{sv}", rename_all = "PascalCase")]
struct Adapter {
address: Option<String>,
name: Option<String>,
powered: bool,
}
#[derive(DeserializeDict, SerializeDict, Type, PartialEq, Debug, Default, Clone)]
#[zbus(signature = "a{sv}", rename_all = "PascalCase")]
struct Media {
supported_features: Vec<String>,
#[zbus(rename = "SupportedUUIDs")]
supported_uuids: Vec<String>,
}
#[derive(DeserializeDict, SerializeDict, Type, PartialEq, Debug, Default)]
#[zbus(signature = "a{sa{sv}}")]
struct Interfaces {
#[zbus(rename = "org.bluez.Adapter1")]
adapter: Option<Adapter>,
#[zbus(rename = "org.bluez.Media1")]
media: Media,
}
The same pattern works for a{oa{sv}} (object-path keys) and composes to deeper nesting — the
outer derive defers each field to its own Serialize/Deserialize impl rather than wrapping it
as a variant.
How do I use zbus from synchronous code?
zbus’s API is async, but a program does not need an async runtime of its own to use it.
zbus::block_on runs a future to completion on the calling thread, so put the D-Bus code
inside one call:
use zbus::{Connection, Result, proxy};
#[proxy(
interface = "org.freedesktop.systemd1.Manager",
default_service = "org.freedesktop.systemd1",
default_path = "/org/freedesktop/systemd1"
)]
trait SystemdManager {
#[zbus(property)]
fn log_level(&self) -> Result<String>;
}
fn main() -> Result<()> {
zbus::block_on(async {
let connection = Connection::session().await?;
let manager = SystemdManagerProxy::new(&connection).await?;
println!("LogLevel: {}", manager.log_level().await?);
Ok(())
})
}
Everything shown in the other chapters works the same way inside that future, and the program
depends on zbus alone, plus futures-util if it reads signals or property changes from a
stream. With the default-rt feature, on by default, the same thread also runs zruntime’s
scheduler and I/O reactor in between polls of the future, and a program that runs block_on on
several threads drives their connections in parallel; with the tokio feature, Tokio’s runtime
runs them instead. A handful of blocking system calls — a DNS lookup, a nonce-file read, a
peer-credential lookup — run on a short-lived worker thread of their own, so they do not tie up
the connection’s thread.
Two rules follow from the connection’s work running on the calling thread:
- The future must not block the thread waiting for the connection. A synchronous wait for a reply, or a busy loop until a signal arrives, never finishes, because the work it waits for can only run in between polls of the future.
zbus::block_onmust not be called from inside a task zbus is running, such as a method of an interface the connection serves, or a future inside anotherzbus::block_on. Such a call panics, because it would be waiting for the very thread it is on. Nor should it be called from a task of another runtime; the function’s documentation has the details.
Why do async tokio API calls from interface methods not work?
Many of the tokio (and tokio-based) APIs assume the tokio runtime to be driving the async
machinery, and by default a connection’s tasks and I/O run on zruntime, on the thread inside
zbus::block_on (the zruntime backend), which is not a tokio runtime. So it’s not
possible to use these APIs from interface methods.
Not to worry, though! You can enable tight integration between tokio and zbus by enabling tokio
feature:
# Sample Cargo.toml snippet.
[dependencies]
zbus = { version = "6", features = ["tokio"] }
With both features enabled, the backend is chosen once per connection, when it is built: Tokio when
a Tokio runtime is current on the thread that builds it, zruntime otherwise. This keeps the
features additive, so an application that relies on zruntime keeps working even when
another crate in the workspace enables zbus’s tokio feature. To leave the zruntime backend out
of the build, disable default features and list what you use instead, e.g.
default-features = false, features = ["tokio", "proxy", "service"]. The zruntime crate stays
in the dependency tree for the Event and broadcast channels zbus’s own types build on, but none
of its runtime is built.
This per-connection selection only matters for a program that has an async runtime of its own. A
program without one uses zbus::block_on instead. Without the tokio feature, that call and the
connection built inside it use zruntime; with it, the call runs inside a Tokio
runtime, so a connection built inside it always uses Tokio. That runtime is single-threaded, so
the connection’s work only runs while a thread is inside zbus::block_on; the
tokio-multithread feature (which enables tokio) makes it a multi-threaded runtime instead.
Note that zbus::block_on must not be called from inside a task zbus is running, such as an
interface method: it panics there.
Note: On Windows, a connection that ends up on Tokio cannot use a Unix domain socket, even
when the default-rt feature is also on; give it a TCP or autolaunch: address instead, or
build it from a thread with no Tokio runtime current so it picks zruntime. See the
corresponding tokio issue on GitHub.
I’m experiencing hangs, what could be wrong?
There are typically two reasons this can happen with zbus:
1. A interface method that takes a &mut self argument is taking too long
Simply put, this is because of one of the primary rules of Rust: while a mutable reference to a resource exists, no other references to that same resource can exist at the same time. This means that before the method in question returns, all other method calls on the providing interface will have to wait in line.
A typical solution here is use of interior mutability or launching tasks to do the actual work
combined with signals to report the progress of the work to clients. Both of these solutions
involve converting the methods in question to take &self argument instead of &mut self.
2. A stream (e.g SignalStream) is not being continuously polled
Please consult MessageStream documentation for details.
Why aren’t property values updating for my service that doesn’t notify changes?
A common issue might arise when using a zbus proxy is that your proxy’s property values aren’t updating. This is due to zbus’ default caching policy, which updates the value of a property only when a change is signaled, primarily to minimize latency and optimize client request performance. By default, if your service does not emit change notifications, the property values will not update accordingly.
However, you can disabling caching for specific properties:
-
Add the
#[zbus(property(emits_changed_signal = "false"))]annotation to the property for which you desire to disable caching on. For more information about all the possible values foremits_changed_signalrefer toproxydocumentation. -
Use
proxy::Builderto build your proxy instance and useproxy::Builder::uncached_propertiesmethod to list all properties you wish to disable caching for. -
In order to disable caching for either type of proxy use the
proxy::Builder::cache_propertiesmethod.
How do I use Option<T> with zbus?
While Option<T> is a very commonly used type in Rust, there is unfortunately no concept of a
nullable-type in the D-Bus protocol. However, there are two ways to simulate it:
1. Designation of a special value as None
This is the simplest way to simulate Option<T>. Typically the
default value for the type in question is a good choice. For example the empty string ("") is
often used as None for strings and string-based types. Note however that this solution can not
work for all types, for example bool.
Since this is the most widely used solution in the D-Bus world and is even used by the D-Bus
standard interfaces, zbus::wire provides a custom type for this, Optional<T> that
makes it super easy to simulate a nullable type, especially if the contained type implements the
Default trait.
2. Encoding as an array (a?)
The idea here is to represent None case with 0 elements (empty array) and the Some case with 1
element. zbus provides an option-as-array Cargo feature, which when enabled, allows the
(de)serialization of Option<T>. Unlike the previous solution, this solution can be used with
all types. However, it does come with some caveats and limitations:
- Since the D-Bus type signature does not provide any hints about the array being in fact a
nullable type, this can be confusing for users of generic tools like
d-feet. It is therefore highly recommended that service authors document each use ofOption<T>in their D-Bus interface documentation. - Currently it is not possible to use
Option<T>forinterfaceandproxyproperty methods. - Both the sender and receiver must agree on use of this encoding. If the sender sends
T, the receiver will not be able to decode it successfully asOption<T>and vice versa. - While
zbus::Variantcan be converted intoOption<T>, the reverse is currently not possible.
Due to these limitations, option-as-array feature is not enabled by default and must be explicitly
enabled.
Note: We hope to be able to remove #2 and #4, once specialization lands in stable Rust.
How do enums work?
By default, zbus::wire encodes an unit-type enum as a u32, denoting the variant index. Other
enums are encoded as a structure whose first field is the variant index and the second one are the
variant’s field(s). The only caveat here is that all variants must have the same number and types
of fields. Names of fields don’t matter though. You can make use of Variant or OwnedVariant
if you want to encode different data in different fields. Here is a simple example:
use zbus::{Type, wire::{serialized::Context, to_bytes, LE}};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Type, PartialEq, Debug)]
enum Enum<'s> {
Variant1 { field1: u16, field2: i64, field3: &'s str },
Variant2(u16, i64, &'s str),
Variant3 { f1: u16, f2: i64, f3: &'s str },
}
let e = Enum::Variant3 {
f1: 42,
f2: i64::MAX,
f3: "hello",
};
let ctxt = Context::new(LE, 0);
let encoded = to_bytes(ctxt, &e).unwrap();
let decoded: Enum = encoded.deserialize().unwrap().0;
assert_eq!(decoded, e);
Enum encoding can be adjusted by using the serde_repr crate and by annotating the
representation of the enum with repr:
use zbus::{Type, wire::{serialized::Context, to_bytes, LE}};
use serde_repr::{Serialize_repr, Deserialize_repr};
#[derive(Deserialize_repr, Serialize_repr, Type, PartialEq, Debug)]
#[repr(u8)]
enum UnitEnum {
Variant1,
Variant2,
Variant3,
}
let ctxt = Context::new(LE, 0);
let encoded = to_bytes(ctxt, &UnitEnum::Variant2).unwrap();
let e: UnitEnum = encoded.deserialize().unwrap().0;
assert_eq!(e, UnitEnum::Variant2);
Unit enums can also be (de)serialized as strings:
use zbus::{Type, wire::{serialized::Context, to_bytes, LE}};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Type, PartialEq, Debug)]
#[zbus(signature = "s")]
enum StrEnum {
Variant1,
Variant2,
Variant3,
// Catch-all that preserves the raw value for any variant we don't know about.
#[serde(untagged)]
Other(String),
}
let ctxt = Context::new(LE, 0);
let encoded = to_bytes(ctxt, &StrEnum::Variant2).unwrap();
let e: StrEnum = encoded.deserialize().unwrap().0;
assert_eq!(e, StrEnum::Variant2);
let s: &str = encoded.deserialize().unwrap().0;
assert_eq!(s, "Variant2");
How do I use bit flags?
D-Bus has no type for bit flags: a set of flags goes over the bus as the unsigned integer with
their bits set, usually a u32. zbus needs no special support for them either. Declare the set as
a newtype over that integer, derive zbus’s traits on it as on any other type of yours, and have
the bitflags crate generate the flags and the methods and operators to work with them, through
the impl form of its bitflags! macro:
use serde::{Deserialize, Serialize};
use zbus::{
Basic, OwnedVariant, Type, Variant, proxy,
wire::{serialized::Context, to_bytes, LE},
};
#[derive(
Clone, Copy, Debug, Default, PartialEq, Eq, Hash,
Serialize, Deserialize, Type, Variant, OwnedVariant,
)]
pub struct Permissions(u32);
bitflags::bitflags! {
impl Permissions: u32 {
const READ = 0x1;
const WRITE = 0x2;
const EXECUTE = 0x4;
}
}
// Only needed to use the flags as the key of a dictionary.
impl Basic for Permissions {
const SIGNATURE_CHAR: char = u32::SIGNATURE_CHAR;
const SIGNATURE_STR: &'static str = u32::SIGNATURE_STR;
}
#[proxy(
interface = "org.zbus.Files",
default_path = "/org/zbus/Files",
default_service = "org.zbus.Files",
)]
trait Files {
fn open(&self, path: &str, permissions: Permissions) -> zbus::Result<()>;
#[zbus(property)]
fn default_permissions(&self) -> zbus::Result<Permissions>;
}
// On the bus, the flags are the integer.
assert_eq!(Permissions::SIGNATURE, "u");
let ctxt = Context::new(LE, 0);
let flags = Permissions::READ | Permissions::WRITE;
let encoded = to_bytes(ctxt, &flags).unwrap();
assert_eq!(encoded.bytes(), to_bytes(ctxt, &0x3u32).unwrap().bytes());
let decoded: Permissions = encoded.deserialize().unwrap().0;
assert_eq!(decoded, flags);
Type gives the flags the signature of the integer, and the serde derives encode them as it.
Variant and OwnedVariant convert them to and from a Variant holding the integer, which a
property needs, since property values travel as variants. Default makes the empty set the null
value of an Optional<T>.
A few things to be aware of:
- Decoding keeps bits that none of the flags define, so a peer can set flags your code does not
know yet without breaking it. Where you would rather refuse them,
Permissions::from_bits(flags.bits())returnsNonefor such a set. - The derived
Debugprints the integer (Permissions(3)). To print the flags’ names instead (READ | WRITE), implementDebugwithbitflags::parser::to_writer; it writes nothing for an empty set. - A flags type made with the
structform ofbitflags!can go over the bus too, but without theVariantconversions: enable thebitflagscrate’sserdefeature, deriveSerialize,DeserializeandTypeinside the macro, and giveTypethe signature with#[zbus(signature = "u")], as the struct wraps an internal type the macro generates, which implements none of zbus’s traits.
How do I connect over VSOCK?
zbus has no transport for VSOCK, which links a virtual machine to its host, as D-Bus defines no
address for it. A connection still runs over a VSOCK stream that you open yourself, such as the
VsockStream of the tokio-vsock crate: implement Socket for it and hand it to
connection::Builder::socket. The Socket documentation has an implementation for any stream
of Tokio’s, VsockStream among them. As a VSOCK peer has no credentials to check, that
implementation asks for the ANONYMOUS authentication mechanism, which a bus at the other end has
to allow.