2015-07-06 6 views
7

Załóżmy, że próbuję zrobić fantazyjny parser zero-copy w Rust przy użyciu &str, ale czasami potrzebuję zmodyfikować tekst (na przykład w celu wprowadzenia zastępowania zmiennych). Naprawdę chcę zrobić coś takiego:Używanie str i String w sposób wymierny

fn main() { 
    let mut v: Vec<&str> = "Hello there $world!".split_whitespace().collect(); 

    for t in v.iter_mut() { 
     if (t.contains("$world")) { 
      *t = &t.replace("$world", "Earth"); 
     } 
    } 

    println!("{:?}", &v); 
} 

Ale oczywiście String zwrócony przez t.replace() nie żyją wystarczająco długo. Czy jest to dobry sposób? Być może istnieje typ, który oznacza "idealnie &str, ale w razie potrzeby String"? A może istnieje sposób na wykorzystanie adnotacji dożywotnich, aby poinformować kompilator, że zwrócony plik String powinien pozostać przy życiu do końca main() (lub mieć ten sam czas życia co v)?

Odpowiedz

9

Rust ma dokładnie to, co chcesz w formie Cow (klon przy zapisie) typ.

use std::borrow::Cow; 

fn main() { 
    let mut v: Vec<_> = "Hello there $world!".split_whitespace() 
              .map(|s| Cow::Borrowed(s)) 
              .collect(); 

    for t in v.iter_mut() { 
     if t.contains("$world") { 
      *t.to_mut() = t.replace("$world", "Earth"); 
     } 
    } 

    println!("{:?}", &v); 
} 

jak @sellibitze poprawnie Obligacjami, to_mut() tworzy nowy String co powoduje alokację sterty przechowywania poprzednią wartość pożyczonych. Jeżeli jesteś pewien, masz tylko pożyczone struny, a następnie można użyć

*t = Cow::Owned(t.replace("$world", "Earth")); 

W przypadku vec zawiera Cow::Owned elementy, to byłoby jeszcze wyrzucić przydział. Możesz temu zapobiec za pomocą następującego bardzo kruchego i niebezpiecznego kodu (obsługuje on bezpośrednią manipulację ciągów UTF-8 w bajtach i opiera się na fakcie, że zamiennik ma dokładnie taką samą liczbę bajtów.) Wewnątrz twojej pętli for .

let mut last_pos = 0; // so we don't start at the beginning every time 
while let Some(pos) = t[last_pos..].find("$world") { 
    let p = pos + last_pos; // find always starts at last_pos 
    last_pos = pos + 5; 
    unsafe { 
     let s = t.to_mut().as_mut_vec(); // operating on Vec is easier 
     s.remove(p); // remove $ sign 
     for (c, sc) in "Earth".bytes().zip(&mut s[p..]) { 
      *sc = c; 
     } 
    } 
} 

Pamiętaj, że jest to dokładnie dopasowane do mapowania "$ world" -> "Earth". Wszelkie inne mapowania wymagają starannego rozważenia wewnątrz niebezpiecznego kodu.

+2

Tutaj 'to_mut' tworzy niepotrzebną wartość' String' (dotyczy alokacji pamięci sterty), która jest natychmiast nadpisywany (wymaga dealokacji).) Zmieniam linię na '* t = krowa :: własność (t.replace (" $ world "," earth "));', aby uniknąć tego narzutu. – sellibitze

+1

Twój ostatni przykład prawdopodobnie Powinien mieć więcej ostrzeżeń poza "starannym rozważaniem" umieszczonym wokół niego, bezpośrednio steruje ciągami UTF-8 bazując na bajtach i opiera się na fakcie, że zamiennik ma dokładnie taką samą liczbę bajtów, to zdecydowanie optymalizacja, ale nie uniwersalne zastosowanie: – Shepmaster

+0

dodano więcej ostrzeżeń i trochę pogrubionego tekstu.Czy zastanawiam się czy PR dodaje funkcję 'replace (& mut self, needle, value)' do 'Stri ng' struct zostanie zaakceptowany –

8

std::borrow::Cow, w szczególności używany jako Cow<'a, str>, gdzie 'a jest długością analizowanego ciągu.

use std::borrow::Cow; 

fn main() { 
    let mut v: Vec<Cow<'static, str>> = vec![]; 
    v.push("oh hai".into()); 
    v.push(format!("there, {}.", "Mark").into()); 

    println!("{:?}", v); 
} 

Produkuje:

["oh hai", "there, Mark."] 
+0

60 sekund za późno :( –

+0

@ker: Straciłem moc po 10 sekundach po przesłaniu, ledwo udało się.: D –