My failing code (Minimal, Reproducible Example):
// my code
fn test() {
let mut list: Vec<Text> = Vec::new();
const ARRAY: [char; 3] = ['a', 'b', 'c'];
for (i, _) in ARRAY.iter().enumerate() {
list.push(Text::new(&ARRAY[i].to_string()));
}
}
// external crate
#[derive(Debug, Clone, Copy, PartialEq)]
struct Text<'a> {
pub text: &'a str,
}
impl<'a> Text<'a> {
pub fn new(text: &'a str) -> Self {
Text {
text
}
}
}
The compiler: "temporary value dropped while borrowed". Red squiggly lines below
ARRAY[i].to_string()
Classic borrow checker problem, I guess?
I tried to change up the types to be &strs instead of chars and everything worked fine:
// my code
fn test() {
let mut list: Vec<Text> = Vec::new();
const ARRAY: [&str; 3] = ["a", "b", "c"]; // CHANGE HERE
for (i, _) in ARRAY.iter().enumerate() {
list.push(Text::new(&ARRAY[i])); // CHANGE HERE
}
}
Can't figure out what's so special about char or the conversion with to_string() that makes this code fail.
CodePudding user response:
Your failing code is equivalent to this one:
for a in &ARRAY {
let temp = a.to_string();
list.push(Text::new(&temp));
drop(temp);
}
Except that the temporary variable has a name in my code (the call to drop() is redundant, I added it for clarity).
So all the temporaries created during the loop are being added as references to the list, remember that Text holds a reference, not the string itself. But these temporaries do not survive the iteration where they are created, so your list contains dangling references. Rust detects that and fails to compile.
The easy solution would be to modify Text to hold the String itself, instead of a reference. If you cannot modify that, then your strings have to live somewhere, you can build a Vec<String> and get the references from there:
fn test() {
const ARRAY: [char; 3] = ['a', 'b', 'c'];
let mut tmps: Vec<String> = Vec::new();
for a in &ARRAY {
tmps.push(a.to_string());
}
let list: Vec<Text> = tmps
.iter()
.map(|s| Text::new(s))
.collect();
}
Your second code works simply because now you array contains static references &'static str so there is no temporary variables anywhere.
CodePudding user response:
What's special is that to_string() creates an owned string, whereas "..." gives you a &'static str. The owned string has a clear life cycle which ends at the end of its scope, when it deallocates the memory where the data is stored. You attempt to store references to such strings and hold on to them after the string has already been deallocated.
The Text type from the external crate is designed to borrow data owned by someone else. If you can't change that, you'll simply need to make sure that you keep the owned values around for at least as long as the Text values. For example:
fn test() {
let mut list: Vec<Text> = Vec::new();
const ARRAY: [char; 3] = ['a', 'b', 'c'];
// keep the strings live
let strings: Vec<String> = ARRAY.iter().map(|c| c.to_string()).collect();
// `Text`s in `list` refer to strings in `strings`
for s in &strings {
list.push(Text::new(s));
}
}
