Implementing Deref to return the "dependent" from a self-referential struct

I'm implementing a zero-copy parser for the KeyFile format as used by glib2 (based on the XDG Desktop Entry specification - for example, they are the format of .desktop files on Linux).

I currently have a KeyFile<'a> struct that represents a "parsed" KeyFile, and it does not own its data, but I would like to also make an "owned" type available to make the API a bit more ergonomic for users. I'm using self_cell, and this appears to work as expected:

pub struct OwnedKeyFile {
    inner: OwnedKeyFileInner,
}

self_cell! {
    struct OwnedKeyFileInner {
        owner: String,
        #[covariant]
        dependent: KeyFile,
    }
}

However, I definitely don't want to re-implement all the APIs from KeyFile on OwnedKeyFile, which would only have one method (to create an instance of the type), and for all other methods I would ideally want to delegate everything to the inner KeyFile. This sounds like a prime use case for implementing Deref, if I am reading the documentation for that trait correctly.

However, I'm currently struggling to actually implement the Deref trait here, since I cannot specify the necessary lifetime parameters:

impl Deref for OwnedKeyFile {
    type Target = KeyFile<'a>;
    //                   ^^^^
    //                   use of undeclared lifetime name `'a`

    fn deref(&self) -> &Self::Target {
        self.inner.borrow_dependent()
    }
}

Even if I "force" things by adding a lifetime parameter to OwnedKeyFile artificially with PhantomData, I get a different error - apparently, .borrow_dependent() returns a &KeyFile with a different lifetime than that of &self (which is really strange, maybe that is a bug in self_cell?).

From what I can tell, what I'm trying to do here is "safe" (i.e. "borrowing" a OwnedKeyFile as &KeyFile), because the same code compiles fine if I don't try to implement Deref at the same time:

impl OwnedKeyFile {
    fn deref(&self) -> &KeyFile {
        self.inner.borrow_dependent()
    }
}

Is it just not possible to implement Deref here because I cannot name the lifetime of the associated Target type, or am I missing something here?

I have published the whole project on GitHub if more context is required.

Well, it returns a type with two lifetimes, a &'s KeyFile<'s> from &'s self.

That's a &'s T* where for every lifetime 's, T* is a distinct type (KeyFile<'s1> and KeyFile<'s2> are distinct types, unless 's1 == 's2).

But Deref demands a &'s U where U is always the same type.

The difference between Deref::deref and your method are more apparent if you #[deny(elided_lifetimes_in_paths)].

impl OwnedKeyFile {
    // This looked like a reference to a single concrete type before,
    // but now you can see it's a reference to something which has a
    // lifetime parameter -- not a single concrete type
    fn deref(&self) -> &KeyFile<'_> {
        self.inner.borrow_dependent()
    }
}

Probably forcing the issue would be unsound (messing with self-referencial lifetimes usually is), but I'm no expert of that crate.

This is not a direct answer to your question, but one way to create a structure that can either own or borrow its data is via the Cow enum:

use std::borrow::Cow;

#[derive(Clone)]
struct Bar {
    baz: i32,
}

struct Foo<'a> {
    bar: Cow<'a, Bar>,
}

impl<'a> Foo<'a> {
    fn bob(&self) {
        println!("{}", self.bar.as_ref().baz);
    }
}

fn main() {
    let bar = Bar { baz: 32 };
    let foo_ref = Foo { bar: Cow::Borrowed(&bar) };
    foo_ref.bob();

    let foo_owned = Foo { bar: Cow::Owned(Bar { baz: 42 }) };
    foo_owned.bob();
}

This requires your type to be Clone-able. If this isn't possible, you can create a Cow-like enum that impls AsRef:

struct Bar {
    baz: i32,
}

enum MaybeOwned<'a> {
    Owned(Bar),
    Borrowed(&'a Bar),
}

impl<'a> AsRef<Bar> for MaybeOwned<'a> {
    fn as_ref(&self) -> &Bar {
        match self {
            MaybeOwned::Owned(val) => &val,
            MaybeOwned::Borrowed(val) => val,
        }
    }
}

struct Foo<'a> {
    bar: MaybeOwned<'a>,
}

impl<'a> Foo<'a> {
    fn bob(&self) {
        println!("{}", self.bar.as_ref().baz);
    }
}

fn main() {
    let bar = Bar { baz: 32 };
    let foo_ref = Foo { bar: MaybeOwned::Borrowed(&bar) };
    foo_ref.bob();

    let foo_owned = Foo {
        bar: MaybeOwned::Owned(Bar { baz: 42 }),
    };
    foo_owned.bob();
}

Yup, I can do that by making the owner field of OwnedKeyFile be of type Cow<'a, str> instead of String, but that doesn't help me because I still can't implement Deref (or even DerefMut) for that wrapper type, making the inner type's methods inaccessible unless I copy-paste all the impls ...

Ah, that makes sense, but doesn't really help me :frowning: I'm starting to think that implementing Deref would be really nice here, but isn't possible in safe Rust :face_holding_back_tears: and I'm not willing to use unsafe { std::mem::transmute(foo) } just to get the lifetimes to match up even if I think it's correct xD

If you implement the pattern I described, you would combine OwnedKeyFile and KeyFile into one type where the data behind an &'a reference in KeyFile would be moved into a Cow. All of the methods on the new type (let's call it CowKeyFile) would use self.cow_data.field_or_method internally to access the owned or borrowed data. All of the methods would be implemented on the combined CowKeyFile type and there would be no Deref impl at all.

By the way, in my example above in the bob fn I wrote self.bar.as_ref().baz, but that can be simplified to just self.bar.baz, because Cow implements Deref.

A related pattern is to make a struct which is generic over the underlying storage of data (KeyFile<S: AsRef<T>>, where S is any type that can provide an &T on demand - owned or borrowed).

Hm ... this sounds interesting, thank you! I might get back to doing something like that in the future.

For now, I've done something simpler that does almost everything I wanted - taking inspiration from Cow , I implemented a into_owned() method on my type, which returns a KeyFile<'static> by recursively cloning all contained data.