Help with My_Box::as_mut_ref

use super::*;

use std::sync::Arc;

pub struct My_Box<T> {
    index: usize,
    base: My_Box_Base<T>,}

impl My_Box<T> {
    pub fn as_ref(&self) -> &T {
        &self.base.data[index]}

    pub fn as_mut_ref(&mut self) -> & mut T {
        todo!()}}

impl Drop for My_Box<T> {
    fn drop(&mut self) {
        base.free_list.lock().unwrap().push_back(self.index);}}

#[derive(Clone)]
pub struct My_Box_Base<T: Default> {
    data: Arc<[T]>,
    free_list: Arc<Mutex<Vec<usize>>>,}

impl<T: Default> My_Box_Base<T> {
    pub fn get(&self) -> Optionn<My_Bo> {
        let index = self.free_list.lock().unwrap().pop()?;
        Some(My_Box {
            index,
            base: self.clone(),})}

    pub fn new(n: usize) -> My_Box_Base<T> {
        let mut data = Vec::with_capacity(n);
        for _ in 0..n {
            data.push(T::default());}

        let mut free_list = Vec::with_capacity(n);
        for i in 0..n {
            free_list.push(i)}

        My_Box_Base {
            data: Arc::from(data),
            free_list: Arc::new(Mutex::new(free_list)),}}}

The goal here is to avoid lots of alloc/free on Box<...>.

I am okay with the drop handler on T not being called.

My question here is: how do we safely write as_mut_ref.

My_Box::as_mut_ref() cannot be written with its current signature, since an Arc<[T]> can only yield &T references and not &mut T references. One fix would to use an Arc<[RwLock<T>]> for data, then return its guards from as_ref() and as_mut_ref():

use std::sync::{Arc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard};

pub struct My_Box<T> {
    index: usize,
    base: My_Box_Base<T>,
}

impl<T> My_Box<T> {
    pub fn as_ref(&self) -> RwLockReadGuard<'_, T> {
        self.base.data[self.index].read().unwrap()
    }

    pub fn as_mut_ref(&mut self) -> RwLockWriteGuard<'_, T> {
        self.base.data[self.index].write().unwrap()
    }
}

impl<T> Drop for My_Box<T> {
    fn drop(&mut self) {
        self.base.free_list.lock().unwrap().push(self.index);
    }
}

pub struct My_Box_Base<T> {
    data: Arc<[RwLock<T>]>,
    free_list: Arc<Mutex<Vec<usize>>>,
}

impl<T> Clone for My_Box_Base<T> {
    fn clone(&self) -> Self {
        My_Box_Base {
            data: self.data.clone(),
            free_list: self.free_list.clone(),
        }
    }
}

impl<T: Default> My_Box_Base<T> {
    pub fn get(&self) -> Option<My_Box<T>> {
        let index = self.free_list.lock().unwrap().pop()?;
        Some(My_Box {
            index,
            base: self.clone(),
        })
    }

    pub fn new(n: usize) -> My_Box_Base<T> {
        let mut data = Vec::with_capacity(n);
        for _ in 0..n {
            data.push(Default::default());
        }

        let mut free_list = Vec::with_capacity(n);
        for i in 0..n {
            free_list.push(i)
        }

        My_Box_Base {
            data: Arc::from(data),
            free_list: Arc::new(Mutex::new(free_list)),
        }
    }
}

Of course, this is not ideal, since it exposes implementation details. Other solutions are possible, either with unsafe code or with third-party crates.

First, a simple problem that would come back to bite you in the future:
derive(Clone) will create an impl block that requires T: Clone, you can see that on the playground if you click on Tools > Expand macros.
Even though the implementation is correct it won't work as you expect. There is a feature on Rust's Roadmap for the next years called "Perfect derive" that aims to solve this issue. There is also this great post from Niko Matsakis that goes into some of the past and future problems related to it along with some alternatives.

This was a long way of saying that you'll have to add your own impl block for Clone :smile:

Or you could just not implement Clone at all.

I'll continue on the other points using this modification as a base. Just a few fixes of typos and method names to make it compile.


Back to the actual question, Arc can only give you a shared reference to what is inside of it, even if you have a mutable reference to the Arc itself. This is to comply with Rust's aliasing rules, an Arc is a shared smart pointer, that is its entire purpose of being, so it must only give shared references.
You know that there is never more than one mutable reference to the same item on the slice. And you know that because you were careful to only ever make one My_Box with each index, with the available indexes safely guarded behind a Mutex preventing race conditions. You know that, not the compiler.
When there is an invariance that you can verify but the compiler can't you'll need to either:

  • Write some unsafe code
  • Change the architecture of your code, not always possible

Is there a way to do this without unsafe? With only the standard library, I don't think you can do this today without changing data to Arc<Vec<Mutex<T>>>, which would be quite inefficient.
There might be libraries that handle the unsafe part for you, after all Rust has a large ecosystem of crates with low-level primitive, but I personally don't know of one that would help solve this problem. I do know some other allocators that work in batches to avoid the lots of alloc/free of Box, like bumpalo and typed_arena. You can try one of those.
You can also look for crates to help you, but if you want to make this work without external dependencies you'll need a little bit of unsafe.

unsafe is just a way to tell the compiler that you are following all the rules even though it can't see that. If you do things correctly and uphold the invariants that the compiler requires there is no problem using unsafe code. There is an excellent talk from Jon Gjengset about that.
You still need to be prepared, so make sure you have read The Rustonomicon before going this route.


What you are trying to convey to the compiler is that it is safe to mutate through that reference to T even when you only have an immutable reference to its container. This is interior mutability and to have that in Rust there must be an UnsafeCell. You might think that since it's going to have unsafe anyway you could do this:

unsafe { &mut *(&self.as_ref() as *const _ as *mut _) }
// OR
unsage { core::mem::transmute(self.as_ref()) }

You can't, that is undefined behavior. The only valid way to go from a shared reference to a mutable reference is through an UnsafeCell, so we can change data to be:

data: Arc<[UnsafeCell<T>]>,

Here is the implementation using UnsafeCell with the justification of why it is safe. Notice that there are no changes to your logic, since it was indeed correct:


Other notes:

  • Arc::from(data) does another allocation with the same size as the Vec, see the docs.
  • You could use MaybeUninit<UnsafeCell<T>> and make get receive a value to remove the requirement of T: Default and match Box::new

Edits were typos.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.