rec_cell 0.1.0

Zero-cost borrow-checking of aliased references with cyclic construction
Documentation
use core::{cell::Cell, mem::MaybeUninit, ptr};
use std::{format, rc::Rc, string::String};

use crate::{CellLike, CursorMut, RecCell, RecToken, SliceCursor, SliceCursorMut};

fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}

#[test]
fn trait_impls_and_owned_cell_operations() {
    assert_send::<RecCell<'static, usize>>();
    assert_sync::<RecCell<'static, usize>>();

    let mut from = RecCell::from(String::from("from"));
    assert_eq!(from.as_mut(), "from");
    from.as_mut().make_ascii_uppercase();
    assert_eq!(from.into_inner(), "FROM");
    assert_eq!(RecCell::<usize>::default().into_inner(), 0);

    RecToken::new(|mut token| {
        let mut cell = RecCell::new(1usize);
        assert_eq!(format!("{cell:?}"), "RecCell { .. }");
        assert_eq!(*cell.borrow(&token), 1);
        *cell.borrow_mut(&mut token) = 2;
        *cell.get_mut() = 3;
        assert_eq!(cell.into_inner(), 3);
        assert_eq!(format!("{token:?}"), "RecToken { .. }");
    });
}

#[test]
fn debug_impls_do_not_expose_or_require_the_inner_value() {
    struct NotDebug;

    RecToken::new(|mut token| {
        let cell = RecCell::new(NotDebug);
        assert_eq!(
            format!("{:?}", cell.cursor(&token)),
            "Cursor { cell: RecCell { .. }, token: RecToken { .. } }"
        );
        assert_eq!(
            format!("{:?}", cell.cursor_mut(&mut token)),
            "CursorMut { cell: RecCell { .. }, token: RecToken { .. } }"
        );
        let cells = [RecCell::new(NotDebug)];
        assert_eq!(
            format!("{:?}", SliceCursor::new(&cells, &token)),
            "SliceCursor { cells: [RecCell { .. }], token: RecToken { .. } }"
        );
        assert_eq!(
            format!("{:?}", SliceCursorMut::new(&cells, &mut token)),
            "SliceCursorMut { cells: [RecCell { .. }], token: RecToken { .. } }"
        );
    });

    RecToken::new(|token| {
        let (debug, _) = token
            .with_builder(|builder| Ok::<_, ()>((format!("{builder:?}"), builder)))
            .unwrap();
        assert_eq!(debug, "RecBuilder { .. }");
    });
}

#[test]
fn reinterpreted_scalar_slice_and_array_storage_round_trip() {
    let mut scalar = 1usize;
    let scalar_ptr = ptr::from_mut(&mut scalar);
    let scalar_cell = RecCell::from_mut(&mut scalar);
    assert_eq!(scalar_cell.as_ptr(), scalar_ptr);
    *scalar_cell.get_mut() = 2;
    assert_eq!(scalar, 2);

    let mut values = [1usize, 2, 3];
    let values_ptr = values.as_mut_ptr();
    RecToken::new(|mut token| {
        let cells = RecCell::from_slice_mut(&mut values);
        assert_eq!(RecCell::as_slice_ptr(cells).cast::<usize>(), values_ptr);
        assert_eq!(RecCell::borrow_slice(cells, &token), &[1, 2, 3]);
        RecCell::borrow_slice_mut(cells, &mut token)[1] = 20;
        RecCell::get_slice_mut(cells)[2] = 30;
        assert_eq!(RecCell::borrow_slice(cells, &token), &[1, 20, 30]);

        let mut array = RecCell::new([4usize, 5, 6]);
        let elements = array.as_array_of_cells_mut();
        let cells = RecCell::from_array_of_cells_mut(elements).as_array_of_cells_mut();
        *cells[0].borrow_mut(&mut token) = 40;
        assert_eq!(RecCell::borrow_slice(cells, &token), &[40, 5, 6]);
        assert_eq!(
            RecCell::from_array_of_cells(cells).borrow(&token),
            &[40, 5, 6]
        );
    });
    assert_eq!(values, [1, 20, 30]);
}

