Not sure how to convert NodeMut to NodeRef with ego_tree

I'm trying to use the ego_tree crate and I'm getting an error, but I'm not sure how to fix it.
Also, I seem to be getting different errors for the same problem, depending on where my code is located.

I wrote a function accepting a NodeMut as a parameter, and I want to get the non-mutable NodeRef. NodeMut seems to implement the Into trait, which has the into function which does this, but I get compile errors when I try to call it.

I've simplified my problem to this:

extern crate ego_tree;
use std::path::PathBuf;
use ego_tree::{Tree, NodeMut, NodeRef};
enum DirEntry {
    Folder(PathBuf),
    File { path: PathBuf, content: String },
}

fn f() {
    let mut t = Tree::new(DirEntry::Folder(PathBuf::new()));
    let mut nodemut = t.root_mut();
    let node : NodeRef<DirEntry> = nodemut.into();
    g(&mut nodemut);
}

fn g(parent_dir: &mut NodeMut<DirEntry>) {
    let node : &NodeRef<_> = parent_dir.into();
}

I get this error with the line in g():

error[E0277]: the trait bound &ego_tree::NodeRef<'_, _>: std::convert::From<&mut ego_tree::NodeMut<'_, DirEntry>> is not satisfied

So, since NodeRef doesn't implement From, is there any way to use the into method at all?

The second thing is, that if I got an entirely different error message when I tried the same thing in my original code:
A little more realistic version of my original code is:

fn create(parent_dir: &mut NodeMut<DirEntry>) {
    // Next line gets me the error:
    //  "the type of this value must be known in this context"
    let noderef = parent_dir.into() as &NodeRef<DirEntry>;
    let val = noderef.value();
    if let &DirEntry::Folder(ref parent_path) = val {
        // Do something with parent_path
        let name = "abc";
        parent_dir.append(DirEntry::File { path: parent_path.join(name), content: String::from("") });
    }
}

However, in this code, I get the different error "the type of this value must be known in this context" on what I think is a line doing the same thing, even though I specified what I want to convert the return type to. I'm mentioning this because I was stuck with this error for several hours.
I'm sure it's something I don't understand about the language, but what's the difference between the problematic line in g() and in create()?

You can't get a NodeRef from a reference to a NodeMut; NodeMut implements Into<NodeRef> for NodeMut. That is, you can convert a NodeMut into a NodeRef, consuming the NodeMut value in the process.

You should treat NodeRef like an immutable reference (can have many of them at the same time, copy them, etc) and a NodeMut like a mutable reference (only one at a time, cannot copy it, only move it).

So, your create function should not try to get a NodeRef from the NodeMut. Instead, it should just work off the NodeMut itself. When you pattern match against the val you have to structure the code to avoid non-lexical lifetime issues (i.e. make sure the val binding is out of scope when you go to insert into the parent_dir.

Thanks, that's just what I was trying to do: trying to avoid the non-lexical lifetime issues, by using NodeRef.
I forgot to mention it, but you figured it anyway....

I tried to do what you suggested, but I honestly can't figure out how to structure my code to avoid the "cannot borrow as mutable more than once" errors, without the following somewhat ugly tricks:

  1. Creating an emtpy mut PathBuf just to make the compiler happy
  2. Using .clone() on the parent_path.

fn create(parent_dir: &mut NodeMut) {
    let mut path = PathBuf::new();
    {
        let val = parent_dir.value();
        if let &mut DirEntry::Folder(ref parent_path) = val {
            path = parent_path.clone();
        }
    }
    // Do something with parent_path
    let name = "abc";
    parent_dir.append(DirEntry::File { path: path.join(name), content: String::from("") });
}

This... looks wrong. I have a feeling I must be really missing something here with Rust. Is it always this difficult dealing with mutable tree data structures in the language?

The "trick" to avoiding NLL is to let the first borrow die (or not be active) when you want to borrow the second time. In this particular case, you can probably structure the code as (or any variation of it so long as the DirEntry borrow is gone:

let path = if let &mut DirEntry::Folder(ref parent_path) = parent_dir.value() {
        let name = "abc";
        Some(parent_path.join(name))
    } else {
        None
    };
path.map(|p| parent_dir.append(DirEntry::File { path: p, content: String::from("") }));

This assumes you don't want to append anything if parent_dir.value() is not a DirEntry::Folder. You can of course adjust this to do something else in the None case other than ignore the operation.

The above works because the mutable borrow is active only during the if let part; since we return a new PathBuf (i.e. Some(parent_path.join(name)), we're now "detached" from the DirEntry::File reference.

1 Like

Thanks! That really helped.

Would this sort of thing be taken care of with this RFC? So I wouldn't have to do tricks like that?
https://github.com/rust-lang/rfcs/pull/2094

Yes, I think it should take care of it. Basically any place where a borrow is still active purely due to lexical issues (rather than being truly borrowed, of course) should go away.