2015-08-19 17 views
6

Diciamo che ho una struct con un riferimento a esso, e un altro struct con un riferimento a tale struct, qualcosa di simile:Lifetimes e riferimenti a oggetti contenenti riferimenti

struct Image<'a> { 
    pixel_data: &'a mut Vec<u8>, 
    size: (i32, i32), 
} 

struct SubImage<'a> { 
    image: &'a mut Image<'a>, 
    offset: (i32, i32), 
    size: (i32, i32), 
} 

Le struct hanno interfacce quasi identiche, la differenza è che SubImage regola i parametri di posizione in base al relativo offset prima di inoltrarli alle funzioni corrispondenti del riferimento Image contenuto. Vorrei che queste strutture fossero per lo più intercambiabili, ma non riesco a capire come ottenere la vita giusta. Originariamente, stavo solo usando Image, e potrebbe passare intorno agli oggetti in modo semplice, senza mai pasticciare in giro con prescrittori corso della vita:

fn main() { 
    let mut pixel_data: Vec<u8> = Vec::new(); 
    let mut image = Image::new(&mut pixel_data, (1280, 720)); 
    render(&mut image); 
} 

fn render(image: &mut Image) { 
    image.rect_fill(0, 0, 10, 10); 
} 

Poi ho creato SubImage, e voleva fare le cose in questo modo:

fn render2(image: &mut Image) { 
    let mut sub = SubImage { 
     image: image,   // line 62 
     offset: (100, 100), 
     size: (600, 400), 
    }; 

    sub.rect_fill(0, 0, 10, 10); 
} 

questo, però, causa un errore di compilazione:

main.rs:62:16: 62:21 error: cannot infer an appropriate lifetime for automatic coercion due to conflicting requirements 

il suggerimento del compilatore è quello di cambiare la firma a questo:

fn render2<'a>(image: &'a mut Image<'a>) 

tuttavia, che proprio spinge il problema fino alla funzione che chiama render2, e ha preso un &mut Image. E questo è abbastanza fastidioso, dato che la funzione richiama alcuni strati in profondità, e non ho dovuto fare nulla di tutto ciò quando stavo usando la classe Image (che ha anche un riferimento) e aggiustando gli offset in linea.

Quindi, prima di tutto, non capisco nemmeno perché sia ​​necessario (devo ammettere che la mia comprensione della vita della ruggine è limitata). E in secondo luogo (la mia domanda principale), c'è qualcosa che posso fare aSubImageper rendere non necessarie queste esplicite vite?

risposta

7

Sì, questo errore può essere fonte di confusione ma esiste una ragione legittima.

struct SubImage<'a> { 
    image: &'a mut Image<'a>, 
    offset: (i32, i32), 
    size: (i32, i32), 
} 

Qui si dichiara che il riferimento a Image deve vivere esattamente finché come i dati presi in prestito all'interno l'immagine stessa - lo stesso parametro durata 'a viene utilizzato sia nel riferimento e come parametro per Image: &'a mut Image<'a> .

Tuttavia, render2() viola questo requisito. La firma effettiva di render2() è la seguente:

fn render2<'b, 'a>(image: &'b mut Image<'a>) 

Pertanto, si cerca di creare SubImage con &'b mut Image<'a>, dove 'b uguale non necessariamente per 'a (e in questo caso particolare, certamente non lo fa), e così il il compilatore esce.

Anche tale firma è l'unica ragione per cui si può chiamare questa funzione, fornendo lo &mut image in main(), perché &mut image hanno durata di image variabile, ma la Image contenuta all'interno di questa variabile ha corso della vita di pixel_data che è leggermente più lungo.Il seguente codice non è Rust valida, ma è vicino al modo in cui il compilatore capisce le cose e si illustra il problema:

fn main() { 
    'a: { 
     let mut pixel_data: Vec<u8> = Vec::new(); 
     'b: { 
      let mut image: Image<'a> = Image::new(&'a mut pixel_data, (1280, 720)); 
      render2::<'b, 'a>(&'b mut image); 
     } 
    } 
} 

Quando si dichiara render2() come

