Tim Diekmann
Tim Diekmann

Reputation: 8486

How do I return an Iterator over a collection encapsulated by a RefCell/RwLock Ref/Guard using unsafe code?

Multiple questions were already asked regarding this topic:

The answers are more or less: It's not possible (without unsafe).

I tried the unsafe variant myself and want to ask if this way is safe.

The idea is that I wrap the guard in a struct that implements Iterator. Besides the guard, an iterator is stored which will be created from the stored guard:

struct MapIter<'a> {
    guard: RwLockReadGuard<'a, HashMap<i32, i32>>,
    iter:  Iter<'a, i32, i32>,
}

It's created with these lines:

impl<'a> MapIter<'a> {
    fn new(map: &'a RwLock<HashMap<i32, i32>>) -> Box<Self> {
        // create a `box Self`
        // the iterator remains uninitialized.
        let mut boxed = Box::new(Self {
            guard: map.read().expect("ToDo"),
            iter:  unsafe { mem::uninitialized() },
        });

        // create the iterator from `box Self`.
        boxed.iter = unsafe { 
            (*(&boxed.guard as *const RwLockReadGuard<'a, HashMap<i32, i32>>)).iter() 
        };
        boxed
    }
}

Now it can implement Iterator:

impl<'a> Iterator for MapIter<'a> {
    type Item = (&'a i32, &'a i32);

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next()
    }
}

Is this code safe?

See this code in action at the playground.

Additionally I get a trivial cast warning

warning: trivial cast: warning: trivial cast: `&std::sync::RwLockReadGuard<'_, std::collections::HashMap<i32, i32>>` as `*const std::sync::RwLockReadGuard<'a, std::collections::HashMap<i32, i32>>`. Cast can be replaced by coercion, this might require type ascription or a temporary variable
   |
   | unsafe { (*(&boxed.guard as *const RwLockReadGuard<'a, HashMap<i32, i32>>)).iter() };
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |

How to get around this?

Upvotes: 1

Views: 796

Answers (1)

trent
trent

Reputation: 28055

No, it's not safe. I can use Container to create a dangling reference in safe code:

let container = Container::new();       // create a container
let r = {
    let mut it = container.iter();
    it.next()                           // obtain a reference to part of it
};
container.map.write().unwrap().clear(); // empty the container
println!("{:?}", r);                    // oh dear.

In the playground this compiles, which isn't good, because r contains references to data that are invalidated when the HashMap is cleared.

Vladimir Matveev's answer to a similar question explains in more detail why this is unsound, and contains the following concise summary:

You cannot do this because it would allow you to circumvent runtime checks for uniqueness violations.

Upvotes: 4

Related Questions