1

I'm trying to implement a trie but the borrow checker is really giving me a hard time:

struct Node {
    // a trie node
    value: char,
    children: Vec<Node>,
}

impl Node {
    fn add_child(&mut self, value: char) -> &mut Node {
        // adds a child to given node
        let vec: Vec<Node> = Vec::new();
        let node = Node {
            value,
            children: vec,
        };
        self.children.push(node);
        self.children.last_mut().unwrap()
    }

    fn get_child(&mut self, value: char) -> Option<&mut Node> {
        // checks if given node has a child with given value, returns the child if it exists
        for child in self.children.iter_mut() {
            if child.value == value {
                return Some(child);
            }
        }
        None
    }

    fn has_child(&self, value: char) -> bool {
        for child in self.children.iter() {
            if child.value == value {
                return true;
            }
        }
        false
    }

    fn add_word(&mut self, word: String) {
        let mut cursor = self;
        for c in word.chars() {
            match cursor.get_child(c) {
                Some(node) => cursor = node,
                None => cursor = cursor.add_child(c),
            }
        }
        cursor.add_child('~');
    }
}

The add_word method gives these 5 errors:

error[E0499]: cannot borrow `*cursor` as mutable more than once at a time
  --> src/main.rs:41:19
   |
41 |             match cursor.get_child(c) {
   |                   ^^^^^^ mutable borrow starts here in previous iteration of loop
...
47 |     }
   |     - mutable borrow ends here

error[E0506]: cannot assign to `cursor` because it is borrowed
  --> src/main.rs:42:31
   |
41 |             match cursor.get_child(c) {
   |                   ------ borrow of `cursor` occurs here
42 |                 Some(node) => cursor = node,
   |                               ^^^^^^^^^^^^^ assignment to borrowed `cursor` occurs here

error[E0506]: cannot assign to `cursor` because it is borrowed
  --> src/main.rs:43:25
   |
41 |             match cursor.get_child(c) {
   |                   ------ borrow of `cursor` occurs here
42 |                 Some(node) => cursor = node,
43 |                 None => cursor = cursor.add_child(c),
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment to borrowed `cursor` occurs here

error[E0499]: cannot borrow `*cursor` as mutable more than once at a time
  --> src/main.rs:43:34
   |
41 |             match cursor.get_child(c) {
   |                   ------ first mutable borrow occurs here
42 |                 Some(node) => cursor = node,
43 |                 None => cursor = cursor.add_child(c),
   |                                  ^^^^^^ second mutable borrow occurs here
...
47 |     }
   |     - first borrow ends here

error[E0499]: cannot borrow `*cursor` as mutable more than once at a time
  --> src/main.rs:46:9
   |
41 |             match cursor.get_child(c) {
   |                   ------ first mutable borrow occurs here
...
46 |         cursor.add_child('~');
   |         ^^^^^^ second mutable borrow occurs here
47 |     }
   |     - first borrow ends here

This is the Go code that I was trying to translate:

func (n *trieNode) AddWord(word string) {
cursor := n
for i := 0; i < len(word); i++ {
    if cursor.HasChild(byte(word[i])) == nil {
        cursor = cursor.AddChild(byte(word[i]))
    } else {
        cursor = cursor.HasChild(byte(word[i]))
    }
}
// tilde indicates the end of the word
cursor.AddChild(byte('~'))
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Sarp Başaraner
  • 505
  • 7
  • 17
  • Time for NLL :-) – Malice Jan 23 '18 at 11:20
  • [Still doesn't compile on nightly with `#![feature(nll)]`](https://play.rust-lang.org/?gist=034570c3b03dc03d5834217691300ff9&version=nightly). Not sure if this is a weakness in the current implementation or if the code is subtly incorrect. – trent Jan 23 '18 at 11:28
  • @Malice what is it? – Sarp Başaraner Jan 23 '18 at 11:33
  • @trentcl I see that my problem is marked as problem case #4 there, maybe I should just quit trying and wait for the language to get updated? I'm only interested in rust for recreational purposes anyway. – Sarp Başaraner Jan 23 '18 at 11:49
  • 1
    @trentcl I'd say weakness. The block works fine in a separate `get_or_add_child(&mut self, value: char) -> &mut Node` method (which is also easy to implement without NLL, so I'd recommend going for that - same as in my answer in the linked duplicate). – Stefan Jan 23 '18 at 11:51
  • The duplicate applied to your situation [with NLL](https://play.rust-lang.org/?gist=dd350569456fa8e178ca9aeedd364ffb&version=nightly) @Stefan you want to chime in with the non-NLL solution? – Shepmaster Jan 23 '18 at 13:58
  • @Shepmaster I must be missing something - I can't get it working. I may have been wrong with my duplicate suggestion. It seems without NLL the early return still blocks the lifetime in other paths, as the returned value uses the lifetime too (the linked question doesn't have a return value). – Stefan Jan 23 '18 at 14:36
  • The second duplicate applied to your situation, [without NLL](https://play.rust-lang.org/?gist=13686fc99b2f44cf1f0cd4c9831313e0&version=stable). This requires a bit of inefficiency because we have to use an index instead of the reference. We are all very excited for [Non-Lexical Lifetimes](https://github.com/rust-lang/rfcs/blob/master/text/2094-nll.md) to land! – Shepmaster Jan 23 '18 at 14:57
  • 1
    @Stefan OK, I've added another duplicate which uses indices to work around it. Seem reasonable? In the future, I recommend solving the problem using the suggested duplicate(s). This lets us be sure that it is a duplicate and then we can paste it in as a comment to help the OP see how it works. – Shepmaster Jan 23 '18 at 15:05
  • @SarpBaşaraner *maybe I should just quit trying and wait for the language to get updated? I'm only interested in rust for recreational purposes anyway.* — I love Rust and am biased, but I think there's lots of good things to use and learn right now. There are some annoying cases (of which is this one!) but the community is working on making things better. If you just want to play with Rust, you can even think about using a nightly compiler and enabling the NLL feature to get a jump on the future ;-) – Shepmaster Jan 23 '18 at 15:08
  • *The block works fine in a separate `get_or_add_child(&mut self, value: char) -> &mut Node` method* — I've opened https://github.com/rust-lang/rust/issues/47680 for this. – Shepmaster Jan 23 '18 at 15:41
  • 1
    @Shepmaster well with all the help I'm getting at least its community seems amazing! I will keep tinkering with Rust I guess. Also, for years I've tried to make a meaningful contribution to any major FOSS, it'll be very interesting if this problem that I didn't even comprehend fully turns out to affect stuff significantly :) – Sarp Başaraner Jan 23 '18 at 16:10

0 Answers0