Discussion:
[rust-dev] Tying lifetime of struct to owned object within it
David Brown
2014-08-13 05:45:36 UTC
Permalink
I'm trying to figure out how to get something like this to compile.
What I want is a struct that owns a string as well as contains
structures containing slices of that string. My real use case is more
complicated, but it seems to boil down easily to this.

This gives me an error:

life.rs:16:21: 16:32 error: `result.text` does not live long enough
life.rs:16 result.fields = result.text.as_slice().split(' ').collect();
^~~~~~~~~~~
life.rs:13:40: 19:2 note: reference must be valid for the lifetime 'a as defined on the block at 13:39...
life.rs:13 fn build<'a>(text: String) -> Info<'a> {
life.rs:14 let mut result = Info { text: text, fields: Vec::new() };
life.rs:15
life.rs:16 result.fields = result.text.as_slice().split(' ').collect();
life.rs:17
life.rs:18 result
...
life.rs:13:40: 19:2 note: ...but borrowed value is only valid for the block at 13:39
life.rs:13 fn build<'a>(text: String) -> Info<'a> {
life.rs:14 let mut result = Info { text: text, fields: Vec::new() };
life.rs:15
life.rs:16 result.fields = result.text.as_slice().split(' ').collect();
life.rs:17
life.rs:18 result
...

Is there a way to do this, or do I need to allocate strings for the
copies of the individual slices?

Thanks,
David Brown

----------------------------------------------------------------------
// life.rs

fn main() {
let info = build(String::from_str("this is a test"));
println!("{}", info.fields[0]);
}

struct Info<'a> {
text: String,
fields: Vec<&'a str>,
}

fn build<'a>(text: String) -> Info<'a> {
let mut result = Info { text: text, fields: Vec::new() };

result.fields = result.text.as_slice().split(' ').collect();

result
}
----------------------------------------------------------------------
Thiez
2014-08-18 21:48:16 UTC
Permalink
The borrow checker is correct, your code is not actually safe. Imagine this:

let mut info = build(String::from_str("this is a test"));
info.text = String::from_str("this assignment drops the original
string");
info.fields[0]; // Accesses freed memory.


The thing is that `Info.text` is not guaranteed to live as long as `Info`.

If you change the type of `Info.text` and the argument of `build` to `&'a
str` and pass "this is a test" everything works nicely. I suppose it would
be safe if you could convince the compiler that `Info.text` cannot change
but there is only inherited immutability so that does not work.

You could do something with arenas; by only having a borrowed reference to
`Info` the compiler should ensure that you do not sneakily change either
`Info.text` or 'Info.fields`.

----------------------------------------------------------------------
extern crate arena;

fn main() {
let ref ar = arena::Arena::new();
let info = build(ar, "this is a test");
println!("{}", info.fields[0]);
}

struct Info<'a> {
text: &'a String,
fields: Vec<&'a str>,
}

fn build<'a>(ar: &'a arena::Arena, text: &str) -> &'a Info<'a> {
ar.alloc(||{
let mut result = Info {
text: ar.alloc(||String::from_str(text)),
fields: vec![],
};
result.fields = result.text.as_slice().split(' ').collect();
result
})
}
----------------------------------------------------------------------
Post by David Brown
I'm trying to figure out how to get something like this to compile.
What I want is a struct that owns a string as well as contains
structures containing slices of that string. My real use case is more
complicated, but it seems to boil down easily to this.
life.rs:16:21: 16:32 error: `result.text` does not live long enough
life.rs:16 result.fields = result.text.as_slice().split('
').collect();
^~~~~~~~~~~
life.rs:13:40: 19:2 note: reference must be valid for the lifetime 'a as
defined on the block at 13:39...
life.rs:13 fn build<'a>(text: String) -> Info<'a> {
life.rs:14 let mut result = Info { text: text, fields: Vec::new() };
life.rs:15 life.rs:16 result.fields = result.text.as_slice().split('
').collect();
life.rs:17 life.rs:18 result
...
life.rs:13:40: 19:2 note: ...but borrowed value is only valid for the block at 13:39
life.rs:13 fn build<'a>(text: String) -> Info<'a> {
life.rs:14 let mut result = Info { text: text, fields: Vec::new() };
life.rs:15 life.rs:16 result.fields = result.text.as_slice().split('
').collect();
life.rs:17 life.rs:18 result
...
Is there a way to do this, or do I need to allocate strings for the
copies of the individual slices?
Thanks,
David Brown
----------------------------------------------------------------------
// life.rs
fn main() {
let info = build(String::from_str("this is a test"));
println!("{}", info.fields[0]);
}
struct Info<'a> {
text: String,
fields: Vec<&'a str>,
}
fn build<'a>(text: String) -> Info<'a> {
let mut result = Info { text: text, fields: Vec::new() };
result.fields = result.text.as_slice().split(' ').collect();
result
}
----------------------------------------------------------------------
_______________________________________________
Rust-dev mailing list
Rust-dev at mozilla.org
https://mail.mozilla.org/listinfo/rust-dev
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/rust-dev/attachments/20140818/845d9ba2/attachment.html>
Loading...