Lifetime parameters in bounds in generics

Hello,

please consider the following code:

struct Element<'a> {
    __placeholder: &'a usize
}

struct ElementFactory<'a> {
    __placeholder: &'a usize
}

pub fn test<T, Y, F>(f: F)
        where F: FnOnce(&T) -> &Y
{
    // no need to make assumptions about lifetime parameters of T or Y
}

fn general<'a, 'b>(x: &'a ElementFactory<'b>) -> &'a Element<'b> {
    panic!()
}

fn specific<'a>(x: &'a ElementFactory<'a>) -> &'a Element<'a> {
    panic!()
}

fn main() {
    // no problem
    test(&general); 

    // expected type `        std::ops::FnOnce<(&   Element<'_>,)>,
    // found type    `for<'a> std::ops::FnOnce<(&'a Element<'a>,)>
    test(&specific);
}

The problem is clear:

F: FnOnce(&'0 T) -> &'0 Y
if T and Y both accept a lifetime parameter '1, F should not assume '0 = '1.

But what if in test I don't need this freedom? Is there any way to loosen the bound on F in such a way that test will accept specific and remain generic?

This question is motivated by my failure to wrap typed-arena into owning_ref like this:

use owning_ref::BoxRef;
use std::cell::Cell;
use typed_arena::Arena;

struct Element<'a> {
    other: Cell<Option<&'a Element<'a>>>,
}

fn setup_arena<'a>(arena: &'a Arena<Element<'a>>) -> &'a Element<'a> {
    let e1 = arena.alloc(Element {other: Cell::new(None)});
    let e2 = arena.alloc(Element {other: Cell::new(None)});

    e2.other.set(Some(e1));
    e2
}

fn main() {
    let r = BoxRef::new(Box::new(Arena::new())).map(&setup_arena); //error
}

But this requires an understanding of both creates so I think it is more productive to discuss the first, simplified example.

Many thanks.

No you can't. Are you sure you need owning_ref for this? Normally you put Arena either in a global or owned somewhere that outlives all references, and you store a &Arena where you need to use it. In this case, something like this:

use std::cell::Cell;
use typed_arena::Arena;

struct Element<'a> {
    other: Cell<Option<&'a Element<'a>>>,
}

fn setup_arena<'a>(arena: &'a Arena<Element<'a>>) -> &'a Element<'a> {
    let e1 = arena.alloc(Element {other: Cell::new(None)});
    let e2 = arena.alloc(Element {other: Cell::new(None)});

    e2.other.set(Some(e1));
    e2
}

fn main() {
    let arena = Arena::new(); // note how `Arena` outlives all references
    let arena = &arena;
    let r = setup_arena(arena);
}

Let us expand the elided lifetime:

pub fn test<T, Y, F>(f: F)
    where F: for<'a> FnOnce(&'a T) -> &'a Y,
{
    ...
}

In this example, the generic parameter T is declared in a larger scope than 'a is. This means that T cannot depend on the lifetime 'a. However in specific, the parameter in place of T does depend on the lifetime 'a.

2 Likes

Compiles:

pub fn test<'a, 'b, T: 'a, Y: 'b, F>(f: F)
        where F: FnOnce(&'a T) -> &'b Y
{
    // no need to make assumptions about lifetime parameters of T or Y
}

Yeah but then you can't call the closure without leaking memory since 'a might be 'static.

I want to use owning_ref not only to create the arena but to be able to move it together with some references to the stored elements like this:

Pseudocode:

mod api {
    struct Graph {
        storage: Box<Arena<Element>>
        root_node: &Element
    }

    fn MakeGraph() -> Graph;
}

The alternative is this closure pattern:

fn <F>MakeGraph(f: F)
  where F: fn(&Graph) { [...] }

but in my case, this would be super-inconvenient for the user.

In that case you would be better served by a Vec<_> and indicies rather than Arena. Graphs are one of the areas where Rust's ownership scheme makes things hard to do in the normal ways. You can take a look at the fantastic petgraph crate to see if that fits your use case or to see how it's implemented.

1 Like

"Graph" might not be the best word for the structure I have (i.e. you can't arbitrarily remove edges). I am making a small UI library where I expect a lot of interdependence (i.e. function calls) between the components. I have considered using indices but:

  • it gets ugly to make the vector or some accessor available anywhere where accessing a different component might be needed,
  • the typed_arena and its circular references do the job perfectly for me, the only problem is that I have to move the damn structure one time, please understand my frustration :slightly_smiling_face: .

I am yet inexperienced in rust and I want to work out a general intuition of which pattens are fine and which are cumbersome, so I'm kind of testing the limits with this project. I feel like the infrastructure for the safe circular structures with normal references is 95% there and with some more creates I will be able to safely implement all the memory patterns I like to use in C++. Let me know if you know any good discussion on this subject.

That's still a graph :slight_smile:

That's true, one option is to Rc the Vec, but that does get ugly quick.

In that case I would put the Arena outside of the Graph. Yes, it's a little less ergonomic for your users, but it's also far easier to reason about and implement.

Rust doesn't support self-referential types very well, if at all (i.e. storing the Arena in the same type as pointers into that same arena). Normally it's encouraged to try and eliminate the self-referential types. Because you are making a UI library, it should be safe to assume there will only ever be one instance of the UI, and you could make it global, and that would ease accessing it wherever it is needed.

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.