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

Establishing a connection

The first thing you will have to do is to connect to a D-Bus bus or to a D-Bus peer. This is the entry point of the zbus API.

Connection to the bus

To connect to the session bus (the per-user bus), simply call Connection::session(). It returns an instance of the connection (if all went well). Similarly, to connect to the system bus (to communicate with services such as NetworkManager, BlueZ or PID1), use Connection::system().

Moreover, it can be converted to a MessageStream that implements futures::stream::Stream, which can be used to conveniently receive messages, for the times when low-level API is more appropriate for your use case.

Note: it is common for a D-Bus library to provide a “shared” connection to a bus for a process: all session() share the same underlying connection for example. At the time of this writing, zbus doesn’t do that.

Note: on macOS, there is no standard implicit way to connect to a session bus. zbus provides opt-in compatibility to the Launchd session bus discovery mechanism via the launchctl getenv feature. The official dbus installation method via Homebrew provides a session bus installation, utilizing macOS LaunchAgents feature. By default, zbus consumes an address for a bus connection that is provided via launchctl getenv DBUS_LAUNCHD_SESSION_BUS_SOCKET command output.

Using a custom bus address

You may also specify a custom bus with connection::Builder::address which takes a D-Bus address as specified in the specification.

Note: the unixexec: and ibus: transports are only available when the unixexec and ibus cargo features of zbus, respectively, are enabled.

Peer to peer connection

Peer-to-peer connections are bus-less1, and the initial handshake protocol is a bit different. There is the notion of client & server endpoints, but that distinction doesn’t matter once the connection is established (both ends are equal, and can send any messages).

For example to create a bus-less peer-to-peer connection on Unix, you can do:

#[tokio::main]
async fn main() -> zbus::Result<()> {
#[cfg(unix)]
{
use std::os::unix::net::UnixStream;
use zbus::{connection::Builder, Guid};

let guid = Guid::generate();
let (p0, p1) = UnixStream::pair().unwrap();
#[allow(unused)]
let (client_conn, server_conn) = futures_util::try_join!(
    // Client
    Builder::unix_stream(p0).p2p().build(),
    // Server
    Builder::unix_stream(p1).server(guid).p2p().build(),
)?;
}

Ok(())
}

unix_stream takes a std::os::unix::net::UnixStream, whichever runtime the connection ends up on. A stream of another kind is handed over as the socket it wraps: into_std() for a tokio::net::UnixStream.

Note: the p2p and server methods of connection::Builder are only available when p2p cargo feature of zbus is enabled.

Runtimes

A connection takes its readiness notifications, timers, spawned tasks and blocking work from one runtime, chosen once when it is built. A program with no async runtime of its own drives it with zbus::block_on; see the FAQ. A program built on Tokio turns on the tokio feature, and a connection built while a Tokio runtime is current runs on it. Any other executor can use the zruntime backend below as it is, with no integration needed: a connection built outside a zbus::block_on call is run by that backend’s own helper thread. Builder::runtime is there for an application that wants to hand a connection a runtime of its own instead, not something every other executor needs. A connection’s async locks come from zruntime whichever runtime it runs on, except on a Tokio build, which uses Tokio’s locks instead.

Built-in backends

With the default-rt cargo feature (a default feature), a connection runs on zruntime, one per thread that runs zbus::block_on, depending on no general-purpose async executor crate. The thread that drives it is the one inside zbus::block_on: between two polls of the future handed to it, that thread runs the tasks, sockets and timers of every zruntime connection built in it — apart from a handful of blocking system calls (a DNS lookup, a nonce-file read, a peer-credential lookup) that use a short-lived worker thread of their own instead. Two threads that each call zbus::block_on drive their own connections, in parallel. A thread’s runtime serves every connection built inside its zbus::block_on calls, so a task of one connection that runs long delays the others’ I/O until it yields, and a connection used from another thread is still driven by the thread that built it. A connection built on a thread that is in no zbus::block_on at the time — one that some other executor polls — has no thread of its own to look to, and goes instead on a single runtime the whole process shares with every other such connection. Only where a connection has work and no thread is inside zbus::block_on — it is polled from some other executor, or a call returned with the connection alive — does zbus start a helper thread, which leaves once nothing is left to run. If the tokio feature is also enabled and a Tokio runtime is current on the thread that builds the connection, it runs on Tokio instead. With only tokio enabled, a connection always runs on the Tokio runtime that is current when it is built; building one from a thread with no such runtime fails with Error::Unsupported.

A build with neither feature depends on no tokio crate. It still depends on the zruntime crate, whose Event, async locks, broadcast channels and unblock (the thread that blocking work runs on by default) zbus uses, but not with its runtime feature, so none of zruntime’s runtime is built. Every connection in the build needs an explicit runtime, given through Builder::runtime.

Supplying your own runtime

Builder::runtime takes any implementation of traits::Runtime, for an application whose event loop is neither backend above:

#![allow(unused)]
fn main() {
use zbus::{Connection, Result, connection::Builder, runtime::traits::Runtime};

async fn connect(runtime: impl Runtime) -> Result<Connection> {
    Builder::session().runtime(runtime).build().await
}
}

The runtime::traits module documents what an implementation supplies.

Bringing your own socket

Besides an address, a connection can be built directly over a socket you already have. Builder::unix_stream and Builder::tcp_stream each take an owned, platform-native stream and register it on the connection’s runtime; a stream of another kind is handed over as the socket it wraps, such as into_std() for a Tokio stream. Builder::socket and Builder::authenticated_socket take any implementation of Socket, for any other transport, such as a VSOCK stream, an in-process channel or a tunnel of your own. The Socket documentation has how to write one.


  1. Unless you implemented them, none of the bus methods will exist. ↩