Sure. This is how the struct stands as it is now
rental! {
pub mod rent_manager {
use libudev;
#[rental]
pub struct Manager {
context: Box<libudev::Context>,
enumerator: Box<libudev::Enumerator<'context>>,
monitor_socket: libudev::MonitorSocket<'context>,
}
}
}
New looks like this:
fn new() -> Result<Self> {
rent_manager::Manager::try_new(
Box::new(libudev::Context::new()?),
|context| {
let mut enumerator = libudev::Enumerator::new(&context)?;
enumerator.match_subsystem(Self::UDEV_SUBSYSTEM)?;
enumerator.match_property("DEVTYPE", Self::UDEV_DEVTYPE)?;
Ok(Box::new(enumerator))
},
|_, context| {
let mut monitor = libudev::Monitor::new(context)?;
monitor.match_subsystem_devtype(Self::UDEV_SUBSYSTEM, Self::UDEV_DEVTYPE)?;
Ok(monitor.listen()?)
}
).map_err(|e: rental::TryNewError<Error,_>| e.into())
}
The general idea here is a simple application that automatically adds new wireless interfaces to wpa_supplicant when theyāre plugged into my laptop.
The problem thatās complicating all of this is that the udev library requiring a context.
Iāve run into this more often than not for Rust projects. Itās very common for a hardware API to require the creation of a context as the first step, which will provide enumeration of the devices, then the devices will provide objects. Similarly, network APIs will often go something like socket -> connection -> session -> objects. Since Rustās pattern is generally for a connection to take ownership of a socket, you immediately get hit with a self-referential struct problem. The alternative is to push the problem away by just taking a reference, but this then forces the problem onto the user of the library, who has to figure out how to keep a variable around that owns the item that the reference is to.
This has also come up with parsing and iterators. In parsing, you might be parsing some text data that is going to get split up into string slices that serde can do zero-copy deserialization with, but then you need to keep around the original text data. In iteration, you might want to take ownership of some data and retain a reference or slice back to the data.
I suspect you could force these to work out somehow with Rc and unsafe, but this is much less intuitive (and to varying degrees less performant).
EDIT: Also note that the program as shown above cannot compile, because I canāt access the middle variable with rental. Presumably Iād need to refactor such that the last variable is a struct or tuple which contains all the items which depend upon the context, which I havenāt gotten around to trying yet.
With proper self-referential structs, Iād expect that the above code could be refactored as follows:
pub struct Manager {
context: libudev::Context,
enumerator: libudev::Enumerator<'context>,
monitor_socket: libudev::MonitorSocket<'context>,
}
fn new() -> Result<Self> {
let mut manager = Manager {
context: libudev::Context::new()?),
enumerator: libudev::Enumerator::new(&context)?,
monitor: {
let mut monitor = libudev::Monitor::new(context)?;
monitor.match_subsystem_devtype(Self::UDEV_SUBSYSTEM, Self::UDEV_DEVTYPE)?;
monitor.listen()?
}
}
enumerator.match_subsystem(Self::UDEV_SUBSYSTEM)?;
enumerator.match_property("DEVTYPE", Self::UDEV_DEVTYPE)?;
Ok(manager)
}