Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Special lifetime for downcasting ownership to reference. #1802

Closed
amosonn opened this issue Nov 27, 2016 · 7 comments
Closed

Special lifetime for downcasting ownership to reference. #1802

amosonn opened this issue Nov 27, 2016 · 7 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@amosonn
Copy link

amosonn commented Nov 27, 2016

Suppose I have a trait:

pub trait Summarize {
    type Summary;
    fn summarize(self) -> Self::Summary;
}

Now, for my type, summary is non-mutative, but does reference the original. So I have a struct:

struct MySummary<'a> {
    obj: &'a MyType,
    /* and other things */
}

and the helper summary function I implemented uses a reference:

impl MyType {
    fn do_summarize<'a>(&'a self) -> MySummary<'a> { /* do it */ }
}

Now I implement the Summarize trait for references:

impl<'a> Summarize for &'a MyType {
    type Summary = MySummary<'a>;
    fn summarize(self) -> MySummary<'a> { self.do_summarize() };
}

But I also want to implement it for owned copies. Currently, I can only reimplement everything with a struct that owns the object, a method that returns such a struct, and then implementation of the trait. What I would like to be able to do is:

impl Summarize for MyType {
    type Summary = MySummary<'owned>;
    fn summarize(self) -> MySummary<'owned> { self.do_summarize() };
}

with the special lifetime 'owned assuming the lifetime of the ownership, which is also the lifetime of the reference implicitly created by that last self.do_summarize() call. And similarly for downcasting from ownership to a &mut.

@SimonSapin
Copy link
Contributor

SimonSapin commented Nov 27, 2016

The memory representation of &'a MyType is a pointer, which is probably different from an owned, inline MyType. And a lifetime parameter cannot change the memory representation of your struct.

I think you’re looking for std::borrow::Cow, an enum that contains either a borrowed (with a &_ reference) thing or an owned thing. Replace &'a MyType in the struct MySummary<'a> definition with Cow<'a, MyType>.

In the second impl, use the MySummary<'static> and create an owned cow: Cow::Owned(self). The 'static lifetime is special and represents the entire duration of the program’s execution. Any reference inside MySummary<'static> must be valid that long. But since you’re not using a Cow::Borrowed variant, there isn’t any such reference.

@amosonn
Copy link
Author

amosonn commented Nov 27, 2016

Thanks! This does indeed solve the code duplication, so it might render this feature useless. I still think using a Cow might be an overkill (with some overhead), so I'll try to elaborate:

When do_summarize is called on a borrowed reference, the named lifetime 'a in its definition gets set to the lifetime of the said reference, and the same happens downstream for the struct MySummary<'a>. This means that when I have an obj: &'a MyType, I know that the return type of obj.do_summarize() will be MySummary<'a>.

On the other hand, when do_summarize is called on an owned object, a reference is still generated, since the signature of the function demands it. However, the lifetime of this implicit borrow is something I cannot name outside of the function. Therefore, if I have an obj: MyType, I cannot write out the return type of obj.do_summarize(), though it is still MySummary<'something>, holding a reference.

I will try to think of another example where this is more hindering.

@nrc nrc added the T-lang Relevant to the language team, which will review and decide on the RFC. label Nov 27, 2016
@burdges
Copy link

burdges commented Jan 14, 2017

As stated, there is a conflict in your problem statement : Your fn summarize(self) -> Self::Summary necessarily destroys self, which works fine if Self is a reference. If Self is MyType however then you cannot return a MySummary that references destroyed data. You could include an Cow<'a, MyType> in MySummary but that sounds needlessly complex.

I suspect you want a trait more like

pub trait Summarize {
    type Summary;
    fn summarize(&self) -> Self::Summary;
}

impl Summarize for MyType where {
    type Summary = MySummary; 
    fn summarize<'a>(&'a self) -> MySummary<'a> { .. }
}

At least the [https://doc.rust-lang.org/nomicon/lifetime-elision.html](lifetime elision) rules say the fn becomes the desired fn summarize<'a>(&'a self) -> MySummary<'a> { .. }. And maybe this says that type Summary = MySummary works too.

@amosonn
Copy link
Author

amosonn commented Jan 22, 2017

I checked, and it doesn't work, because that lifetime parameter is really required there in type Summary = MySummary.

The thing is, that my original fn summarize(self) -> Self::Summary doesn't destroy the data, it just moves the ownership. And I would like, for the sake of avoiding code duplication, to be able to "downgrade" my ownership into just a ref / mut ref. And then, the data still exists there until it runs out of scope, like with regular ownership, but it is only accessible via that ref.

@burdges
Copy link

burdges commented Jan 22, 2017

You'll notice this errors with x "does not live long enough" even though I explicitly require X: 'a

fn f<'a,X>(x: X) -> &'a X where X: 'a {
    &x
}

so the same applies to self in your fn summarize(self) -> Self::Summary. I'd expect LLVM to reuse the space self occupied when summarize returns.

I have used types with lifetime parameters used as associated types once before. I cannot find my code example right now, but I think exploring the Cow<'a, B> type's bound B: 'a helped me do it. You might explore some variations on :

impl<'a> Summarize for MyType where MyType: 'a {
    type Summary = MySummary<'a>;
    fn summarize(&'a self) -> MySummary<'a> { .. }
}

@dtolnay
Copy link
Member

dtolnay commented Nov 15, 2017

I believe this is going to be addressed by Generic Associated Types in rust-lang/rust#44265.

trait Summarize {
    type Summary<'a>;
    fn summarize(&'a self) -> Self::Summary<'a>;
}

struct MyType;

struct MySummary<'a> {
    obj: &'a MyType,
}

impl Summarize for MyType {
    type Summary<'a> = MySummary<'a>;
    fn summarize(&'a self) -> MySummary<'a> {
        MySummary { obj: self }
    }
}

For now the closest stable workaround would be:

trait Summarize<'a> {
    type Summary;
    fn summarize(&'a self) -> Self::Summary;
}

struct MyType;

struct MySummary<'a> {
    obj: &'a MyType,
}

impl<'a> Summarize<'a> for MyType {
    type Summary = MySummary<'a>;
    fn summarize(&'a self) -> MySummary<'a> {
        MySummary { obj: self }
    }
}

or:

trait Summarize {
    type Summary;
    fn summarize(self) -> Self::Summary;
}

struct MyType;

struct MySummary<'a> {
    obj: &'a MyType,
}

impl<'a> Summarize for &'a MyType {
    type Summary = MySummary<'a>;
    fn summarize(self) -> MySummary<'a> {
        MySummary { obj: self }
    }
}

@Centril
Copy link
Contributor

Centril commented Oct 7, 2018

Closing per notes in #1802 (comment).

@Centril Centril closed this as completed Oct 7, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests

6 participants