Rust: 2, Rob: 0
Feb 4, 2021I’m starting to play around with some Rust programming in my spare time. I’m enjoying it so far, but these two things were tripping me up for a while. Lets say you have an API call which returns you an immutable vector. The API call is from a REST API; the results are paginated. So every time you call this function you end up with another vector. In many cases you just want to append these all together and operate on the whole dataset once you have the full collection. In most languages you might create an empty vector before starting your loop and then append the resulting vector on each iteration of the loop. C++ makes this a bit complicated, since insert requires three iterators:
#include <vector>
#include <iostream>
struct Person{
int age;
};
int main() {
std::vector<Person> all_people{ {34}, {35}, {36} };
// I know this is kind of a dumb example, this closure's kind of pointless in this simple case
auto append_people = [&all_people](const std::vector<Person>& some_people){
all_people.insert(std::end(all_people), std::begin(some_people), std::end(some_people));
};
const std::vector<Person> other_people{ {37}, {38}, {39} };
append_people(other_people);
for(auto &person : all_people) {
std::cout << "Age:" << person.age << std::endl;
}
}
I’m used to C++; stuff like this doesn’t bother me anymore, even though I’ve seen better APIs in the standard libraries of other languages. It took me a lot of searching and experimenting before I realized how to even get this to compile in Rust! There were two things I was missing.
use std::vec;
pub fn main() {
struct Person{
age : i32,
}
let mut all_people : Vec<Person> = vec![Person{age: 34}, Person{age: 35}, Person{age: 36}];
// I know this is kind of a dumb example, this closure's kind of pointless in this simple case
let mut append_people = |some_people : Vec<Person>| {
all_people.extend(some_people);
};
let other_people : Vec<Person> = vec![Person{age: 37}, Person{age: 38}, Person{age: 39}];
append_people(other_people);
for person in all_people {
eprintln!("Age: {} ", person.age);
}
}
In Rust if your closure implicitly pulls a variable like all_people into its scope and modifies it, then it seems like your closure must be marked as mutable. Here’s what you get when you neglect to do this.
15 | append_people(other_people);
| ^^^^^^^^^^^^^ cannot borrow as mutable
This isn’t terribly surprising, but I didn’t come across anything mentioning this in the Rust Book. Here’s the other thing that was tripping me up. I was trying to use .append(), but that only works with mutable values. Here’s what you get in that case:
11 | all_people.append(some_people);
| ^^^^^^^^^^^
| |
| expected `&mut Vec<Person>`, found struct `Vec`
| help: consider mutably borrowing here: `&mut some_people`
The API I was calling does not return a mutable value, so I had to use .extend() instead.
In most ways this is simpler and less verbose than the C++ implementation, you just have to get used to interpreting the errors the Rust compiler is throwing at you. In the end you get some very nice guarantees that seem well worth the hassle.