A puzzle and why generics are not generic enough right now

Pretty much every single suggestion I’ve seen in this area has too little plumbing to be sound wrt lifetime inference and borrow-checking.

However, the “revival” idea is promising in another form, which is that Option<&'a mut T> ought to be copyable under some circumstances, but it can’t be Copy because that’d produce two seemingly-unrelated mutable references.

But if we had a way to change the type of the copy, to, say, Option<&'b mut T> (where 'b is shorter than 'a), then it would be a perfectly valid copy, which means:

// TODO: better names
trait CloneShrink<'a, T> {
    // Could this work without `&'a mut self`?
    fn clone_shrink(&'a mut self) -> T;
}
impl<'a, T: Clone> CloneShrink<'a, T> for T {
    fn clone_shrink(&mut self) -> T {
        self.clone()
    }
}

// #[lang = "copy_shrink"]
// The compiler would need to enforce that `T: CopyShrink<U>`
// cannot exist unless `T` and `U` only differ by lifetimes.
// e.g. `T = U` and `T = Foo<'a>, U = Foo<'b>` are both valid,
// but `T = Foo<'a>, U = &'b Bar` isn't.
// GATs would make this easier by requiring that you have
// `T = Self::GAT<'a>, U = Self::GAT<'b>`.
trait CopyShrink<'a, T>: CloneShrink<'a, T> {}
impl<'a, T: Copy> CopyShrink<'a, T> for T {}

impl<'a: 'b, 'b, T: ?Sized> CloneShrink<'b, &'b mut T> for &'a mut T {
    fn clone_shrink(&'b mut self) -> &'b mut T {
        *self
    }
}

fn main() {
    let mut x = &mut 0;
    *x.clone_shrink() += 1;
    println!("{}", x)
}

That exact code errors with this funny note:

   = note: downstream crates may implement trait `std::clone::Clone` for type `&mut _`

But if you remove the blanket impls, it does work, which means the compiler should be able to integrate CopyShrink in its is-copyable checks, without a lot of design and implementation work.

cc @nikomatsakis @pnkfelix

EDIT: I realize now, after reaching something that works (or at least can be tested for the “Clone” equivalent), that I likely ended up the same point @dhardy started from, with the main difference being associated type vs type parameter (we can do type parameter today, with a small check on every impl).

1 Like