Structs, closures, references and Strings, oh my!


#1

Disclaimer: This is my first day with Rust, so be gentle :slight_smile:

I’m trying to wrap my head around Rusts ownership and borrowing details. I’m coming from a more functional background and I haven’t touched C/C++ in some time, so the & and * syntax instantly gives me the heebie-jeebies.

Anyway, I’m using the struct update notation inside a map-closure:

struct Foo {
    id:   u32,
    attr: u32,
}

fn main() {
    let v = vec![Foo {id: 1, attr:42},
                 Foo {id: 2, attr:43}];

    let v2 = v.iter().map(|x| Foo {attr: x.attr+1, .. *x}).collect::<Vec<Foo>>();
}

Works as expected. Great.
… however, if I change the type of the id field to String:

struct Foo {
    id:   String,
    attr: u32,
}

fn main() {
    let v = vec![Foo {id: String::from("Hello"), attr:42},
                 Foo {id: String::from("World"), attr:43}];

    let v2 = v.iter().map(|x| Foo {attr: x.attr+1, .. *x}).collect::<Vec<Foo>>();
}

the compiler complains about borrowing issues:

let v2 = v.iter().map(|x| Foo {attr: x.attr+1, .. *x}).collect::<Vec<Foo>>();
                                                  ^^ cannot move out of borrowed content

Could someone enlighten me why the first example works but the second doesn’t?


#2

One solution is to duplicate the strings:

struct Foo {
    id: String,
    attr: u32,
}

fn main() {
    let v = vec![Foo {id: "Hello".into(), attr: 42},
                 Foo {id: "World".into(), attr: 43}];

    let v2: Vec<_> = v
                     .iter()
                     .map(|x| Foo {id: x.id.clone(), attr: x.attr + 1})
                     .collect();
}

In Rust the Strings are structs that contain a pointer. Generally to make the efficiency of the code more visible, Rust asks you to specify if you want to copy the memory pointed by those pointers. This means it doesn’t copy the strings by default.


#3

The method v.iter() borrows v and gives an iterator over borrows of v's elements, while v.into_iter() takes ownership of v and gives an iterator over v's elements directly.

Since v.iter() is only borrowing v, after the call v is still perfectly valid to use. The strings that were owned by v before the call are going to continue to be owned by v. The map(...) closure you wrote tries to take the String out of x (which is borrowed from v) and give ownership of it to the new Foo in v2. It isn’t allowed to because v still owns those strings. This is what the error message is saying: x is borrowed, so we can’t give ownership of Strings from x over to something else.

It worked before for primitive types like u32 (and in general, types that implement the Copy trait) because those are so cheap to copy that the compiler doesn’t worry about it. Strings might be expensive to copy so the compiler doesn’t silently copy them for you.

Your options are:

  • Clone the strings, as in @leonardo’s comment. The old v will continue to own the original Strings and the new v2 will own its own copy of the Strings.
  • Transfer ownership of the Strings from v to v2 by using into_iter() instead of iter(). In this case v can no longer be used because the same Strings are now owned by v2.

#4

The underlying reason is that i32 is Copy, but String is not. Copy types are allowed to be freely copied. So when you have a reference to a Copy type, you can freely just copy what it refers to.

With something like String, though, you can’t just freely make the copy; doing so would cause problems. The new book goes into this: http://rust-lang.github.io/book/ch04-01-what-is-ownership.html

Since you’re already making new copies of Foo here, rather than modifying them in place, @leonardo’s suggestion makes sense. And it’s what those languages you’re used to would be doing anyway.

If you didn’t want to be making all of the copies you could use into_iter, which instead provides ownership over the contents of the Foos in v. This would let you move the Strings into your new Foos, removing the deep copy.

EDIT: Ah! @dtolnay said what I said, but wrote it while I was typing. Well, there’s two different explanations of the same thing :smile:


#5

Thanks guys, that makes total sense. It didn’t came to my mind that Strings don’t have a copy trait.

Concerning the book: I’m currently reading the old (?) book what for learning the ropes. Is this still the way to go or should I switch to the github one?


#6

The old book is more complete, but not as good. The new book is much
better, but not complete. So, it depends :slight_smile: