Cześć, ćwiczebnie próbuję napisać klasę, która zapewni dopasowywanie stringów. Ze względu na naukę, chciałbym w miarę możliwości używać referencji &str. Dopóki robię proste, sekwencyjne dopasowywanie wszystko jest łatwo. Mam po prostu w strukturze referencję do tego co zostało mi do przeczytania i nie mam z tym problemów. Problem pojawia się gdy chcę powtórzyć wielokrotnie to samo dopasowanie. Jedyny pomysł, jaki przyszedł mi do głowy to stworzyć metodę, która przyjmie funkcję/lambdę robiącą poszczególne działania i stworzę iterator, który będzie robić co trzeba. Ostatecznie, chciałbym uzyskać taką składnię:
let mut it = matcher("foo=1 bar=2 coo zza=3")
.many(|m1|
m1.until_word()
.const_str("=")
.merge::<u64,(&str, u64),_>(|name, val| (name,val)));
Wszystko działa dobrze, dopóki nie próbuję zwracać referencji na stringa, bo wtedy kompilator marudzi, że nie może mi zagwarantować, że ta referencja nie przeżyje obiektu, na który pokazuje. Tak więc jeśli zwracam cokolwiek innego niż referencję (liczby, String, etc) jest OK i jest to jakieś rozwiązanie. Z drugiej strony nie wydaje mi się to rozsądne, bo przecież jest dla mnie oczywiste, że nie mam się czego obawiać, a alokować miejsce na stercie ilekroć chcę pokazać kawałek tekstu z iteratora wydaje się być co najmniej nieładne i niefektywne. Tak wiec pewnie ten problem ma dobre rozwiązanie o którym nie wiem.
Coś przeoczyłem, że kompilator nie jest w stanie podążyć za cyklem życia tych referencji?
Powinienem inaczej użyć anotacji lifetimeów (wszędzie praktycznie jest 'a -> 'a, więc teoretycznie chyba mógłby załapać…)?
Może jest jakiś magiczny obiekt, który w magiczny sposób odróżni taki bezpieczny przypadek jak mam tutaj, a potencjalnie niebezpiecznym przypadkiem kiedy rzeczywście referencja przeżyje to na co pokazuje.
Poglądowo wklejam kod użytych tu klas:
trait Matchable {
type Out;
fn scan(inp: &str) -> Option<(&str, Self::Out)>;
}
impl Matchable for u64 {
type Out=u64;
fn scan(inp: &str) -> Option<(&str, u64)> {
let it = inp.chars();
let mut ans : u64 = 0;
let mut i = 0;
for c in it {
match c.to_digit(10) {
Some(x) => {
ans = ans*10 + x as u64;
i += 1;
},
None => break
}
}
if i == 0 {
None
} else {
Some((&inp[i..], ans))
}
}
}
#[cfg(test)]
struct MatcherLoop<'a, R, F>
where
F: Fn(Matcher<()>) -> Matcher<R>,
{
tail: Option<&'a str>,
f: F,
}
#[cfg(test)]
impl<'a, R, F> Iterator for MatcherLoop<'a, R, F>
where
F: Fn(Matcher<()>) -> Matcher<R>,
{
type Item = R;
fn next(&mut self) -> Option<Self::Item> {
let tail = self.tail.take();
let m = (self.f)(matcher(tail?));
self.tail = Some(m.tail);
m.val
}
}
struct Matcher<'a,T> {
tail: &'a str,
val: Option<T>
}
impl<'a,T> Matcher <'a,T> {
fn then<F, R> (self, f: F) -> Matcher<'a, R>
where F: Fn(&'a str, T) -> Matcher<'a, R> {
match self.val {
Some(v) => f(self.tail, v),
None => Matcher { tail: self.tail, val: None }
}
}
#[cfg(test)]
fn after(mut self, refs: &'a str) -> Self {
let mut finder = matcher(self.tail);
while finder.tail.len() > refs.len() {
finder = finder.const_str(refs);
if finder.val.is_some() {
self.tail = finder.tail;
return self;
}
finder.tail = &finder.tail[1..];
finder.val = Some(());
}
self.val = None;
return self;
}
#[cfg(test)]
fn until_word(self) -> Matcher<'a, &'a str> {
let mut cur = self.tail;
let mut ans;
while cur.len() > 0 {
ans = matcher(cur).word();
if ans.val.is_some() {
return ans;
}
cur = &cur[1..];
}
Matcher { tail: self.tail, val: None }
}
#[cfg(test)]
fn until<R: Matchable>(self) -> Matcher<'a, <R as Matchable>::Out> {
let mut cur = self.tail;
let mut ans;
while cur.len() > 0 {
ans = matcher(cur).value::<R>();
if ans.val.is_some() {
return ans;
}
cur = &cur[1..];
}
Matcher { tail: self.tail, val: None }
}
#[cfg(test)]
fn many<R,F>(self, f:F) -> MatcherLoop<'a,R,F>
where F: Fn(Matcher<()>) -> Matcher<R> {
MatcherLoop { tail: Some(self.tail), f }
}
#[cfg(test)]
fn word(self) -> Matcher<'a, &'a str> {
let mut i: usize = 0;
let mut cur = self.tail.chars();
while cur.next().map(|c| c.is_alphabetic()).unwrap_or(false) {
i += 1;
}
if i > 0 {
Matcher { tail: &self.tail[i..], val: Some(&self.tail[0..i]) }
} else {
Matcher { tail: self.tail, val: None }
}
}
fn const_str(self, refs: &'a str) -> Self {
self.then(|tail, val| {
let mut it = tail.chars();
for refc in refs.chars() {
match it.next() {
Some(c) => match c == refc {
true => (),
false => return Matcher{ tail, val: None }
},
None => return Matcher { tail, val: None }
}
}
Matcher { tail: it.as_str(), val: Some(val) }
})
}
fn value<R: Matchable> (self) -> Matcher<'a,<R as Matchable>::Out> {
self.then(|tail, _| {
match R::scan(tail) {
Some((t2, val)) => Matcher { tail: t2, val: Some(val) },
None => Matcher { tail, val: None }
}
})
}
fn merge<R, V, F> (self, f: F) -> Matcher<'a, V>
where R: Matchable,
F: Fn(T, <R as Matchable>::Out) -> V {
self.then(|tail, v1| {
match R::scan(tail) {
Some((t2, v2)) => Matcher { tail: t2, val: Some(f(v1, v2)) },
None => Matcher { tail, val: None }
}
})
}
fn result(self) -> Option<T> {
self.val
}
#[cfg(test)]
fn result_ref(&self) -> &Option<T> {
&self.val
}
}
fn matcher<'a>(src: &'a str) -> Matcher<'a, ()> {
Matcher { tail: src, val: Some(()) }
}