Sneaking a Peek at Private Data for Testing in Rust

For test purposes, it's often useful to take a peek at (and possibly mess with) data which would otherwise be private to a component.

While writing test code for one of my projects, I found that this need sometimes conflicts with Rust's safety guarantees, in particular, the guarantee that there is at most one mutable reference to an object, and that if there is a mutable reference, then no immutable ones may also exist.

For example, say your type takes ownership of something in its constructor function, which you later want to take a peek at to see what was done with it, or to change it. Rust won't allow this normally, because by giving that type ownership, you've given up all control:

error: use of moved value x

To work around this, you might be tempted to have your type take the object as a mutable reference, instead of taking ownership outright. Ah, but now you run into Rust's limitations on mutable references listed above. While that type holds a mutable reference, you can't touch or look at the object:

error: cannot borrow x as mutable more than once at a time

Next try: use a Rc to allow you to share the object. This won't work either, because you can only get a mutable reference out of an Rc when there is exactly one strong reference, and in your usage, there will be two: one held by the type, and one held by your test code. This is the worst, as it only fails at runtime:

thread '<main>' panicked at 'called Option::unwrap() on a None value'

The solution I came up with uses the UnsafeCell type. UnsafeCell allows you to get a mutable pointer at any time. This is unsafe in the general case, but if we're careful, we an use this to our advantage.

I implemented a type called Sneaky that lets you do this. It takes an instance of whatever you want, and makes a Rc of an UnsafeCell of it. I used Rc so that this type can safely be passed to other types that want to take ownership of a value.

Sneaky implements the Borrow and BorrowMut types by dereferencing the pointer obtained from calling UnsafeCell::get(). It also implements AsRef and AsMut and Deref and DerefMut identically.

Everything is safe, except for a sneak function which clones the inner Rc and gives you another handle to the variable. Until you call sneak, everything is safe because there is only one instance of the variable, and so it can be mutated freely. But once called, now you have two handles and you need to be careful. Therefore sneak is marked as unsafe.

Here's the full code:

And here's an example of its usage:

Show Comments