I'm new to Rust and am proud to have finally hit my first impasse with the borrow checker. Here's a pared-down example that's giving me trouble.
I am processing a futures::stream::Stream
of tweets from Twitter. Each tweet is a Tweet
struct containing the tweet's id
and text
. For each tweet I would like to perform an analysis of its text
, which starts by breaking it up into a vector of Part
s. Each Part
references a slice of the original tweet text
and categorizes it as either a "word" or "whitespace" using an enum. Finally, I would like to map this stream of Tweets
to a tuple containing the tweet and its text's analysis.
It is a design goal to keep exactly one copy of the tweet's text
around, partly for memory-efficiency and partly to practice running into ownership issues like this one, so I am not interested in utilizing clones or copies. I am also feeling-out the ability to write modular code with Rust, so I'm very interested in solutions that speak to better design choices. In particular, I hoped to implement the annotated::to_parts
function shown below in a reusable way, but my approach may have caused issues.
Finally, here are the relevant bits of code,
// src/main.rs
tweet_stream.map(|tweet| {
let parts = annotated::to_parts(&tweet.text);
(tweet, parts)
})
// ...
// src/annotated.rs
pub fn to_parts(string: &str) -> Vec<Part> {
// ...
}
#[derive(Debug, PartialEq)]
pub enum Part<'a> {
Word(&'a str),
Whitespace(&'a str),
}
// src/twitter.rs
#[derive(Deserialize, Debug)]
pub struct Tweet {
#[serde(rename = "id_str")]
pub id: String,
pub text: String
}
And here are my errors,
error[E0515]: cannot return value referencing local data `tweet.text`
--> src/main.rs:62:13
|
60 | let parts = annotated::to_parts(&tweet.text);
| ----------- `tweet.text` is borrowed here
61 |
62 | (tweet, parts)
| ^^^^^^^^^^^^^^ returns a value referencing data owned by the current function
error[E0505]: cannot move out of `tweet` because it is borrowed
--> src/main.rs:62:14
|
52 | .map(|tweet| {
| - return type of closure is (twitter::Tweet, std::vec::Vec<annotated::Part<'1>>)
...
60 | let parts = annotated::to_parts(&tweet.text);
| ----------- borrow of `tweet.text` occurs here
61 |
62 | (tweet, parts)
| -^^^^^--------
| ||
| |move out of `tweet` occurs here
| returning this value requires that `tweet.text` is borrowed for `'1`
My intuition tells me that this should be allowed because the Tweet
and the Part
s that reference the slices of tweet.text
are returned together from the closure, so their lifetimes should be compatible. My intuition also tells me that I should be able to write a function for splitting a string into parts, irrelevant to tweets, and use it on a field of a struct without running into issues. But I'm definitely missing something. I appreciate any thoughts or help!