fn render2<'a>(image: &'a mut Image<'a>) 

, invece, si fa "spingere" il problema upstream - ora la funzione non può essere chiamata a tutti con &mut image, e ora è possibile capire perché - richiederebbe unificare le durate 'a e 'b, impossibile perché 'a è più lungo di 'b.

La soluzione corretta è quella di utilizzare vite separate per riferimento Image e Image se stessa in SubImage definizione:

struct SubImage<'b, 'a:'b> { 
    image: &'b mut Image<'a>, 
    offset: (i32, i32), 
    size: (i32, i32), 
} 

Ora 'b e 'a possono essere diversi tempi di vita, anche se in modo che questo funzioni è necessario legato 'a durata con 'b, ovvero 'a deve vivere almeno fino a 'b. Questa è esattamente la semantica richiesta dal tuo codice. Se questo vincolo non viene applicato, allora sarebbe possibile che l'immagine referenziata "muoia" prima che il riferimento ad esso vada fuori ambito, che è una violazione delle regole di sicurezza di Rust.

+0

C'è qualche informazione ufficiale sui limiti di durata? Mi sembrano informazioni molto importanti, ma non l'ho visto da nessuna parte nel libro ufficiale o nelle specifiche. – Malcolm

+0

In effetti, questa è una cosa importante e non credo di averlo visto nemmeno nella documentazione. Probabilmente vale la pena di creare un problema nel tracker dei problemi di Rust su questo. –

3

is there anything I can do to SubImage to make these explicit lifetimes not necessary?

La risposta di Vladimir è azzeccata, ma ti incoraggio a cambiare un po 'il tuo codice. Molto del mio codice originale aveva riferimenti molto simili a cose con riferimenti. Se è necessario il numero, avere una vita separata può essere di grande aiuto. Tuttavia, avevo appena incorporare il Image in SubImage: nulla

struct Image<'a> { 
    pixel_data: &'a mut Vec<u8>, 
    size: (i32, i32), 
} 

struct SubImage<'a> { 
    image: Image<'a>, 
    offset: (i32, i32), 
    size: (i32, i32), 
} 

Nel mio caso, non ero realmente guadagnando avendo riferimenti annidati. L'incorporamento della struct lo rende un po 'più grande, ma può rendere l'accesso un po' più veloce (una puntata del puntatore in meno). È importante sottolineare che in questo caso elimina la necessità di una seconda vita.

+0

Nel mio codice attuale, 'pixel_data' è in realtà di proprietà di una diversa struttura, da un modulo diverso che varia in base alla piattaforma. Questa struttura potrebbe contenere altri dati (ad esempio, quando si utilizza GDI di Windows, contiene un oggetto 'BITMAPINFO', necessario per chiamare' StretchDIBits'). Questo codice specifico della piattaforma chiama nel mio codice, fornendomi un oggetto 'Immagine' contenente un riferimento ai suoi dati pixel. Potrei potenzialmente eliminare il middle-man ('Image') e avere il codice specifico della piattaforma mi passi semplicemente un' SubImage' che rappresenta in realtà l'intera immagine. Ma questo è il modo in cui il mio codice si sta evolvendo finora. –

+0

@BenjaminLindley se 'pixel_data' è di proprietà di una diversa struttura, va bene. Si noti che il codice finale ha ancora un riferimento a questo, e non tenta di possederlo. Il mio punto è che raramente hai bisogno di un riferimento a un riferimento. Nota che puoi facilmente * copiare * un 'immagine', che è una buona indicazione che potresti semplicemente incorporarlo. Sembra davvero che tu possa fare quello che sto suggerendo, quindi ti incoraggio a provarlo! – Shepmaster

+1

Wow, non sono mai stato così disattento quando ho letto una risposta. Sono solo stanco, credo. Per qualche ragione, ho pensato che stavi dicendo di incorporare i dati dei pixel in 'Immagine'. Devo aver appena letto * "Molto del mio codice originale aveva riferimenti molto simili a cose con riferimenti."*, e supponendo che è lì che stavi andando, poi il mio cervello ha riempito il resto della tua risposta. Comunque, sì, potrebbe funzionare. –