"Self-borrowing" from owned data in the same Struct

I've hit another ownership issue, but I'm sure it's just me not considering something. I'd like for some borrowed memory to "hitch a ride" with its owned source, and return from a function together. Say, in a struct like this:

struct Foo<'a> {
    borrowed: Vec<&'a str>,
    owned: Vec<String>,
}

Something is already fishy about that 'a, since there's nothing about it that guarantees the borrow is coming from the Strings in the next field. Yet that's precisely what I'm attempting:

fn foo<'a>() -> Foo<'a> {
    let owned: Vec<String> = vec!["hello".to_owned(), "there".to_owned()];
    let borrowed = owned.iter().map(|s| s.as_str()).collect();

    Foo { borrowed, owned }
}

It was initially a surprise to me that this doesn't compile. We see:

    |
404 |     let borrowed = owned.iter().map(|s| s.as_str()).collect();
    |                    ----- `owned` is borrowed here
405 |
406 |     Foo { borrowed, owned }
    |     ^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function

Surely you jest, Rust. Clearly owned is moved out too, and my foo function no longer owns it. I'll add that due to the 'a ('static doesn't help), we also see:

406 |     Foo { borrowed, owned }
    |     ----------------^^^^^--
    |     |               |
    |     |               move out of `owned` occurs here
    |     returning this value requires that `owned` is borrowed for `'a`

The 'a from outside is stange, but it's the only thing I can think to do. The Foo demands some explicit lifetime, and with that there, the foo function complains if I try to leave anything out.

How do I let my borrowed data "hitch a ride" with its original owned source?

It's not going to work because if a Foo struct is moved to some new address, the borrowed references it contains will be invalidated.

But something that might work is

struct Foo {
    owned: Vec<String>,
    borrowed_indices: Vec<usize>
}

where you can have indexes to strings in your owned vec and created fresh references from those as needed.

It is not possible in Rust to have a struct where one field references another, at least not with ordinary references.

So nor is there syntax to achieve the same with a tuple in the return signature? Some (Vec<&'foo str>, Vec<? String>) where ? somehow marks the source of the ownership via the connection to 'foo?

No, a value with an outstanding borrow cannot move, and returning a tuple containing such a vector would qualify as a move of the vector.

One option is to use the type Rc<str>, which lets you have shared ownership of a string. But you would need to use it in both places.

I think I can work around this by creating the owned source from outside the function, passing it in as a &'a [String] (etc) and then hooking my return type to that same 'a.

Sure, if the strings are owned by a different struct, then it is fine.

Though it is still worth to consider if not using String is worth the pain of getting lifetimes involved.

I do need the lifetimes here, as the original Strings are shared across many child data structures for efficiency.

Okay, well, the rule you need to follow to have something like that to work are:

  1. The strings must be owned by some other struct than the one containing the borrows.
  2. That other struct may not move or be modified while the borrows still exist.
2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.