The intention of the code below is to work around the longstanding language limitation where you can't talk about the lifetime of a field of a struct, by making the relevant field be a mutable reference supplied at construction time. Interner::buf is a reference with lifetime 'a, and Interner::map holds keys that point into that buffer, which also have lifetime 'a.
One of the methods throws a compile error that I don't know how to fix and which honestly seems wrong.
Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
--> src/lib.rs:39:19
|
15 | impl<'a> Interner<'a> {
| -- lifetime `'a` defined here
...
27 | pub fn intern(&mut self, s: &str) -> Substr {
| - let's call the lifetime of this reference `'1`
...
39 | let old = self.map.insert(k, ss);
| ^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'a`
|
= note: requirement occurs because of a mutable reference to `HashMap<&str, Substr>`
= note: mutable references are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
error: could not compile `playground` (lib) due to 1 previous error
To the extent I understand what this is trying to tell me (which I'm not sure I do) it seems to be backwards. k is a subslice of self.buf and therefore should have lifetime 'a; ss is reference-free; the lifetime of self and therefore self.map should, if anything, be required to be shorter than the lifetime 'a. Shouldn't it?
Code
use std::collections::HashMap;
use std::mem;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Substr {
pub start: usize,
pub end: usize,
}
pub struct Interner<'a> {
buf: &'a mut String,
map: HashMap<&'a str, Substr>,
}
impl<'a> Interner<'a> {
pub fn new(buf: &'a mut String) -> Interner<'a> {
Self {
buf,
map: HashMap::new(),
}
}
pub fn into_buffer(self) -> String {
mem::take(self.buf)
}
pub fn intern(&mut self, s: &str) -> Substr {
if let Some(ss) = self.map.get(s) {
return *ss;
}
let start = self.buf.len();
self.buf.push_str(s);
let end = self.buf.len();
let ss = Substr { start, end };
let k = &self.buf[start..end];
let old = self.map.insert(k, ss);
assert_eq!(old, None, "should have returned early with '{}' in map", s);
ss
}
}