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 referencep
, assignc
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
Post a Comment