How can I have a lifetime dependency without a reference?

谁说我不能喝 提交于 2019-12-12 04:16:20

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!