1

I'm playing around with ASTs in Rust, and one thing which I thought would be neat would be if all nodes could simply contain slices into the original source string, rather than copying memory around. However, I can't quite work out how I should structure the lifetimes in my code such that this goal is achieved. My naive attempt yielded this program:

#[derive(Debug)]
struct Node<'source> {
    slice   : &'source str,
    nid     : usize       ,
    parent  : usize       ,
    children: Vec<usize>  ,
}

#[derive(Debug)]
struct Tree<'source> {
    source: String            ,
    nodes : Vec<Node<'source>>,
}

impl<'source> Tree<'source> {
    fn new() -> Self {
        Tree {source: String::from("Hello World"), nodes: vec![]}
    }

    fn slice(&'source mut self, from: usize, to: usize) {
        let new_nid = self.nodes.len();
        self.nodes.push(
            Node { slice: &self.source[from..to], nid: new_nid, parent: 0, children: vec![] }
        );
    }
}


fn main() {
    let mut tree = Tree::new();
    tree.slice(2, 6);
    println!("{:?}", tree);
}

Compiler Output:

error[E0502]: cannot borrow `tree` as immutable because it is also borrowed as mutable
  --> ast.rs:32:22
   |
31 |     tree.slice(2, 6);
   |     ---- mutable borrow occurs here
32 |     println!("{:?}", tree);
   |                      ^^^^ immutable borrow occurs here
33 | }
   | - mutable borrow ends here

This does not compile because:

  • The slices contained by the nodes need to outlive the slice() function call

  • Therefore, the mutable reference to self taken by slice() needs to outlive the slice() function call.

  • Therefore, tree remains mutably borrowed after the tree.slice() call in the main function.

  • Therefore, the println! macro cannot compile.

Ideally, the slices within the nodes need to be able to outlive the slice() call, but the mutable reference self should not. I understand that this is forbidden by the borrow checker, as self contains the source string, into which the slices reference.

With this in mind, how can I restructure my code to allow nodes to contain slices into source, without having to borrow self for longer than the length of the slice function? Is this feasible at all, or need I resort to unsafe?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Jack Byrne
  • 21
  • 3
  • [Why can't I store a value and a reference to that value in the same struct?](https://stackoverflow.com/q/32300132/155423) – Shepmaster Mar 20 '18 at 21:14
  • *would be if all nodes could simply contain slices into the original source string* — This is a very effective parsing technique (which I use in almost all of my parsers), but this is not what your code does. – Shepmaster Mar 20 '18 at 21:43

1 Answers1

1

The simplest solution to your problem is to store a std::ops::Range<usize> instead of a &str. Then when you need to look at the actual string, provide a method on your AST that takes a Range<usize> and returns a &str.

Another approach is to not own the original String and instead tie your AST to the lifetime of the string it was parsed from, e.g.,

#[derive(Debug)]
struct Tree<'source> {
    source: &'source str,
    nodes : Vec<Node<'source>>,
}

You can't do it the way you have it setup because Rust doesn't know how to check sibling borrows. If you google around for the sibling borrow problem, you'll get more context.


Source: reddit post

Jack Byrne
  • 21
  • 3