#[test]
fn shared_cursors_cover_mapping_updates_errors_and_copy() {
    RecToken::new(|token| {
        let tail = RecCell::new(2usize);
        let head = RecCell::new(&tail);
        let other = RecCell::new(3usize);

        let cursor = head.cursor(&token);
        assert_eq!(*cursor.get().borrow(&token), 2);
        let cursor = cursor.map(|next| *next);
        assert_eq!(cursor.get(), &2);
        let cursor = cursor.map_cell(|_| &other);
        assert_eq!(cursor.get(), &3);
        let err = cursor.try_map::<RecCell<'_, usize>, _>(|_| Err("map failed"));
        assert_eq!(err.unwrap_err(), "map failed");

        let mut cursor = head.cursor(&token).map(|next| *next);
        cursor.update(|_| &other);
        assert_eq!(cursor.get(), &3);
        assert_eq!(
            cursor.try_update(|_| Err::<&RecCell<'_, usize>, _>("update failed")),
            Err("update failed")
        );
        assert_eq!(cursor.get(), &3);
        let cursor = cursor.try_map_cell(Ok::<_, ()>).unwrap();
        let (cell, token) = cursor.into_inner();
        assert_eq!(cell.borrow(token), &3);
    });
}

#[test]
fn mutable_cursors_cover_mapping_updates_replacement_and_errors() {
    RecToken::new(|mut token| {
        let first = RecCell::new(1usize);
        let next = RecCell::new(2usize);
        let mut cursor = CursorMut::new(&first, &mut token);
        assert_eq!(cursor.get(), &1);
        *cursor.get_mut() = 10;
        assert_eq!(cursor.replace(11), 10);
        assert_eq!(cursor.take(), 11);
        *cursor.get_mut() = 1;
        cursor.update(|_| &next);
        assert_eq!(cursor.get(), &2);
        assert_eq!(
            cursor.try_update(|_| Err::<&RecCell<'_, usize>, _>("update failed")),
            Err("update failed")
        );
        assert_eq!(cursor.get(), &2);
        let cursor = cursor.map_cell(|cell| cell);
        let cursor = cursor
            .try_map(|value| {
                *value = 20;
                Ok::<_, ()>(&first)
            })
            .unwrap();
        assert_eq!(cursor.get(), &1);
        let err = cursor.try_map_cell::<RecCell<'_, usize>, _>(|_| Err("map failed"));
        assert_eq!(err.unwrap_err(), "map failed");

        let (cell, token) = first.cursor_mut(&mut token).into_inner();
        assert_eq!(cell.borrow(token), &1);
    });
}

#[test]
fn slice_cursors_cover_every_shape_transition() {
    RecToken::new(|mut token| {
        let first = [RecCell::new(1usize), RecCell::new(2)];
        let second = [RecCell::new(3usize), RecCell::new(4)];
        let single = RecCell::new(5usize);

        let cursor = SliceCursor::new(&first, &token);
        assert_eq!(cursor.get(), &[1, 2]);
        assert_eq!(cursor.clone().get(), &[1, 2]);
        let cursor = cursor.map(|_| &single).map_cell(core::array::from_ref);
        assert_eq!(cursor.get(), &[5]);
        let err = cursor.try_map_cell::<RecCell<'_, usize>, _>(|_| Err("map failed"));
        assert_eq!(err.unwrap_err(), "map failed");

        {
            let mut cursor = SliceCursor::new(&first, &token);
            cursor.update(|_| &second);
            assert_eq!(cursor.get(), &[3, 4]);
            assert_eq!(
                cursor.try_update(|_| Err::<&[RecCell<'_, usize>], _>("update failed")),
                Err("update failed")
            );
            let (cells, token) = cursor.into_inner();
            assert_eq!(RecCell::borrow_slice(cells, token), &[3, 4]);
        }

        let mut cursor = SliceCursorMut::new(&first, &mut token);
        cursor.get_mut()[0] = 10;
        let cursor = cursor.map(|values| {
            values[1] = 20;
            &single
        });
        assert_eq!(cursor.get(), &5);
        let mut cursor = cursor.map_cell(core::array::from_ref);
        cursor.get_mut()[0] = 50;
        let err = cursor.try_map::<RecCell<'_, usize>, _>(|_| Err("map failed"));
        assert_eq!(err.unwrap_err(), "map failed");

        let mut cursor = SliceCursorMut::new(&first, &mut token);
        cursor.update(|_| &second);
        assert_eq!(cursor.get(), &[3, 4]);
        assert_eq!(
            cursor.try_update(|_| Err::<&[RecCell<'_, usize>], _>("update failed")),
            Err("update failed")
        );
        let (cells, token) = cursor.into_inner();
        assert_eq!(RecCell::borrow_slice(cells, token), &[3, 4]);

        let array_cursor = first.as_cursor(token);
        assert_eq!(array_cursor.get(), &[10, 20]);
    });
}

#[test]
fn cyclic_construction_preserves_identity_and_initializes_all_container_forms() {
    struct Node<'a, 't> {
        next: &'a RecCell<'t, Node<'a, 't>>,
        value: usize,
    }

    RecToken::new(|token| {
        let mut self_slot = MaybeUninit::<Node<'_, '_>>::uninit();
        let (self_cell, token) = RecCell::new_cyclic(&mut self_slot, token, |cell| {
            (
                cell,
                Node {
                    next: cell,
                    value: 1,
                },
            )
        });
        let node = self_cell.borrow(&token);
        assert_eq!(node.value, 1);
        assert!(ptr::eq(node.next, self_cell));

        let mut pair = (MaybeUninit::uninit(), MaybeUninit::uninit());
        let ((left, right), token) = token
            .make_cyclic((&mut pair.0, &mut pair.1), |(left, right)| {
                ((left, right), (2usize, 3usize))
            });
        assert_eq!((*left.borrow(&token), *right.borrow(&token)), (2, 3));

        let mut slots = [const { MaybeUninit::<usize>::uninit() }; 3];
        let [first, second, third] = &mut slots;
        let ((), token) = token.make_cyclic([first, second, third], |cells| {
            (
                (),
                [
                    cells[0].as_ptr() as usize,
                    cells[1].as_ptr() as usize,
                    cells[2].as_ptr() as usize,
                ],
            )
        });
        for (cell, slot) in slots.iter().enumerate() {
            // SAFETY: `make_cyclic` initialized every array element above.
            let value = unsafe { slot.assume_init_ref() };
            assert_eq!(value, &(slots[cell].as_ptr() as usize));
        }

        assert_eq!(RecCell::new(9).borrow(&token), &9);
    });
}

#[test]
fn cyclic_builder_supports_empty_and_maximum_tuple_inputs() {
    RecToken::new(|token| {
        let ((), token) = token.make_cyclic((), |()| ((), ()));

        let mut slots = [const { MaybeUninit::<usize>::uninit() }; 12];
        let [s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11] = &mut slots;
        let ((), token) = token
            .make_cyclic((s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11), |_| {
                ((), (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11))
            });
        for (index, slot) in slots.iter().enumerate() {
            // SAFETY: `make_cyclic` initialized every tuple element above.
            assert_eq!(unsafe { slot.assume_init_ref() }, &index);
        }
        assert_eq!(RecCell::new(1).borrow(&token), &1);
    });
}

#[test]
fn fallible_builders_do_not_write_and_successful_builder_sequences_do() {
    RecToken::new(|token| {
        let mut untouched = MaybeUninit::new(7usize);
        let error =
            RecCell::try_new_cyclic(&mut untouched, token, |_| Err::<((), usize), _>("failed"));
        assert_eq!(error, Err("failed"));
        // SAFETY: failed construction leaves the pre-initialized slot untouched.
        assert_eq!(unsafe { untouched.assume_init_ref() }, &7);
    });

    RecToken::new(|token| {
        let mut direct = MaybeUninit::uninit();
        let (value, token) = token
            .try_make_cyclic(&mut direct, |_| Ok::<_, ()>((5usize, 6usize)))
            .unwrap();
        assert_eq!(value, 5);
        // SAFETY: successful construction initializes the slot.
        assert_eq!(unsafe { direct.assume_init_ref() }, &6);

        let mut first = MaybeUninit::uninit();
        let mut second = MaybeUninit::uninit();
        let ((first_cell, second_cell), token) = token
            .with_builder(|builder| {
                builder.try_add(&mut first, |builder, first_cell| {
                    let (second_cell, builder) = builder
                        .try_add(&mut second, |builder, second_cell| {
                            Ok::<_, ()>((second_cell, builder, 20usize))
                        })?;
                    Ok::<_, ()>(((first_cell, second_cell), builder, 10usize))
                })
            })
            .unwrap();
        assert_eq!(
            (*first_cell.borrow(&token), *second_cell.borrow(&token)),
            (10, 20)
        );

        let mut failing = MaybeUninit::new(30usize);
        let error = token.with_builder(|builder| {
            builder.try_add(&mut failing, |_, _| {
                Err::<((), crate::RecBuilder<'_, '_>, usize), _>("failed")
            })
        });
        assert_eq!(error, Err("failed"));
        // SAFETY: failed construction leaves the pre-initialized slot untouched.
        assert_eq!(unsafe { failing.assume_init_ref() }, &30);
    });
}

struct DropCounter(Rc<Cell<usize>>);

impl Drop for DropCounter {
    fn drop(&mut self) {
        self.0.set(self.0.get() + 1);
    }
}

#[test]
fn owned_cells_drop_once_and_cyclic_storage_requires_explicit_drop() {
    let owned_drops = Rc::new(Cell::new(0));
    {
        let _cell = RecCell::new(DropCounter(owned_drops.clone()));
    }
    assert_eq!(owned_drops.get(), 1);

    let cyclic_drops = Rc::new(Cell::new(0));
    RecToken::new(|token| {
        let mut slot = MaybeUninit::uninit();
        let ((), token) = RecCell::new_cyclic(&mut slot, token, |_| {
            ((), DropCounter(cyclic_drops.clone()))
        });
        assert_eq!(cyclic_drops.get(), 0);
        // SAFETY: construction above initialized the slot and all token-gated
        // access has ended.
        unsafe { slot.assume_init_drop() };
        assert_eq!(cyclic_drops.get(), 1);
        assert_eq!(RecCell::new(1).borrow(&token), &1);
    });
}

#[cfg(feature = "cyclic_with_mut")]
#[test]
fn mutable_token_cyclic_constructors_restore_the_token() {
    RecToken::new(|mut token| {
        let mut first = MaybeUninit::uninit();
        let value = token.make_cyclic_with_mut(&mut first, |_| (7usize, 11usize));
        assert_eq!(value, 7);
        // SAFETY: `make_cyclic_with_mut` initialized the slot.
        assert_eq!(unsafe { first.assume_init_ref() }, &11);

        let mut second = MaybeUninit::uninit();
        let value = RecCell::new_cyclic_with_mut(&mut second, &mut token, |_| (8usize, 12usize));
        assert_eq!(value, 8);
        // SAFETY: `new_cyclic_with_mut` initialized the slot.
        assert_eq!(unsafe { second.assume_init_ref() }, &12);
        assert_eq!(RecCell::new(1).borrow(&token), &1);
    });
}