uazu / qcell

Statically-checked alternatives to RefCell and RwLock
Apache License 2.0
356 stars 22 forks source link

Debugging ergonomics? #43

Closed xxx closed 1 year ago

xxx commented 1 year ago

Heya, thanks for making this crate.

I'm currently porting a project over from RefCell, and I'm finding that I really miss being able to derive Debug on things that now contain QCells. Debugging anything in a QCell is requiring me to go in with a debugger every time, when I often just want to dump out what's in it. I typically can't use get_mut or into_inner in any of these places.

Do you have any suggestions to make this more ergonomic?

uazu commented 1 year ago

As far as I can see, I can't safely give access to the inside of the cell for Debug without also having access to the owner, and the Debug trait doesn't allow that to be passed through. There might be a mutable reference to the contents of the cell active somewhere else, and if the Debug implementation is given an immutable reference, that's a soundness hole according to Rust's ownership rules. That would obviously be called out as a bug in the crate.

I think the only way to do a debugging dump would be if you had access to the owner, and manually access the cell's contents to debug them (e.g. using ro).

If you can see another way of doing this soundly, let me know, but right now I can't see a way.

xxx commented 1 year ago

Thanks for answering. Yeah, I can't really think of anything either. Right now I'm using this module and wrapping the object temporarily when needed, which takes care of things well enough for me. (In this, key == owner. I think of QCells as more like jail cells, and you need the key to get anything out of it - owner has another semantic meaning on the project.)

use std::fmt::{Debug, Display, Formatter};

use qcell::QCellOwner;

pub struct WithKey<'a, T> {
    pub value: &'a T,
    pub cell_key: &'a QCellOwner,
}

/// A trait for types that can be paired with a [`QCellOwner`].
pub trait Keyable<'a> {
    /// Get a pairing of me and the cell key.
    fn with_key(&'a self, cell_key: &'a QCellOwner) -> WithKey<'a, Self>
    where
        Self: Sized,
    {
        WithKey {
            value: self,
            cell_key,
        }
    }

    /// Key-aware `Debug`
    fn keyable_debug(&self, cell_key: &QCellOwner) -> String;

    /// Key-aware `Display`
    fn keyable_display(&self, cell_key: &QCellOwner) -> String {
        self.keyable_debug(cell_key)
    }
}

impl<'a, T> Display for WithKey<'a, T>
where
    T: Keyable<'a>,
{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.value.keyable_display(self.cell_key))
    }
}

impl<'a, T> Debug for WithKey<'a, T>
where
    T: Keyable<'a>,
{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str(&self.value.keyable_debug(self.cell_key))
    }
}

impl<'a, T> PartialEq for WithKey<'a, T>
where
    T: Keyable<'a> + PartialEq,
{
    fn eq(&self, other: &Self) -> bool {
        self.value == other.value
    }
}