问题
I'm wrapping a C library that has context
and device
objects. The context
object needs to outlive the device
object because the device
keeps an internal reference to the context
.
To express this, I use a PhantomData
field in the Device
wrapper:
use std::marker::PhantomData;
struct Context;
impl Context {
fn open_device<'a>(&'a self) -> Device<'a> {
Device { _context: PhantomData, }
}
}
struct Device<'a> {
_context: PhantomData<&'a Context>,
}
Now, in my client code, I would like to have a struct that holds both the Context
and Device
objects. But because the Device
holds a reference (a fake one) to the Context
, I'm not able to do this (see this question). But this is an unnecessary restriction because the Device
struct doesn't actually contain a reference to the Context
.
So how can I tie the lifetime of the Device
to the lifetime of the Context
in a way that would allow me to hold both of them in a struct?
回答1:
As of today, Rust is not able to express a lifetime that refers to an object defined in the same struct, so it's impossible to declare the proper lifetime for the Device
member.
Instead of storing both objects directly in the struct, could you instead store references to these structs? (This won't work if you want to make a function that returns that struct.)
struct Both<'a: 'b, 'b> {
context: &'a Context,
device: &'b Device<'a>,
}
Another option is to declare the Device
as having a Context
with 'static
lifetime (I'm using 'static
because it's the only lifetime with a name), but always using a method to "cast" the Device
into one with appropriate lifetime parameters, rather than using the field directly.
struct Both {
context: Context,
device: Device<'static>,
}
impl Both {
fn get_device<'a>(&'a self) -> &'a Device<'a> {
&self.device
}
}
Actually, thanks to lifetime elision, it is not necessary to specify the lifetime parameters explicitly on get_device
(likewise for your open_device
method, by the way):
impl Both {
fn get_device(&self) -> &Device {
&self.device
}
}
There's just a gotcha: you need to use transmute
to lie about the device's lifetime parameter when you initialize the struct.
use std::mem;
fn get_both() -> Both {
let context = Context; // could also be a parameter
let device: Device<'static> = unsafe { mem::transmute(context.open_device()) };
Both {
context: context,
device: device,
}
}
You might also want to consider having the Both
struct containing a Device
that has no lifetime parameter, then wrapping that in another struct that does have a lifetime parameter and returning that from a method.
use std::marker::PhantomData;
use std::mem;
struct Context;
impl Context {
fn open_device(&self) -> Device {
Device
}
}
struct Device;
struct DeviceWrapper<'a> {
_context: PhantomData<&'a Context>,
device: &'a Device,
}
struct Both {
context: Context,
device: Device,
}
impl Both {
fn get_device(&self) -> DeviceWrapper {
DeviceWrapper { _context: PhantomData, device: &self.device }
}
}
fn get_both() -> Both {
let context = Context;
let device = context.open_device();
Both {
context: context,
device: device,
}
}
(In fact, the DeviceWrapper
probably doesn't need the _context
member, since the DeviceWrapper
's lifetime is tied to that of the Both
already.)
来源:https://stackoverflow.com/questions/33202211/how-can-i-have-a-lifetime-dependency-without-a-reference