Suppose you have a struct with some owned data, and you want to write a constructor which takes ownership of some data, and also provides an alternative view into it. You might want to do this with a graph, say (have the graph represented by a linked list, but also provide a view into the nodes and edges of the graph via references).
With the following, contrived example, I define a struct that owns a String, and has references to the first half of the string, along with the second half. I then try to define a constructor which takes ownership of a String and defines two references to its contents.
pub struct StringAndParts<'a> {
pub s: String,
pub first_half: &'a str,
pub second_half: &'a str,
}
impl<'a> StringAndParts<'a> {
pub fn from_string(s: String) -> Result<StringAndParts<'a>, &'static str> {
if s.chars().count() < 3 {
return Err("StringAndParts::from_string - supplied string must take a string of length at least 2");
}
let halfway_point = s.len() / 2;
let first_half = &s.as_str()[..halfway_point];
let second_half = &s.as_str()[halfway_point..];
Ok(StringAndParts {
s,
first_half,
second_half,
})
}
}
fn main() {
let s = String::from("Hello, world!");
let maybe_sap = StringAndParts::from_string(s.clone());
if let Ok(sap) = maybe_sap {
format!(
"The StringAndParts formed from {} looks like this: {}\n{:?}\n{:?}\n",
s, sap.s, sap.first_half, sap.second_half
);
}
}
However, with cargo build, I get the following errors:
error[E0515]: cannot return value referencing function parameter `s`
--> src/main.rs:17:9
|
15 | let first_half = &s.as_str()[..halfway_point];
| ---------- `s` is borrowed here
16 | let second_half = &s.as_str()[halfway_point..];
17 | / Ok(StringAndParts {
18 | | s,
19 | | first_half,
20 | | second_half,
21 | | })
| |__________^ returns a value referencing data owned by the current function
error[E0515]: cannot return value referencing function parameter `s`
--> src/main.rs:17:9
|
16 | let second_half = &s.as_str()[halfway_point..];
| ---------- `s` is borrowed here
17 | / Ok(StringAndParts {
18 | | s,
19 | | first_half,
20 | | second_half,
21 | | })
| |__________^ returns a value referencing data owned by the current function
error[E0505]: cannot move out of `s` because it is borrowed
--> src/main.rs:18:13
|
7 | impl<'a> StringAndParts<'a> {
| -- lifetime `'a` defined here
...
15 | let first_half = &s.as_str()[..halfway_point];
| ---------- borrow of `s` occurs here
16 | let second_half = &s.as_str()[halfway_point..];
17 | / Ok(StringAndParts {
18 | | s,
| | ^ move out of `s` occurs here
19 | | first_half,
20 | | second_half,
21 | | })
| |__________- returning this value requires that `s` is borrowed for `'a`
Some errors have detailed explanations: E0505, E0515.
For more information about an error, try `rustc --explain E0505`.
My understanding of this error is that the borrow checker isn't happy because s goes out of scope at the end of the function block, and first_half and second_half reference this data. However, as s is also returned as part of the function, I feel that s should still be considered "live". I'm slightly perplexed by the last error, returning this value requires that `s` is borrowed for `'a` --- any further lifetime annotations on the above code don't seem to make sense to me.
Is my understanding of the above errors correct? If so, how can I convince the Rust compiler that the data in first_half and second_half is valid as they are inextricably linked to s? I've tried shoehorning some more lifetimes on the definitions of first_half and second_half, but this doesn't appear to help the situation. Many thanks in advance.