I am messing around with an idea and have ended up trying some unsafe
stuff. I have found myself in a situation where I have a reference that, from my understanding, is not referencing anything.
In the test unbind_child_context_ref
, the ContextRef
is deref
'ed, and the reference is stored in a variable.
The ContextRef
internals are set to None
and, in my mind, the ChildContext
is dropped. But calling on the child_context_ref
doesn't cause the test to panic.
What is actually going on here?
use std::cell::{Ref, RefCell, RefMut, UnsafeCell};
use std::collections::HashMap;
use std::mem;
use std::ops::Deref;
use std::rc::{Rc, Weak};
pub struct Context {
children: RefCell<ContextChildren>
}
struct ContextChildren {
child_context: InternalContextRef<ChildContext>,
// ...
}
fn replace_context<T>(internal_context: &mut InternalContextRef<T>, ctx: T) -> ContextRef<T> {
if internal_context.is_not_null() {
// nullify the last context
let _ = internal_context.release();
}
let int_ctx_ref = InternalContextRef::new(ctx);
*internal_context = int_ctx_ref.clone();
ContextRef {
internal: int_ctx_ref
}
}
impl Context {
pub fn new() -> Self {
Context {
children: RefCell::new(ContextChildren::new())
}
}
fn bind_child_context(&self, id: u32) -> ContextRef<ChildContext> {
let mut children = self.children.borrow_mut();
// if id != children.child_context
replace_context(
&mut children.child_context,
ChildContext::new(id)
)
}
fn unbind_child_context(&self) {
let children = self.children.borrow_mut();
let _ = children.child_context.release();
}
}
impl ContextChildren {
fn new() -> Self {
Self {
child_context: InternalContextRef::<ChildContext>::null()
}
}
}
pub struct ContextRef<T> {
internal: InternalContextRef<T>
}
impl<T> Deref for ContextRef<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.internal
}
}
struct InternalContextRef<T> {
ptr: Rc<UnsafeCell<Option<T>>>
}
impl <T> InternalContextRef<T> {
fn null() -> Self {
InternalContextRef {
ptr: Rc::new(UnsafeCell::new(None))
}
}
fn new(data: T) -> Self {
InternalContextRef {
ptr: Rc::new(UnsafeCell::new(Some(data)))
}
}
fn is_not_null(&self) -> bool {
let ptr_ref = unsafe { & *self.ptr.get() };
ptr_ref.is_some()
}
fn is_null(&self) -> bool {
let ptr_ref = unsafe { & *self.ptr.get() };
ptr_ref.is_some()
}
fn try_context(&self) -> Option<&T> {
let ptr_ref = unsafe { & *self.ptr.get() };
ptr_ref.as_ref()
}
fn release(&self) -> Option<T> {
let ptr_mut_ref = unsafe { &mut *self.ptr.get() };
ptr_mut_ref.take()
}
fn replace(&self, data: T) -> Option<T> {
let ptr_mut_ref = unsafe { &mut *self.ptr.get() };
ptr_mut_ref.replace(data)
}
}
impl<T> Deref for InternalContextRef<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
let ptr_ref = unsafe { & *self.ptr.get() };
if let Some(ctx) = ptr_ref.as_ref() {
return ctx;
} else {
panic!("context has been released");
}
}
}
impl<T> Clone for InternalContextRef<T> {
fn clone(&self) -> Self {
InternalContextRef {
ptr: Rc::clone(&self.ptr)
}
}
}
struct ChildContext {
id: u32,
}
impl ChildContext {
fn new(id: u32) -> Self {
Self {
id
}
}
fn id(&self) -> u32 {
self.id
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn child_context_creation() {
let context = Context::new();
let child_context = context.bind_child_context(1);
assert!(child_context.id() == 1);
}
#[test]
#[should_panic]
fn unbind_child_context() {
let context = Context::new();
let child_context = context.bind_child_context(1);
context.unbind_child_context();
child_context.id();
}
#[test]
#[should_panic]
fn unbind_child_context_ref() {
let context = Context::new();
let child_context = context.bind_child_context(1);
let child_context_ref = child_context.deref();
context.unbind_child_context();
// the internal ChildContext should be dealloc
// child_context_ref is a reference that still lives
child_context_ref.id();
}
}