2015-07-06 13 views
7

Supponiamo che sto cercando di eseguire un parser a zero copie in Rust utilizzando &str, ma a volte ho bisogno di modificare il testo (ad es. Per implementare la sostituzione di variabili). Ho molta voglia di fare qualcosa di simile:Utilizzo di stringhe e stringhe intercambiabili

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); 
} 

Ma naturalmente il String restituito da t.replace() non vivere abbastanza a lungo. C'è un modo carino per aggirare questo? Forse c'è un tipo che significa "idealmente un &str ma se necessario un String"? O forse c'è un modo per usare le annotazioni a vita per dire al compilatore che il String restituito deve essere mantenuto in vita fino alla fine di main() (o avere la stessa durata di v)?

risposta

9

Rust ha esattamente quello che vuoi in forma di Cow (clone On Write) tipo.

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); 
} 

come @sellibitze nota correttamente, il to_mut() crea un nuovo String che provoca un'allocazione heap per memorizzare il valore preso in prestito precedente. Se si è sicuri di stringhe solo hanno preso in prestito, quindi è possibile utilizzare

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

Nel caso in cui il Vec contiene Cow::Owned elementi, questo sarebbe ancora buttare via l'assegnazione. È possibile impedirlo utilizzando il codice molto fragile e non sicuro (Esegue la manipolazione basata su byte diretta delle stringhe UTF-8 e si basa sul fatto che la sostituzione si verifica esattamente con lo stesso numero di byte.) All'interno del ciclo 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; 
     } 
    } 
} 

Si noti che questo è adattato esattamente alla mappatura "$ mondo" -> "Terra". Qualsiasi altra mappatura richiede un'attenta considerazione all'interno del codice non sicuro.

+2

Il 'to_mut' qui crea solo un valore' String' non necessario (che implica l'allocazione della memoria heap) che viene immediatamente sovrascritto (comporta deallocation) .Renderei la riga in '* t = Cow :: Owned (t.replace (" $ world "," Earth "));' per evitare questo overhead. – sellibitze

+1

Il tuo ultimo esempio probabilmente dovrebbe avere più avvertimenti al di là di "attenta considerazione" posti attorno ad esso .Sta tratta direttamente la manipolazione basata su byte di stringhe UTF-8 e si basa sul fatto che la sostituzione avviene esattamente lo stesso numero di byte. È sicuramente un'ottimizzazione, ma non uno applicabile universalmente, – Shepmaster

+0

ha aggiunto più avvisi e testo in grassetto. Mi chiedo se un PR aggiunga una funzione 'replace (& mut self, needle, value)' allo Stri la struttura del 'ng sarebbe stata accettata –

8

std::borrow::Cow, utilizzato in particolare come Cow<'a, str>, dove 'a rappresenta la durata della stringa da analizzare.

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); 
} 

Produce:

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

60 secondi di ritardo :( –

+0

@ker: Ho perso energia circa 10 secondi dopo l'invio, appena fatto. D –

Problemi correlati