(Yet another) question about lifetime


#1

I have the following code:

#![feature(rustc_private)]
extern crate arena;
use arena::TypedArena;

pub struct MemoryPool {
  arena: TypedArena<Vec<u8>>,
  bytes_allocated: usize
}

impl MemoryPool {
  pub fn consume(&mut self, data: Vec<u8>) -> &mut [u8] {
    self.bytes_allocated += data.capacity();
    self.arena.alloc(data)
  }
}

pub struct S<'a> {
  mem_pool: &'a mut MemoryPool
}

impl<'a> S<'a> {
  pub fn write(&mut self) -> &'a mut [u8] {
    let v = vec!();
    self.mem_pool.consume(v)
  }
}

Basically what I want to do is to create a thin wrapper on TypedArena that can track the # of bytes allocated in total. However, this doesn’t work since self.mem_pool is mutable, and hence cannot outlive the lifetime of its owning struct S.

My question is:

  1. Why the lifetime of self.mem_pool.consume(v) cannot be the same as 'a? To my understanding, TypedArena.alloc() should adjust the lifetime of the input to be the same as the arena object, but not the reference, right? If that’s so, even though the struct S instance could go out of scope before the lifetime 'a does, the MemoryPool object is still alive, and hence so does the return value of function write.
  2. How can I rewrite this code to make it work as intended? one workaround is to store two things in the struct S: a &'a TypedArena<Vec<u8>> and another mutable memory tracking struct that just count the bytes allocated by the former. However this seems awkward, since every time I need to pass in two separate things and coordinate them to do the alloc & count stuff.

Thanks in advance!


#2

The easiest alternative is to use Cell<usize> for bytes_allocated, like so:

pub struct MemoryPool {
  arena: TypedArena<Vec<u8>>,
  bytes_allocated: Cell<usize>
}

impl MemoryPool {
  pub fn consume(&self, data: Vec<u8>) -> &mut [u8] {
    self.bytes_allocated.set(self.bytes_allocated.get() + data.capacity());
    self.arena.alloc(data)
  }
}

pub struct S<'a> {
  mem_pool: &'a MemoryPool
}

impl<'a> S<'a> {
  pub fn write(&self) -> &'a mut [u8] {
    let v = vec!();
    self.mem_pool.consume(v)
  }
}

It’s because you have a mutable reference to MemoryPool, which makes S<'a> invariant - i.e. lifetimes cannot be approximated anymore, and must be identical. Since you only need the mutable reference to increment bytes_allocated, the easiest and most straightforward alternative is the Cell one I mentioned above.


#3

Ah, thanks! I totally forgot about Cell in this case. That solves my problem.

Yes, I understand that Rust is invariant on lifetime of mutable, but I just think in this case it might be too strict. Not sure what harm can be done if we allow the above.