Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

FAQ

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:

  1. Using the SerializeDict and DeserializeDict derive macros from zbus. This is the best option for simple cases.
  2. Using the Serialize and Deserialize derive macros from the serde crate. 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:

  1. 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.
  2. zbus::block_on must not be called from inside a task zbus is running, such as a method of an interface the connection serves, or a future inside another zbus::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 for emits_changed_signal refer to proxy documentation.

  • Use proxy::Builder to build your proxy instance and use proxy::Builder::uncached_properties method 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_properties method.

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:

  1. 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 of Option<T> in their D-Bus interface documentation.
  2. Currently it is not possible to use Option<T> for interface and proxy property methods.
  3. 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 as Option<T> and vice versa.
  4. While zbus::Variant can be converted into Option<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()) returns None for such a set.
  • The derived Debug prints the integer (Permissions(3)). To print the flags’ names instead (READ | WRITE), implement Debug with bitflags::parser::to_writer; it writes nothing for an empty set.
  • A flags type made with the struct form of bitflags! can go over the bus too, but without the Variant conversions: enable the bitflags crate’s serde feature, derive Serialize, Deserialize and Type inside the macro, and give Type the 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.