2015-09-16 15 views
5

Ho il seguente codice semplificato, in cui una struct A contiene un determinato attributo. Mi piacerebbe creare nuove istanze di A da una versione esistente di quell'attributo, ma come posso fare in modo che la durata del nuovo valore dell'attributo duri oltre la chiamata di funzione?Restituisce qualcosa che è allocato nello stack

pub struct A<'a> { 
    some_attr: &'a str, 
} 

impl<'a> A<'a> { 
    fn combine(orig: &'a str) -> A<'a> { 
     let attr = &*(orig.to_string() + "suffix"); 
     A { some_attr: attr } 
    } 
} 

fn main() { 
    println!("{}", A::combine("blah").some_attr); 
} 

Il codice di cui sopra produce

error[E0597]: borrowed value does not live long enough 
--> src/main.rs:7:22 
    | 
7 |   let attr = &*(orig.to_string() + "suffix"); 
    |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ does not live long enough 
8 |   A { some_attr: attr } 
9 |  } 
    |  - temporary value only lives until here 
    | 
note: borrowed value must be valid for the lifetime 'a as defined on the impl at 5:1... 
--> src/main.rs:5:1 
    | 
5 |/impl<'a> A<'a> { 
6 | |  fn combine(orig: &'a str) -> A<'a> { 
7 | |   let attr = &*(orig.to_string() + "suffix"); 
8 | |   A { some_attr: attr } 
9 | |  } 
10| | } 
    | |_^ 

risposta

12

Questa domanda sicuramente è stato risposto prima, ma io non sto chiudendo come un duplicato perché il codice qui è un po 'diverso e penso che sia importante.

Si noti come è stata definita la funzione:

fn combine(orig: &'a str) -> A<'a> 

Si dice che restituisca un valore di tipo A cui interno vivono esattamente fino a quando la stringa fornita. Tuttavia, il corpo della funzione viola questa dichiarazione:

let attr = &*(orig.to_string() + "suffix"); 
A { 
    some_attr: attr 
} 

Qui si costruisce un nuovo String ottenuto da orig, prendere una fetta di esso e cercare di tornare dentro A. Tuttavia, la durata della variabile implicita creata per orig.to_string() + "suffix" è strettamente inferiore alla durata del parametro di input. Pertanto, il tuo programma è respinto.

Un altro modo più pratico di considerare questo è considerare che la stringa creata da to_string() e la concatenazione devono vivere da qualche parte. Tuttavia, si restituisce solo una parte presa in prestito di esso. Pertanto, quando la funzione termina, la stringa viene distrutta e la slice restituita non è più valida. Questa è esattamente la situazione che Rust impedisce.

Per superare questo è possibile sia memorizzare una String all'interno A:

pub struct A { 
    some_attr: String 
} 

oppure è possibile utilizzare std::borrow::Cow per memorizzare sia una fetta o una stringa di proprietà:

pub struct A<'a> { 
    some_attr: Cow<'a, str> 
} 

In quest'ultimo caso vostro funzione potrebbe essere simile a questa:

fn combine(orig: &str) -> A<'static> { 
    let attr = orig.to_owned() + "suffix"; 
    A { 
     some_attr: attr.into() 
    } 
} 

Si noti che poiché si costruisce la stringa all'interno della funzione, questa viene rappresentata come una variante di proprietà di Cow e quindi è possibile utilizzare il parametro di durata 'static per il valore risultante. È possibile anche collegarlo a orig ma non c'è motivo di farlo.

Con Cow è anche possibile creare valori di A direttamente su fette senza allocazioni:

fn new(orig: &str) -> A { 
    A { some_attr: orig.into() } 
} 

Qui il parametro durata A sarà legato (attraverso vita elision) per la durata della stringa di input fetta. In questo caso viene utilizzata la variante presa in prestito di Cow e non viene eseguita alcuna allocazione.

Si noti inoltre che è meglio usare to_owned() o into() per convertire le fette di stringa per String s perché questi metodi non richiedono codice di formattazione a correre e in modo che siano più efficienti.

come è possibile restituire uno A di durata 'static quando lo si sta creando al volo? Non sei sicuro di cosa sia la "variante di proprietà di Cow" e perché ciò rende possibile 'static.

Ecco la definizione di Cow:

pub enum Cow<'a, B> where B: 'a + ToOwned + ?Sized { 
    Borrowed(&'a B), 
    Owned(B::Owned), 
} 

Sembra complesso, ma in realtà è semplice. Un'istanza di Cow può contenere un riferimento ad un tipo B o un valore di proprietà che può essere derivato da B tramite il tratto ToOwned. Perché str implementa ToOwned dove Owned tipo associato equivale a String (scritto come ToOwned<Owned = String>, quando questo enum è specializzato per str, sembra che questo:

pub enum Cow<'a, str> { 
    Borrowed(&'a str), 
    Owned(String) 
} 

Pertanto, Cow<str> può rappresentare sia una fetta di stringa o una stringa di proprietà - e mentre Cow fornisce effettivamente metodi per la funzionalità clone-on-write, è altrettanto spesso utilizzato per contenere un valore che può essere preso in prestito o di proprietà al fine di evitare allocazioni aggiuntive. Perché Cow<'a, B> implementa Deref<Target = B>, è possibile ottenere &B da Cow<'a, B> con semplice rebo la pubblicazione: se x è Cow<str>, quindi &*x è &str, indipendentemente da ciò che è contenuto all'interno di x - naturalmente, è possibile ottenere una porzione di entrambe le varianti di Cow.

Si può vedere che la variante Cow::Owned non contiene alcun riferimento al suo interno, solo String. Pertanto, quando un valore di viene creato utilizzando la variante Owned, è possibile scegliere la durata desiderata () (ricorda che i parametri di durata sono molto simili ai parametri di tipo generico, in particolare è il chiamante che li sceglie) - ci sono nessuna restrizione su di esso. Quindi ha senso scegliere 'static come la migliore durata possibile.

orig.to_owned rimuovere la proprietà da chiunque chiami questa funzione? Sembra che sarebbe scomodo.

Procedimento to_owned() appartiene ToOwned caratteristica:

pub trait ToOwned { 
    type Owned: Borrow<Self>; 
    fn to_owned(&self) -> Self::Owned; 
} 

Questa caratteristica viene implementata con strOwned pari a String. Il metodo to_owned() restituisce una variante di proprietà di qualunque valore venga richiamata. In questo caso specifico, crea String su &str, copiando in modo efficace il contenuto della sezione di stringa in una nuova allocazione. Quindi no, to_owned() non implica il trasferimento della proprietà, è più come se implicasse un clone "intelligente".

Per quanto posso dire String implementa Into<Vec<u8>> ma non str, così come possiamo chiamare into() nel 2 ° esempio?

Il tratto Into è molto versatile ed è implementato per molti tipi nella libreria standard. Into viene in genere implementato tramite il tratto From: se T: From<U>, quindi U: Into<T>. Ci sono due implementazioni importanti From nella libreria standard:

impl<'a> From<&'a str> for Cow<'a, str> 

impl<'a> From<String> for Cow<'a, str> 

Queste implementazioni sono molto semplici - ma restituisce Cow::Borrowed(value) se value è &str e Cow::Owned(value) se value è String.

Ciò significa che &'a str e String attuare Into<Cow<'a, str>>, e quindi può essere convertito in Cow con into() metodo. Questo è esattamente ciò che accade nel mio esempio: sto utilizzando into() per convertire String o &str a Cow<str>. Senza questa conversione esplicita si avrà un errore sui tipi non corrispondenti.

+0

Grazie, funziona! Ma c'è molto che non capisco qui, come puoi restituire un 'A' di durata' statica' quando lo stai creando al volo? Non sono sicuro di quale sia la "variante di proprietà di" Mucca "e perché ciò rende possibile l'uso di" statico ". Non modifichiamo mai 'some_attr' in qualsiasi momento - di solito non è il punto di" copia su scrittura ", per consentire le scritture? 'Orig.to_owned' rimuove la proprietà da chiunque chiami questa funzione? Sembra che sarebbe scomodo. Per quanto ne so, 'String' implements' Into > 'ma non' str', quindi come possiamo chiamare 'into()' nel secondo esempio? – wrongusername

+0

Whoa, ci sono un sacco di domande! Ho aggiornato la mia risposta, si spera che sarebbe utile :) –

Problemi correlati