2015-07-24 10 views
129

leggevo il lifetimes chapter del libro Rust, e mi sono imbattuto in questo esempio per una chiamata/vita esplicito:Perché sono necessarie le vite esplicite in Rust?

struct Foo<'a> { 
    x: &'a i32, 
} 

fn main() { 
    let x;     // -+ x goes into scope 
           // | 
    {       // | 
     let y = &5;   // ---+ y goes into scope 
     let f = Foo { x: y }; // ---+ f goes into scope 
     x = &f.x;    // | | error here 
    }       // ---+ f and y go out of scope 
           // | 
    println!("{}", x);  // | 
}        // -+ x goes out of scope 

E 'abbastanza chiaro per me che l'errore viene impedito dal compilatore è l'uso- senza postazione del riferimento assegnato a x: dopo aver eseguito l'ambito interno, f e quindi &f.x non sono più validi e non deve essere stato assegnato a x.

Il mio problema è che il problema avrebbe potuto facilmente stati analizzati via senza usando la durata esplicito'a, per esempio, inferendo un incarico illegale di un riferimento ad un ambito più ampio (x = &f.x;).

In quali casi sono effettivamente necessarie vite per impedire errori di utilizzo dopo l'uso (o qualche altra classe?)?

+1

Questo era [cross pubblicato su Reddit] (https://www.reddit.com/r/rust/comments/3efmw7/stackoverflow_why_are_explicit_lifetimes_needed/) – Shepmaster

+1

Per i futuri lettori di questa domanda, si prega di notare che collega alla prima edizione di il libro e ora c'è una [seconda edizione] (https://doc.rust-lang.org/stable/book/second-edition/ch10-03-lifetime-syntax.html) :) – carols10cents

risposta

143

Le altre risposte hanno tutti i punti salienti (fjh's concrete example where an explicit lifetime is needed), ma mancano una cosa fondamentale: il motivo per cui sono durate espliciti necessari quando il compilatore vi dirà che hai loro torto?

Questa è in realtà la stessa domanda di "perché sono necessari tipi espliciti quando il compilatore può dedurli". Un esempio ipotetico:

fn foo() -> _ { 
    "" 
} 

Naturalmente, il compilatore può vedere che sto tornando un &'static str, e allora perché non il programmatore deve digitare?

Il motivo principale è che mentre il compilatore può vedere cosa fa il codice, non sa quale fosse il tuo scopo.

Le funzioni sono un limite naturale per il firewall degli effetti del cambiamento del codice. Se dovessimo permettere che le vite siano completamente ispezionate dal codice, allora un cambiamento di aspetto innocente potrebbe influenzare le vite, che potrebbero quindi causare errori in una funzione lontana. Questo non è un esempio ipotetico. Come ho capito, Haskell ha questo problema quando si basa sull'inferno del tipo per le funzioni di primo livello. Ruggine ha stroncato quel particolare problema sul nascere.

C'è anche un vantaggio di efficienza per il compilatore - solo le firme di funzione devono essere analizzate al fine di verificare i tipi e le durate. Ancora più importante, ha un vantaggio di efficienza per il programmatore. Se non avessimo vite esplicite, che cosa fa questa funzione fare:

fn foo(a: &u8, b: &u8) -> &u8 

E 'impossibile dire senza ispezionare la fonte, che andrebbe contro un numero enorme di codifica migliori pratiche.

deducendo un'assegnazione illegale di un riferimento ad un ambito più ampio

Scopes sono vite, essenzialmente. Un po 'più chiaramente, una vita 'a è un parametro generico di durata che può essere specializzato con un ambito specifico in fase di compilazione, in base al sito di chiamata.

sono le vite esplicite effettivamente necessarie per evitare errori [...]?

Niente affatto. Le durate sono necessarie per prevenire errori, ma sono necessarie vite esplicite per proteggere i piccoli programmatori di sanità mentale.

+0

* "Come ho capito, Haskell ha questo problema quando si basa sull'inferenza di tipo per le funzioni di primo livello." * - Molto interessante. Ho una certa comprensione di Haskell, potresti fornire un esempio? – corazza

+12

@jco Immagina di avere una funzione di primo livello 'f x = x + 1' senza una firma di tipo che stai usando in un altro modulo. Se in seguito cambi la definizione in 'fx = sqrt $ x + 1', il suo tipo cambia da' Num a => a -> a' a 'Floating a => a -> a', che causerà errori di tipo a tutti i siti di chiamata dove viene chiamato 'f' con es un argomento 'Int'. Avere una firma del tipo garantisce che gli errori si verifichino localmente. – fjh

+6

* "Gli ambiti sono vite, in sostanza.Un po 'più chiaramente, una vita' a è un parametro generico per tutta la vita che può essere specializzato con un ambito specifico al momento della chiamata. "Wow, questo è davvero un gran bel punto, mi piacerebbe se fosse incluso nel libro questo esplicitamente." – corazza

6

Si noti che non ci sono vite esplicite in quel pezzo di codice, tranne la definizione della struttura. Il compilatore è perfettamente in grado di dedurre la durata in main().

Nelle definizioni di tipo, tuttavia, le vite esplicite sono inevitabili. Ad esempio, c'è un'ambiguità qui:

struct RefPair(&u32, &u32); 

Queste dovrebbero essere vite diverse o dovrebbero essere uguali? È importante dal punto di vista dell'utilizzo, struct RefPair<'a, 'b>(&'a u32, &'b u32) è molto diverso da struct RefPair<'a>(&'a u32, &'a u32).

Ora, per i casi semplici, come quello che hai fornito, il compilatore potrebbe teoricamente elide lifetimes, come invece accade in altri posti, ma questi casi sono molto limitate e non vale la pena di complessità in più nel compilatore, e questo guadagno in la chiarezza sarebbe quantomeno discutibile.

+0

Puoi spiegare perché sono molto diverso? –

+0

@ A.B. Il secondo richiede che entrambi i riferimenti condividano la stessa durata. Ciò significa che refpair.1 non può vivere più a lungo di refpair.2 e viceversa - quindi entrambi i ref devono puntare a qualcosa con lo stesso proprietario. Il primo tuttavia richiede solo che il RefPair sopravviva entrambe le sue parti. – llogiq

+1

Se sì, perché viene compilato? http://is.gd/VJaKue –

68

Diamo un'occhiata al seguente esempio.

fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'a u32 { 
    x 
} 

fn main() { 
    let x = 12; 
    let z: &u32 = { 
     let y = 42; 
     foo(&x, &y) 
    }; 
} 

Qui, le vite esplicite sono importanti. Questo compila perché il risultato di foo ha la stessa durata del suo primo argomento ('a), quindi potrebbe sopravvivere al suo secondo argomento. Ciò è espresso dai nomi di durata nella firma di foo. Se si è passati gli argomenti nella chiamata alla foo il compilatore si lamentano che y non vive abbastanza a lungo:

error[E0597]: `y` does not live long enough 
    --> src/main.rs:10:5 
    | 
9 |   foo(&y, &x) 
    |    - borrow occurs here 
10 |  }; 
    | ^`y` dropped here while still borrowed 
11 | } 
    | - borrowed value needs to live until here 
