I'm working on an (as yet unreleased) document tree structure implementation that uses a vector of nodes, where each node contain indexes to parent, child, sibling nodes. Including memory footprint and access efficiency and ergonomics, the Vector/index approach seems to be working better than an Rc<Node>
style tree. Now I have some mutating filter operations to support on the tree which can be modeled nicely using common visitor pattern implementation. However, I can't seem to make this work with the desired interfaces without unsafe
-ly "disabling the borrow checker", and while my code "works" Miri certainly doesn't like it, and I'm not sure if I can prove its safe. A minimized example repro is provided below. Any suggestions on how to avoid the unsafe or prove its safe? I think this is the first time I've gotten stuck in the same way.
// Sample code license: Attribution 4.0 International (CC BY 4.0)
#[derive(PartialEq, Eq, Debug, Clone)]
enum Color {
Green,
Blue,
Red,
}
#[derive(Debug)]
pub struct Palette {
colors: Vec<Color>,
}
impl Palette {
fn new() -> Palette {
let mut colors = Vec::with_capacity(100);
for i in 0..100 {
colors.push(match i % 3 {
0 => Color::Green,
1 => Color::Blue,
_ => Color::Red,
})
}
Palette { colors }
}
fn filter<F>(&mut self, mut f: F) -> bool
where F: Fn(&Palette, &mut Color) -> bool
{
if !self.colors.is_empty() {
self.filter_at(0, &mut f)
} else {
true
}
}
fn filter_at<F>(&mut self, i: usize, f: &mut F) -> bool
where F: Fn(&Palette, &mut Color) -> bool
{
if self.colors.len() > i + 1 && !self.filter_at(i + 1, f) {
self.colors.remove(i+1);
}
// Fails borrow-check:
// error[E0502]: cannot borrow `self.colors` as mutable because it is
// also borrowed as immutable
// f(self, &mut self.colors[i])
// -------------^^^^^^^^^^^----
// | | |
// | | mutable borrow occurs here
// | immutable borrow occurs here
// immutable borrow later used here
// Works, but can we proove it to be safe, based on limitations of what
// F could do in safe code?
let p = &*self as *const Palette;
f(unsafe { &*p }, &mut self.colors[i])
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Miri evaluation error: trying to reborrow for SharedReadOnly, but
// parent tag <6241> does not have an appropriate item in the borrow
// stack
}
fn len(&self) -> usize {
self.colors.len()
}
}
// Example filter
fn adjust_color(p: &Palette, c: &mut Color) -> bool {
match c {
Color::Green => p.len() % 2 == 0,
Color::Blue => p.len() % 2 == 1,
Color::Red => {
*c = if p.len() % 3 == 0 {
Color::Green
} else {
Color::Blue
};
true
}
}
}
fn main() {
let mut p = Palette::new();
p.filter(adjust_color);
println!("result:\n{:#?}", p);
}
Output:
result:
Palette {
colors: [
Green,
Green,
Blue,
Blue,
Green,
Blue,
Blue,
Green,
Blue,
Blue,
Green,
Blue,
Blue,
Green,
Blue,
Blue,
Green,
Blue,
Blue,
Green,
Blue,
Blue,
Green,
Blue,
Blue,
Green,
Blue,
Blue,
Green,
Blue,
Blue,
Green,
Blue,
Blue,
Green,
],
}
Errors:
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 0.51s
Running `target/debug/playground`