Danny Birch
Danny Birch

Reputation: 603

Rust lifetime confusion with custom clone function

I think there is a subtle issue with what I'm trying to do here but I can't quite figure out why. I am attempting to clone a Box type (I know the type inside) but I think the lifetime is being propagated out somehow.

struct Thing 
{
    internal:Box<dyn std::any::Any>
}

impl Thing 
{
    fn new<T:'static>(u:usize) -> Self 
    {
        return Self 
        {
            internal:Box::new(u)
        }
    } 
    fn clone_func<T:'static>(&self) -> Self
    {
        return Self 
        {
            internal:Box::new(self.internal.downcast_ref::<T>().unwrap().clone())
        }
    }
}

pub fn main() 
{
    let a = Thing::new::<usize>(12usize);
    let b = a.clone_func::<usize>();
}

Error

error[E0759]: `self` has an anonymous lifetime `'_` but it needs to satisfy a `'static` lifetime requirement
  --> <source>:19:45
   |
15 |     fn clone_func<T:'static>(&self) -> Self
   |                              ----- this data with an anonymous lifetime `'_`...
...
19 |             internal:Box::new(self.internal.downcast_ref::<T>().unwrap().clone())
   |                      -----------------------^^^^^^^^^^^^------------------------- ...is captured and required to live as long as `'static` here

error: aborting due to previous error

Upvotes: 1

Views: 516

Answers (1)

user4815162342
user4815162342

Reputation: 154876

The problem is that you didn't request T to be Clone, so when you called self.internal.downcast_ref::<T>().unwrap().clone(), you actually cloned the reference, and tried to box it1.

Boxing &T works as far as the types are concerned (because it's a Box<dyn Any>), but it fails borrow checking. While T is guaranteed not to contain references to non-static data, the &T refers to the data inside &self which is not 'static, but has the anonymous lifetime of &self. This is actually pointed out by the compiler, but the error doesn't make sense without context.

Simply changing the trait bound to T: Clone + 'static makes the example compile:

fn clone_func<T: Clone + 'static>(&self) -> Self {
    return Self {
        internal: Box::new(self.internal.downcast_ref::<T>().unwrap().clone()),
    };
}

Playground


1 A shared reference is Clone and Copy because once you have one, you're allowed to create more references to the same data. Normally, given a foo: &T and a T: Clone, foo.clone() will resolve to T::clone(foo), which returns a T as expected. But if T isn't Clone, foo.clone() resolves to <&T>::clone(&foo), which returns another &T referring to the same T value as foo, i.e. it "clones" foo itself rather than the intended *foo.

For example, in this snippet, the type of b.clone() is &X, not X (playground):

//#[derive(Clone)]
struct X;

fn main() {
    let a = X;
    let b = &a;
    let () = b.clone();  // note type of b.clone() is &X, not X
}

If you uncomment #[derive(Clone)], the type of b.clone() becomes the expected X.

Upvotes: 2

Related Questions