4

Il caso del libro è molto semplice di progettazione. Il tema delle vite è considerato complesso.

Il compilatore non può facilmente dedurre la durata in una funzione con più argomenti.

Inoltre, la mia optional cassa ha un OptionBool tipo con un metodo as_slice la cui firma è in realtà:

fn as_slice(&self) -> &'static [bool] { ... } 

Non c'è assolutamente alcun modo il compilatore avrebbe potuto capito che uno fuori.

4

L'annotazione durata nel seguente struttura:

struct Foo<'a> { 
    x: &'a i32, 
} 

specifica che un'istanza Foo non dovrebbe sopravvivere il riferimento che contiene (x campo).

L'esempio che hai trovato nel libro Rust non lo illustra perché le variabili f e escono allo stesso tempo allo stesso tempo.

Un esempio migliore sarebbe questo:

fn main() { 
    let f : Foo; 
    { 
     let y = &5; 
     f = Foo { x: y }; 
    }; 
    println!("{}", f.x); 
} 

Ora, f sopravvive in realtà la variabile puntata da f.x.

3

Se una funzione riceve due riferimenti come argomenti e restituisce un riferimento, l'implementazione della funzione potrebbe talvolta restituire il primo riferimento e talvolta il secondo. È impossibile prevedere quale riferimento verrà restituito per una determinata chiamata. In questo caso, è impossibile dedurre una durata per il riferimento restituito, poiché ogni riferimento di argomento può fare riferimento a una variabile diversa che si lega con una durata diversa. Le vite esplicite aiutano a evitare o chiarire una situazione del genere.

Allo stesso modo, se una struttura contiene due riferimenti (come due campi membro), una funzione membro della struttura può talvolta restituire il primo riferimento e talvolta il secondo. Ancora una volta vite esplicite impediscono tali ambiguità.

In alcune semplici situazioni, c'è il lifetime elision in cui il compilatore può dedurre le durate.

Problemi correlati