FAQ
- How to use a struct as a dictionary?
- 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 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.
zvariant
provides convenient macros for making this possible: SerializeDict
and
DeserializeDict
. You’ll also need to tell Type
macro to treat the type as a dictionary using
the signature
attribute. Here is a simple example:
use zbus::{
proxy, interface, fdo::Result,
zvariant::{DeserializeDict, SerializeDict, Type},
};
#[derive(DeserializeDict, SerializeDict, Type)]
// `Type` treats `dict` is an alias for `a{sv}`.
#[zvariant(signature = "dict")]
pub struct Dictionary {
field1: u16,
#[zvariant(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")),
})
}
}
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 since by default, zbus runs the ObjectServer
in its own internal runtime thread, it’s not
possible to use these APIs from interface methods. Moreover, by default zbus relies on async-io
crate to communicate with the bus, which uses its own thread.
Not to worry, though! You can enable tight integration between tokio and zbus by enabling tokio
feature:
# Sample Cargo.toml snippet.
[dependencies]
# Also disable the default `async-io` feature to avoid unused dependencies.
zbus = { version = "3", default-features = false, features = ["tokio"] }
Note: On Windows, the async-io
feature is currently required for UNIX domain socket support,
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_signal
refer toproxy
documentation. -
Use
proxy::Builder
to build your proxy instance and useproxy::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_properites
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, zvariant
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. zvariant
and zbus
provide 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>
forinterface
andproxy
property 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
zvariant::Value
can 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, zvariant
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 Value
or OwnedValue
if you want to encode different data in different fields. Here is a simple example:
use zbus::zvariant::{serialized::Context, to_bytes, Type, 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_value(),
f3: "hello",
};
let ctxt = Context::new_dbus(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::zvariant::{serialized::Context, to_bytes, Type, 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_dbus(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::zvariant::{serialized::Context, to_bytes, Type, LE};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Type, PartialEq, Debug)]
#[zvariant(signature = "s")]
enum StrEnum {
Variant1,
Variant2,
Variant3,
}
let ctxt = Context::new_dbus(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");