Lifetime problem with libudev

Hi!

I am trying to write a function, which traverses a tree upwards, and searches for a specific attribute. The tree node with the matching attribute should be returned.

There is a type UdevDevice, which is a newtype wrapper around the libudev::Device type:

struct UdevDevice<'a>(Rc<libudev::Device<'a>>);

// Bunch of impls for newtype, like Clone, Deref, ...

The lifetime 'a of libudev::Device describes the lifetime of an associated libudev::Context, which must be alive while libudev::Device nodes exist.

pub struct Device<'a> {
    _context: &'a Context,
    device: *mut ::ffi::udev_device,
}

// Included Drop impl to show internal reference counting
impl<'a> Drop for Device<'a> {
    fn drop(&mut self) {
        unsafe {
            ::ffi::udev_device_unref(self.device);
        }
    }
}

/// A libudev context.
pub struct Context {
    udev: *mut ::ffi::udev
}

So here is my attempt at writing the search function (pseudo code):

fn main() {
    let context = /* ... */;
    let device = /* Get device from context + wrap in newtype */;

    let m = find_node(&device, |s| s == "usb", "subsystem");

    /* ... */ 
}

fn find_node<'a, P: Fn(&str) -> bool>(node: &UdevDevice<'a>, 
        predicate: P, attribute: &str)
        -> Option<UdevDevice<'a>> {
    if (predicate)(node.attribute_value(attribute)) {
        Some(node.clone())
    } else if let Some(i) = node.parent() {
        find_node(&i, predicate, attribute)
//                ^_______________________________________________________ 
//                | Can not use i, because i is borrowed from the current |
//                | function.                                             |
//                \-------------------------------------------------------/
    } else {
        None
    }
}

Essentially, the problem is,. that I can not proof to the compiler, that my UdevDevice node is just dependent on 'a.

Maybe the libudev crate is the problem here? I already considered trying to write a newtype, which implements Clone by using the libudev ffi API to udev_device_ref the node for each clone.

What is the signature of node.parent()?

impl<'a> UdevDevice<'a> {
    fn new(d: Device<'a>) -> Self {
        Self(Rc::new(d))
    }
    fn parent(&'a self) -> Option<Self> {
        self.0.parent().map(|n| Self::new(n))
    }
}

impl<'a> Device<'a> {

    /// Returns the parent of the device.
    pub fn parent(&self) -> Option<Device>;

}

And there’s the issue, parent is bound to the local variable, not 'a in find_node, instead if you want it to have the same lifetime as its child, you should make the signature of parent be

fn parent(&self) -> Option<Self>

Tried that first, does not compile:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
   --> src/backend/linux_hidraw/udev_enumerator.rs:134:16
    |
134 |         self.0.parent().map(|n| Self::new(n))
    |                ^^^^^^
    |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 133:5...
   --> src/backend/linux_hidraw/udev_enumerator.rs:133:5
    |
133 | /     fn parent(&self) -> Option<Self> {
134 | |         self.0.parent().map(|n| Self::new(n))
135 | |     }
    | |_____^
note: ...so that reference does not outlive borrowed content
   --> src/backend/linux_hidraw/udev_enumerator.rs:134:9
    |
134 |         self.0.parent().map(|n| Self::new(n))
    |         ^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 129:6...
   --> src/backend/linux_hidraw/udev_enumerator.rs:129:6
    |
129 | impl<'a> UdevDevice<'a> {
    |      ^^
    = note: ...so that the expression is assignable:
            expected std::option::Option<backend::linux_hidraw::udev_enumerator::UdevDevice<'a>>
               found std::option::Option<backend::linux_hidraw::udev_enumerator::UdevDevice<'_>>

Okay, got this one to work, but the initial problem still persists:

impl<'a> UdevDevice<'a> {
    fn new(d: Device<'a>) -> Self {
        Self(Rc::new(d))
    }
    fn parent(&self) -> Option<UdevDevice> {
        self.0.parent().map(|n| Self::new(n))
    }
}

Huh, I didn’t notice the lifetime elision there, can you change that to

pub fn parent(&self) -> Option<Device<'a>>;

I have to change that in the libudev crate. Working on it…

That’s the same as

fn parent(&'a self) -> Option<Self> { self.0.parent().map(|n| Self::new(n)) }

To see why, lookup lifetime elision

Wait, are you talking about the impl of libudev::Device?

No, UdevDevice

That’s problematic

That does not compile. .parent() can not infer the lifetime for the returning ->Option<UdevDevice<'a>>

I think I will drop the libudev crate, and just do it on my own.

That’s because of the signature for libudev::Device::parent is too conservative.

Yes, thanks. Just realized that. I had a similar problem once with an iterator impl, and I could not figure out what was wrong, until I took a better look at the library I was using.

I will try to contact the libudev crate developer, and see if I an submit a PR.

Tkanks again!

1 Like