Iterating through a string owned by self while mutating self with a separate function

I'm trying to get some code that looks something like this to work:

struct Item {
    document: String,
    appender_string: String,
    doc_vec: Vec<String>
}

impl Item {
    pub fn new(doc: String, app: String) -> Item {
        Item{document: doc, appender_string: app, doc_vec: Vec::new()}
    }
    
    pub fn load_doc(&mut self) {
        for line in self.document.lines() {
            self.append(line);
        }
    }
    
    fn append(&mut self, line: &str){
        let mut l = line.to_string();
        l.push_str(&*self.appender_string);
        self.doc_vec.push(l);
    }
}

fn main() {
    let mut i = Item::new("test\nthis\nthing\n".to_string(), "!".to_string());
    i.load_doc();
    println!("{:?}", i.doc_vec);
}

Which gives me the following error:

<anon>:14:13: 14:17 error: cannot borrow `*self` as mutable because `self.document` is also borrowed as immutable
<anon>:14             self.append(line);
                      ^~~~
<anon>:13:21: 13:34 note: previous borrow of `self.document` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `self.document` until the borrow ends
<anon>:13         for line in self.document.lines() {
                              ^~~~~~~~~~~~~
<anon>:16:6: 16:6 note: previous borrow ends here
<anon>:13         for line in self.document.lines() {
<anon>:14             self.append(line);
<anon>:15         }
<anon>:16     }
              ^
error: aborting due to previous error
playpen: application terminated with error code 101

I understand the issue here, you can't borrow the different fields in self independently, but I was hoping to structure my code something like this. Is there a good way of doing what I'm describing? The key in this example is that both document and doc_vec should be owned by the Item struct.

Frustratingly, this works just fine:

struct Item {
    document: String,
    appender_string: String,
    doc_vec: Vec<String>
}

impl Item {
    pub fn new(doc: String, app: String) -> Item {
        Item{document: doc, appender_string: app, doc_vec: Vec::new()}
    }
    
    pub fn load_doc(&mut self) {
        for line in self.document.lines() {
            let mut l = line.to_string();
            l.push_str(&*self.appender_string);
            self.doc_vec.push(l);
        }
    }
}

fn main() {
    let mut i = Item::new("test\nthis\nthing\n".to_string(), "!".to_string());
    i.load_doc();
    println!("{:?}", i.doc_vec);
}

But in the real-world case I'm trying to work with both append and load_doc are much longer functions, which would make this solution unwieldy. Is there a way I can get this to work as two separate functions?

Besides this specific case I frequently find that I have to compromise abstraction to get lifetimes and/or borrowing to come together properly, does anyone have any good general advice for this?

2 Likes

I have the same problem. When I refacroring code and split it to functions and method got trables

append here doesn't look like a method on Item but rather on Vec<String>. How about this?

struct Item {
    document: String,
    appender_string: String,
    doc_vec: Vec<String>
}

trait Appnd {
    fn appnd(&mut self, line: &str, app: &str);
}

impl Appnd for Vec<String> {
    fn appnd(&mut self, line: &str, app: &str) {
        let mut l = line.to_string();
        l.push_str(app);
        self.push(l);
    }
}

impl Item {
    pub fn new(doc: String, app: String) -> Item {
        Item{document: doc, appender_string: app, doc_vec: Vec::new()}
    }
    
    pub fn load_doc(&mut self) {
        for line in self.document.lines() {
            self.doc_vec.appnd(line, &self.appender_string);
        }
    }
}

fn main() {
    let mut i = Item::new("test\nthis\nthing\n".to_string(), "!".to_string());
    i.load_doc();
    println!("{:?}", i.doc_vec);
}
1 Like

Huh, that makes sense, and would probably work for me. Is this idiomatic Rust? Also the Appnd trait and its implementation for Vec<String> would not be visible outside this module, right? And finally, this implies that you can borrow multiple fields on a struct separately (in this case, borrowing self.doc_vec mutably while doing an immutable borrow of self.appender_string), is that always the case?

[quote="reklawnos, post:4, topic:832"]
Is this idiomatic Rust?
[/quote]No idea. At first I wanted to make append a free function but then figured, why not a method.

[quote="reklawnos, post:4, topic:832"]
Also the Appnd trait and its implementation for Vec<String> would not be visible outside this module, right?
[/quote]Yep, same as the old append.

[quote="reklawnos, post:4, topic:832"]
is that always the case?
[/quote]Mm, I don't see why not.

1 Like

Do you want the load_doc action to be a one-time thing? If so, an easy solution with the same API would be to wrap the document as an Option<String> and have load_doc do self.document.take().unwrap().lines() instead. Then you'd have an assertion that load_doc wasn't called before. Or you can use try!(self.document.take()).lines() and return a Result<>. Note: Although more wordy, I'd go for the Result<> version, unless this is a module-internal type and an assertion would be more appropriate.

In general though, I think I'd use two types if the situation allows for it. One for the Document and one for the produced Item. And then have fn load(self) { ... } unpack the document and produce the item.