Took the challenge and tried working with LCell
. This is what I came up with, trying to stay ergonomic to some extend
use qcell::{LCell, LCellOwner};
use std::{mem, rc::Rc};
struct PersonData<'a, 'id> {
name: &'a str,
age: i32,
children: Vec<Person<'a, 'id>>,
}
#[derive(Clone)]
struct Person<'a, 'id>(Rc<LCell<'id, PersonData<'a, 'id>>>);
impl<'a, 'id> Person<'a, 'id> {
fn new(name: &'a str, age: i32) -> Self {
Person(Rc::new(LCell::new(PersonData {
name,
age,
children: Vec::new(),
})))
}
fn with_owner_mut<'r>(&'r self, owner: &'r mut LCellOwner<'id>) -> PersonMut<'a, 'id, 'r> {
PersonMut {
person: self,
owner,
}
}
fn with_owner<'r>(&'r self, owner: &'r LCellOwner<'id>) -> PersonRef<'a, 'id, 'r> {
PersonRef {
person: self,
owner,
}
}
fn show(&self, owner: &LCellOwner<'id>) {
self.show_w_tab(owner, 0);
}
fn show_w_tab(&self, owner: &LCellOwner<'id>, tab: usize) {
println!(
"{:>1$}, {age} yrs old",
self.0.ro(owner).name,
tab + self.0.ro(owner).name.len(),
age = self.0.ro(owner).age
);
}
fn show_r(&self, owner: &LCellOwner<'id>, tab: usize) {
self.show_w_tab(owner, tab);
for c in self.0.ro(owner).children.iter() {
c.show_r(owner, tab + 4);
}
}
fn show_family_tree(&self, owner: &LCellOwner<'id>) {
self.show_r(owner, 0)
}
fn collect_r<'b>(
&'b self,
owner: &'b LCellOwner<'id>,
result: &mut Vec<&'b Self>,
filter: &mut impl FnMut(&PersonData<'a, 'id>) -> bool,
) {
if filter(self.0.ro(owner)) {
result.push(self);
}
for c in self.0.ro(owner).children.iter() {
c.collect_r(owner, result, filter);
}
}
fn collect<'b>(
&'b self,
owner: &'b LCellOwner<'id>,
mut filter: impl FnMut(&PersonData<'a, 'id>) -> bool,
) -> Vec<&'b Self> {
let mut result: Vec<&Self> = Vec::new();
self.collect_r(owner, &mut result, &mut filter);
result
}
}
struct PersonMut<'a, 'id, 'r> {
person: &'r Person<'a, 'id>,
owner: &'r mut LCellOwner<'id>,
}
impl<'a, 'id, 'r> PersonMut<'a, 'id, 'r> {
fn add(self, name: &'a str, age: i32) -> Self {
self.person
.0
.rw(self.owner)
.children
.push(Person::new(name, age));
self
}
}
struct PersonRef<'a, 'id, 'r> {
person: &'r Person<'a, 'id>,
owner: &'r LCellOwner<'id>,
}
impl<'a, 'id, 'r> PersonRef<'a, 'id, 'r> {
fn get_child(&self, ix: usize) -> Option<PersonRef<'a, 'id, '_>> {
self.person
.0
.ro(self.owner)
.children
.get(ix)
.map(|person| PersonRef {
person,
owner: self.owner,
})
}
fn clone_person(&self) -> Person<'a, 'id> {
self.person.clone()
}
}
fn main() {
LCellOwner::scope(|mut owner| {
let tree1 = Person::new("Ruth", 120);
tree1
.with_owner_mut(&mut owner)
.add("Pat", 91)
.add("John", 89);
tree1
.with_owner(&owner)
.get_child(0)
.unwrap()
.clone_person()
.with_owner_mut(&mut owner)
.add("Jim", 65)
.add("Chuck", 65);
tree1
.with_owner(&owner)
.get_child(1)
.unwrap()
.clone_person()
.with_owner_mut(&mut owner)
.add("Stan", 57)
.add("Anne", 55);
tree1
.with_owner(&owner)
.get_child(1)
.unwrap()
.get_child(0)
.unwrap()
.clone_person()
.with_owner_mut(&mut owner)
.add("Mary", 20);
tree1
.with_owner(&owner)
.get_child(1)
.unwrap()
.get_child(1)
.unwrap()
.clone_person()
.with_owner_mut(&mut owner)
.add("Helena", 21)
.add("Peter", 19);
let fifties = tree1.collect(&owner, |p| p.age >= 50 && p.age < 60);
println!("tree1 people in their fifties...");
for p in fifties.iter() {
p.show(&owner);
}
let tree2 = Person::new("Murial", 91);
tree2
.with_owner_mut(&mut owner)
.add("Maya", 55)
.add("Matt", 59);
tree2.0.ro(&owner).children[0]
.clone()
.with_owner_mut(&mut owner)
.add("Julia", 26)
.add("Andria", 28);
tree2.0.ro(&owner).children[0].0.ro(&owner).children[0]
.clone()
.with_owner_mut(&mut owner)
.add("Tom", 2);
println!("Before Swap");
tree1.show_family_tree(&owner);
tree2.show_family_tree(&owner);
let swap_target1 = tree1
.with_owner(&owner)
.get_child(1)
.unwrap()
.get_child(0)
.unwrap()
.clone_person();
let swap_target2 = tree2
.with_owner(&owner)
.get_child(0)
.unwrap()
.get_child(0)
.unwrap()
.clone_person();
print!("swap target 1 is: ");
swap_target1.show(&owner);
print!("swap target 2 is: ");
swap_target2.show(&owner);
let (swap_target1_mut, swap_target2_mut) = owner.rw2(&swap_target1.0, &swap_target2.0);
mem::swap::<PersonData>(swap_target1_mut, swap_target2_mut);
println!("After Swap");
tree1.show_family_tree(&owner);
tree2.show_family_tree(&owner);
});
}
This features a lot of access pattern like
tree1
.with_owner(&owner)
.get_child(1)
.unwrap()
.get_child(1)
.unwrap()
.clone_person()
.with_owner_mut(&mut owner)
.add("Helena", 21)
.add("Peter", 19);
where the top part uses shared-reference access to move down the tree to some person, then it clones the Rc
and ends the shared-reference access, so that it can borrow &mut owner
and do some modification.
This can then be done with something like
// candidates
let fifties1 = tree1.collect(&owner, |p| p.age >= 50 && p.age < 60);
let fifties2 = tree2.collect(&owner, |p| p.age >= 50 && p.age < 60);
// random selection
// TODO: replace the `0` with a (valid) random index
let swap_target1 = fifties1[0].clone();
let swap_target2 = fifties2[0].clone();