lifetime - Why can't I store a value and a reference to that value in the same struct? -


i have value , want store value , reference inside value in own type:

struct thing {     count: u32, }  struct combined<'a>(thing, &'a u32);  fn make_combined<'a>() -> combined<'a> {     let thing = thing { count: 42 };      combined(thing, &thing.count) } 

sometimes, have value , want store value , reference value in same structure:

struct combined<'a>(thing, &'a thing);  fn make_combined<'a>() -> combined<'a> {     let thing = thing::new();      combined(thing, &thing) } 

sometimes, i'm not taking reference of value , same error:

struct combined<'a>(parent, child<'a>);  fn make_combined<'a>() -> combined<'a> {     let parent = parent::new();     let child = parent.child();      combined(parent, child) } 

in each of these cases, error 1 of values "does not live long enough". error mean?

let's @ a simple implementation of this:

struct parent {     count: u32, }  struct child<'a> {     parent: &'a parent, }  struct combined<'a> {     parent: parent,     child: child<'a>, }  impl<'a> combined<'a> {     fn new() -> self {         let p = parent { count: 42 };         let c = child { parent: &p };          combined { parent: p, child: c }     } }  fn main() {} 

this fail cleaned error:

error: `p` not live long enough   --> src/main.rs:17:34    | 17 |         let c = child { parent: &p };    |                                  ^    | note: reference must valid lifetime 'a defined       on block @ 15:21...   --> src/main.rs:15:22    | 15 |     fn new() -> self {    |                      ^ note: ...but borrowed value valid block suffix       following statement 0 @ 16:37   --> src/main.rs:16:38    | 16 |         let p = parent { count: 42 };    |                                      ^ 

to understand error, have think how values represented in memory , happens when move values. let's annotate combined::new hypothetical memory addresses show values located:

let p = parent { count: 42 }; // `p` lives @ address 0x1000 , takes 4 bytes // value of `p` 42  let c = child { parent: &p }; // `c` lives @ address 0x1010 , takes 4 bytes // value of `c` 0x1000  combined { parent: p, child: c } // return value lives @ address 0x2000 , takes 8 bytes // `p` moved 0x2000 // `c` ... ? 

what should happen c? if value moved p was, refer memory no longer guaranteed have valid value in it. other piece of code allowed store values @ memory address 0x1000. accessing memory assuming integer lead crashes and/or security bugs, , 1 of main categories of errors rust prevents.

this problem lifetimes prevent. lifetime bit of metadata allows , compiler know how long value valid @ current memory location. that's important distinction, it's common mistake rust newcomers make. rust lifetimes not time period between when object created , when destroyed!

as analogy, think of way: during person's life, reside in many different locations, each distinct address. rust lifetime concerned address currently reside at, not whenever die in future (although dying changes address). every time move it's relevant because address no longer valid.

it's important note lifetimes do not change code; code controls lifetimes, lifetimes don't control code. pithy saying "lifetimes descriptive, not prescriptive".

let's annotate combined::new line numbers use highlight lifetimes:

{                                    // 0     let p = parent { count: 42 };    // 1     let c = child { parent: &p };    // 2                                      // 3     combined { parent: p, child: c } // 4 }                                    // 5 

the concrete lifetime of p 1 4, inclusive (which i'll represent [1,4]). concrete lifetime of c [2,4], , concrete lifetime of return value [4,5]. it's possible have concrete lifetimes start @ 0 - represent lifetime of parameter function or existed outside of block.

note lifetime of c [2,4], refers to value lifetime of [1,4]. fine long referring value becomes invalid before referred-to value does. problem occurs when try return c block. "over-extend" lifetime beyond natural length.

this new knowledge should explain first 2 examples. third 1 requires looking @ implementation of parent::child. chances are, this:

impl parent {     fn child(&self) -> child { ... } } 

this uses lifetime elision avoid writing explicit generic lifetime parameters. equivalent to:

impl parent {     fn child<'a>(&'a self) -> child<'a> { ... } } 

in both cases, method says child structure returned has been parameterized concrete lifetime of self. said way, child instance contains reference parent created it, , cannot live longer parent instance.

this lets recognize wrong our creation function:

fn make_combined<'a>() -> combined<'a> { ... } 

although more see written in different form:

impl<'a> combined<'a> {     fn new() -> combined<'a> { ... } } 

in both cases, there no lifetime parameter being provided via argument. means lifetime combined parameterized isn't constrained - can whatever caller wants be. nonsensical, because caller specify 'static lifetime , there's no way meet condition.

how fix it?

the easiest , recommended solution not attempt put these items in same structure together. doing this, structure nesting mimic lifetimes of code. place types own data structure , provide methods allow references or objects containing references needed.

there special case lifetime tracking overzealous: when have placed on heap. occurs when use box<t>, example. in case, structure moved contains pointer heap. pointed-at value remain stable, address of pointer move. in practice, doesn't matter, follow pointer.

the rental crate or owning_ref crate ways of representing case, require base address never move. rules out mutating vectors, may cause reallocation , move of heap-allocated values.

more information

after moving p struct, why compiler not able new reference p , assign c in struct?

while theoretically possible this, doing introduce large amount of complexity , overhead. every time object moved, compiler need insert code "fix up" reference. mean copying struct no longer cheap operation moves bits around. mean code expensive, depending on how hypothetical optimizer be:

let = object::new(); let b = a; let c = b; 

instead of forcing happen every move, programmer gets choose when happen creating methods take appropriate references when call them.


there's 1 specific case can create type reference itself. need use option make in 2 steps though:

#[derive(debug)] struct whataboutthis<'a> {     name: string,     nickname: option<&'a str>, }  fn main() {     let mut tricky = whataboutthis {         name: "annabelle".to_string(),         nickname: none,     };     tricky.nickname = some(&tricky.name[..4]);      println!("{:?}", tricky); } 

this work, in sense, created value highly restricted - can never moved. notably, means cannot returned function or passed by-value anything. constructor function shows same problem lifetimes above:

fn creator<'a>() -> whataboutthis<'a> {     // ... } 

Comments

Popular posts from this blog

c# - Binding a comma separated list to a List<int> in asp.net web api -

Delphi 7 and decode UTF-8 base64 -

html - Is there any way to exclude a single element from the style? (Bootstrap) -