I recently ran into a situation where after tearing my hair out for some time saying "Why won't the borrow checker let me do this!?" I found a solution — but I'm now wondering "Why does the borrow checker let me do this?"
Here's a simplified version: If text
is a &str
reference to the contents of a text file, we want to update a HashSet
by inserting the lines of the file — but sometimes we want a HashSet<&'a str>
of string slices borrowed from text
and sometimes a HashSet<String>
of heap-allocated copies of those slices. I defined a trait to allow implementation sharing. (In the larger problem we also want operations other than insertion.)
Two things puzzle me about the solution below. First, I'm not sure why it's OK to say
impl<'a> LineSet<'a> for VecSet { ... }
I'd been under the assumption that the 'a
in LineSet<'a>
referred to the lifetime of the LineSet
. Is it simply a lifetime parameter that can be used fairly arbitrarily in the trait and impl definitions?
Second, I wondered why it's OK to say:
impl<'a> LineSet<'a> for SliceSet<'a> {
fn update_with(&mut self, line: &'a str) {
self.insert(line);
}
}
Here the lifetime of line
could be longer than the lifetime of self
, rather than identical to it. I'd expected to need this:
fn update_with<'b: 'a>(&mut self, line: &'b str)
This second version, explicitly specifying the larger lifetime, also works. Does the compiler desugar the first version into the second? What are the rules about such desugaring (if that's in fact what's happening)?
[Edit: I'd mistakenly used the playground version with <'b: 'a>
, but meant to use the one without.)
(Playground)
use std::collections::HashSet;
trait LineSet<'a> {
fn update_with(&mut self, line: &'a str);
fn update_with_all(&mut self, text: &'a str) {
for line in text.lines() {
self.update_with(line);
}
}
}
type SliceSet<'a> = HashSet<&'a str>;
impl<'a> LineSet<'a> for SliceSet<'a> {
fn update_with(&mut self, line: &'a str) {
self.insert(line);
}
}
type VecSet = HashSet<String>;
impl<'a> LineSet<'a> for VecSet {
fn update_with(&mut self, line: &'a str) {
self.insert(String::from(line));
}
}
fn contents() -> String { String::from("abc\ndef\n") }
fn main() {
let saved = contents();
let mut slice_set = SliceSet::default();
slice_set.update_with_all(saved.as_str()); // `saved` outlives `slice_set`
// slice_set.update_with_all(contents().as_str()); // `contents().as_str()` does not
println!("{:?}", slice_set);
let mut vec_set = VecSet::default();
vec_set.update_with_all(contents().as_str());
println!("{:?}", vec_set);
}