Why can't a Rc which takes ownership of self have ownership taken in a trait w/ predefined body?

Hello. I'm writing an application which makes heavy use of traits and Reference Counters (std::rc::Rc). I have several structs/impl's I only wanted accessed via a Rc (so they may be "passed around" and borrowed easily. These structs which I only want wrapped in an Rc are then wrapped into another struct with some utility methods to make accessing/borrowing easier.

Consider this code segment:

struct RcWrapped{
  ...
  value: Rc<RefCell<dyn RcWrappable>>
}

impl RcWrapped{
  fn from_rc(value: Rc<RefCell<dyn RcWrappable>>) -> RcWrapped{
    RcWrapped{
      value,
      ..Default::default()
    }
  }
}

struct CoolStruct{
  ...
}

// POSSIBLE IMPLEMENTATIONS:

// IMPLEMENTATION A:
impl RcWrappable for CoolStruct;

trait RcWrappable{
  fn to_rc(self) -> RcWrapped
   where Self: Sized {
    RcWrapped::from_rc(Rc::new(RefCell::new(self)))
  }
}

OR

// IMPLEMENTATION B:
impl CoolStruct{
  fn to_rc(self) -> RcWrapped{
    RcWrapped::from_rc(Rc::new(RefCell::new(self)))
  }
}

Look closely, implementation B works just fine. However, I would rather not reuse the same method over and over again, cluttering every implementation of a RcWrappable struct.

Implementation A doesn't work. I get an error saying Self may not live long enough. How can this even matter? RcWrapped owns self through Rc and RefCell in both cases. Self now lives as long as the returned RcWrapped value which is all we need.

Why does the compiler error here? Is one way more valid than the other? Is this even a good approach?

The idea is that I could just write
impl RcWrappable for ... on any struct I want to be easily wrappable into the RcWrapped type.

1 Like

Consider using a marker trait with 'static bound

Actually, I think, just adding the static bound directly should work too:

// add 'static bound to the trait
trait RcWrappable: 'static {
    fn to_rc(self) -> RcWrapped
    where
        Self: Sized,
    {
        RcWrapped::from_rc(Rc::new(RefCell::new(self)))
    }
}
2 Likes

Making my RcWrapped objects static would defeat the whole purpose of making it a controlled Rc anyway.

I don't get your point, because Rc already implicitly requires 'static bound, as you can't put borrowed data in it?

My concern is that my underlying RcWrapped object will exist forever. Is that an incorrect assumption? Will putting 'static on my RcWrappable make my CoolStruct live forever? If RcWrapped is dropped (and there are no other clones of the underlying Rc), will CoolStruct be dropped too?

No. This is a common misconception. 'static only means that there are no borrows.

6 Likes

Of course it will be dropped. You can verify it by by just making a local scope for your trait with 'static bound:

fn main() {
    {
        let wrapped = Rc::new(RefCell::new(CoolStruct {
            name: "test".into(),
        }));
        
        println!("Inside scope, Rc count: {}", Rc::strong_count(&wrapped));
    } // wrapped goes out of scope here
    
   // Can't use wrapped here anymore O.O
}

I wonder how people ever arrive at that conclusion, but maybe you will help us. Because that's something I never had to struggle with, yet, somehow, it's, apparently a problem for newbies. Let's try to convert your questions to simple types… i32, for example – and Point2D struct with two i32 inside (one for x, one for y; just a simple coordinates):

My concern is that my underlying Point2D object will exist forever. Is that an incorrect assumption? Will putting 'static on my Point2D make my i32 live forever? If Point2D is dropped (and there are no other clones of the underlying i32), will i32 be dropped too?

Answers, for i32 and Point2D (which is simple two i32 numbers) are obvious:

  • Yes, Point2D types will exist forever.
  • No, objects of Point2D type wouldn't exist forever.
  • And yes putting 'static on Point2D means ephemeral types (like &int or &mut int) couldn't be put into Point2D
  • But if Point2D object is dropped, then all i32s inside are dropped, too.

Everything is obvious, when there are no refcounters? Now, let's replace i32 with CoolStruct and Point2D with RcWrapped… what have changed? Nothing? Then why would these questions suddenly get different answers?

1 Like

My newbie Rc equivalent is this.... to be honest I still can't get over the fact that this is still unstable O.O

struct CoolStruct<const COOL_CONST: usize = 10> {
    cool_field1: [usize; COOL_CONST],
    cool_field: [usize; COOL_CONST + 1],
}

@consistent-milk12 already provided the solution. I'm just making this post to provide some more (long-winded) explanation.

Rust lifetimes generally denote the duration of borrows. They don't denote what most languages call a "lifetime" -- existence of a value from creation to destruction. When you have a type that meets a 'static bound, it doesn't mean that values of that type are never destructed. It means that the type contains no Rust lifetimes besides perhaps 'static.[1] That's a type-level property, not a directive for how long values will exist.

When the error says "the parameter type Self may not live long enough", what they really mean is "it's possible the Self type might have a non-'static lifetime in it, so this won't work as a default body for all possible implementing types". (Self having a non-'static lifetime in it would almost always mean "the implementing type contains a short term borrow".)

The body of the error phrases it closer to how I would: Self may not meet a 'static bound.

Adding a 'static bound to the trait means that all implementors must meet a 'static bound (must not have any non-'static lifetimes in the type), which means the default body will work for all of them, and the error goes away.


But why was a 'static bound required for the default body in the first place? The answer is somewhat hidden in the RcWrapped type:

struct RcWrapped{
  value: Rc<RefCell<dyn RcWrappable>>
  //               ^^^^^^^^^^^^^^^^
  // This is shorthand for `dyn RcWrappable + 'static`
}

The meaning of that is "the type of whatever value was coerced to this dyn RcWrappable meets a 'static bound".

If you try to coerce some specific type that does not meet a 'static bound, like so...

// Nothing in the trait definition forbids implementing `RcWrappable`
// for this type.  (I.e. there is no `trait RcWrappable: 'static` requirement.)
impl<'borrow_duration> RcWrappable for &'borrow_duration CoolStruct {
    fn to_rc(self) -> RcWrapped {
        // Here, `Self` is `&'borrow_duration CoolStruct`.
        //
        // The coercion to `dyn RcWrappable + 'static` requires
        // `&'borrow_duration CoolStruct: 'static`, but that bound is not met.
        RcWrapped::from_rc(Rc::new(RefCell::new(self)))
    }
}

...the error for the specific type is different, but it is exactly the scenario which the error in the OP was trying to point out: Self doesn't necessarily meet a 'static bound.

The outer Rc<_> type constructor didn't have anything to do with it. The T in Rc<T> is not required to meet a 'static bound. (In practice it usually does, due to the use cases that reference counting targets, but there is no requirement. It is almost certain that the 'static bound is what you want.)


  1. More generally: T: 't means that any lifetime 'x which does or can possibly appear in T meets a 'x: 't bound. ↩︎

7 Likes

'static means that you can delay drop as much as you want basically but it does not have to live forever

2 Likes

No, 'static precisely does mean that something “lives forever”, but when some type lives forever doesn't mean that objects of that type live forever.

It would be very different language if every single i32 had to live forever, isn't it?