someguynamedjosh / ouroboros

Easy self-referential struct generation for Rust.
Apache License 2.0
524 stars 33 forks source link

How to store an iterator together with the structure it is iterating on? AKA How to fix 'the parameter type may not live long enough'? #76

Closed Qqwy closed 1 year ago

Qqwy commented 1 year ago

I am trying to iterate over content stored in an Arc. In the real code, I'm building something akin to a database: A BTreeSet where individual nodes are stored on disk and are only loaded when needed. To minimize copying, nodes are returned inside an Arc. But I want to be able to iterate the subtree of a given node (obviously, without any copying or cloning), and this is where I run into trouble because I want to return references to elements inside an Arc<Vec<K>> (in the real code, the vec is one of the fields of a larger struct) but would want to clean up the Arc once done with a single child.

It seems like a solution would be to wrap std::slice::Iter together with the content it refers to, inside a self-referential struct. Enter Ouroboros.

But the following does not compile, and I cannot figure out how to fix it.

Minimal example:

use ouroboros::self_referencing;

#[self_referencing]
pub struct LeafNodeIter<K>
{
    node: Arc<Vec<K>>, // <- In the real code, this is an Arc<MyStructContainingAVecSomewhere<K>>
    #[borrows(node)]
    #[covariant]
    iter: std::slice::Iter<'this, K>,
}
error[E0310]: the parameter type `K` may not live long enough
  --> src/example.rs:81:11
   |
81 |     iter: std::slice::Iter<'this, K>,
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the type `K` will meet its required lifetime bounds...
   |
note: ...that is required by this bound
  --> ..../rustlib/src/rust/library/core/src/slice/iter.rs:66:24
   |
66 | pub struct Iter<'a, T: 'a> {
   |                        ^^
help: consider adding an explicit lifetime bound...
   |
76 | pub struct LeafNodeIter<K: 'static>
   |                           +++++++++
For more information about this error, try `rustc --explain E0310`.

The suggestion to make K bound by 'static does not seem helpful, since the point of the self-referencing struct is that K is (at most) bound by 'this/the other field's lifetime. Right?

What is happening here, and how can it be fixed? Am I using Ouroboros incorrectly maybe?

someguynamedjosh commented 1 year ago

Depending on what you want, this is either very possible or not currently possible.

If your type K contains no references (e.g. it is i32, String, or any other data type with no lifetime parameter), it is perfectly valid to just put:

#[self_referencing]
struct Wrapper<K: 'static> {

This will still allow the iterator to reference your Arc temporarily. Putting K: 'static still allows you to make 'this-scoped references to values of type K. It's just that the values of type K themselves cannot contain any references with smaller scopes than 'static.

However, if you need to store some data that contains references:

#[self_referencing]
struct Wrapper<'a> {
    node: Arc<&'a str>,

Then unfortunately, you are out of luck, as this hits upon a current limitation in Ouroboros. What you would want in such a case is:

#[self_referencing]
struct Wrapper<'a: 'this> {

To ensure that 'a will not outlive a struct containing a reference with that lifetime. But right now, Ouroboros works by internally replacing 'this with 'static, providing an interface that, behind the scenes, converts between references of appropriate lifetimes and references with (unsafe) static lifetimes. This approach is incompatible with having constructs like 'a: 'this because it would just force 'a to be static. I am open to suggestions if you can see a way around this limitation.

This hits on the issues brought up in #50 .

Qqwy commented 1 year ago

Thank you! This works perfectly!

I think I misunderstood what K: 'static meant before. Indeed I am storing plain-old (reference-free) data.