#Lifetime issues

34 messages · Page 1 of 1 (latest)

tribal raptor
#

I have a struct like this:

pub struct GpuApp<'r> {
    inst: Nvml,
    device: nvml_wrapper::Device<'r>
}
```and am trying to create an instance of this struct.
I currently have:
```rs
        let inst = Nvml::init()?;
        let dev = inst.device_by_index(0)?;

        Ok(Self {
            rt,
            inst,
            device: dev,
        })
```However this fails to compile:
```rs
error[E0597]: `inst` does not live long enough
  --> src/app.rs:26:19
   |
12 | impl<'r> GpuApp<'r> {
   |      -- lifetime `'r` defined here
...
25 |         let inst = Nvml::init()?;
   |             ---- binding `inst` declared here
26 |         let dev = inst.device_by_index(0)?;
   |                   ^^^^ borrowed value does not live long enough
...
31 |             device: dev,
   |                     --- this usage requires that `inst` is borrowed for `'r`
32 |         })
33 |     }
   |     - `inst` dropped here while still borrowed

error[E0505]: cannot move out of `inst` because it is borrowed
  --> src/app.rs:30:13
   |
12 | impl<'r> GpuApp<'r> {
   |      -- lifetime `'r` defined here
...
25 |         let inst = Nvml::init()?;
   |             ---- binding `inst` declared here
26 |         let dev = inst.device_by_index(0)?;
   |                   ---- borrow of `inst` occurs here
...
30 |             inst,
   |             ^^^^ move out of `inst` occurs here
31 |             device: dev,
   |                     --- this usage requires that `inst` is borrowed for `'r`
```I'm confused as to why this happens, since the Nvml struct should live as long as my struct, so I don't get why it says it is dropped while still borrowed.
cinder forge
#

you can't have a reference to another struct field in your struct

#

maybe make device a method that returns the device from inst

tribal raptor
#

at the very least, device_by_index is an ffi method

chrome moat
#

I think it's worth looking into how expensive it is. Usually "by index" methods are quite fast, but I see that this is GPU code so there might be OS calls involved (and you probably don't want to repeatedly call this for semantic reasons)

#

The typical solutions to this are (in my personal order of preference):

  • Require that the user keeps inst alive themselves
  • Use the ouroboros crate to create the self-referential type
  • Use unsafe to do what ouroborous does yourself
  • Just leak the instance
tribal raptor
#

or the easiest method, make a static INST: Lazy<Nvml> = Lazy::new(|| Nvml::init().unwrap());, which is probably what I'll just go with

chrome moat
#

yeah that's basically the same as leaking

#

or just, keeping it alive on the stack

#

you don't gain much, other than dropping the lifetime

dawn rampart
#

the simplest and most straightforward option to this type of situation is to just put let inst = ... in your main() and borrow it elsewhere, i.e. put it outside all of your application's structs

#

statics or Box::leak() are the “and I will never do anything else than this, so it can be 'static” revision of that

#

the cleanest solution to this type of situation is for the library to provide shared ownership, i.e. the Device effectively contains an Arc<Nvml> instead of an &'a Nvml

chrome moat
dawn rampart
#

Yes.

#

Hence why I said "effectively"

chrome moat
#

yeah but not sure I would consider an Arc the cleanest solution if that's the case

#

rust really needs a nice, safe, built-in way for self-referential types

#

it's so common, and often idiomatic

dawn rampart
#

I agree, but another factor here is that in many of these cases you want multiple borrowers, in which case you need an Arc anyway.

chrome moat
#

but you can already borrow a shared reference multiple times

dawn rampart
#

Not if you put it in a self-referential struct.

#

You'd need to also use an Arc in that struct.

chrome moat
#

i mean, sure, there are many cases where self-referential types are the wrong solution

dawn rampart
#
struct GpuApp {
    inst: Arc<Nvml>,
    device: nvml_wrapper::Device<'self>,
}
#

you'd have to do this.

chrome moat
#

In my opinion, the library shouldn't force you to use reference counting or heap allocations. If the user wants to use an Arc to handle the lifetimes, sure

dawn rampart
#

and I agree with you that the language should support this, but given that it does not, I am of the opinion that libraries should ideally offer the option of shared-ownership in place of long-term borrowing.

chrome moat
#

is it feasible to offer both? i guess, if there's only a few types it is

#

or maybe with a feature flag, but this seems like a lot of effort for the library

dawn rampart
#

yes, hence why it would be better for the language to support it

chrome moat
#

so for the famous zipcrate I guess just having and arc variant would be good

dawn rampart
#

(an example of a lib type that does support it is tokio::sync::RwLock — async often has to avoid borrowing, so.)