Trying understand functions lifetimes

Consider next code:

fn get_ref<'a, R>(slice: &'a Vec<i32>, f: fn(&'a Vec<i32>) -> R) -> R
where
    R: 'a,
{
    f(slice)
}

fn main() {
    let v = [1,2,3,4,5,6];
    let iter = get_ref(&v, |x| x.iter().skip(1).take(2));

    println!("{:?}", iter.collect::<Vec<_>>());
}

I create some static variable, then apply some function to its reference and get a result.
It seems to work totally fine. At least it successfully compiles.

Now I am trying to add next level of abstraction. And things are getting weird...

fn owned<'a, R>(owner: Vec<i32>, f: fn(&'a Vec<i32>) -> R)
where
    R: 'a,
{
    let _ = get_ref(&owner, f); // error occurs here
    // `owner` does not live long enough.
}

// get_ref is the same as in the first example
fn get_ref<'a, R>(slice: &'a Vec<i32>, f: fn(&'a Vec<i32>) -> R) -> R
where
    R: 'a,
{
    f(slice)
}

fn main() {
    let v = [1,2,3,4,5,6];
    owned(v, |x| x.iter().skip(1).take(2));
}

For me it looks like pretty the same code. But Rust fails to compile it. I really don't understand why this is happening and how should I rewrite my code to compile.

Because you're passing owner by value, it will be destroyed when the owned() function returns. R is allowed to contain a reference to the vector because of the 'a bound you've specified and thus needs to be destroyed before the vector is. Otherwise, it will contain a reference to deallocated memory that would produce unpredictable results.

1 Like

Imagine if I decide to call the function owned<'static, i32>(Vec<i32>, foo) with foo defined as:

fn foo(vec: &'static Vec<i32>) -> i32 { ... }

This satisfies the bounds for owned, since i32: 'static. However this means that you must have a static reference to call f, but owner does not live forever, since it is destroyed at the end of owned.

One way to fix it is to use the following:

fn owned<R>(owner: Vec<i32>, f: for<'a> fn(&'a Vec<i32>) -> R) {
    let _ = get_ref(&owner, f);
}

It says that f must be callable with any lifetime, rather than just some specific lifetime. However, this has the consequence that R cannot borrow from the argument, since R is declared in a larger scope than 'a. There isn't any way to fix that while keeping the generics as is.

5 Likes

To put it more simply: the lifetime 'a comes from outside of the owned() function, therefore it can be any sufficiently long lifetime that the caller chooses.

However, your owner variable lives inside that function (since you take it by value), so its lifetime is precisely the lifetime of the function body. The caller can't choose it: it is what it is, and it's definitely not long enough.

This is the same error that you would get if you tried to return a specific type from a function with a generic return type:

fn get_foo<T>() -> T {
    42
}

Since T is a generic type parameter, the caller can choose it, so it's not guaranteed to be an integer type, so this function shouldn't compile, because what if someone calls it as get_foo::<String>()?

3 Likes

When you post the same question on multiple sites, please make sure to link them together so people don't end up wasting their time on answering a question that already has been answered.

2 Likes

Ok, then can we perform next unsafe trick to erase lifetimes?

fn owned<'a, R>(owner: Vec<i32>, f: fn(&'a Vec<i32>) -> R)
where
    R: 'a
{
    let f_ptr = f as *const fn(&Vec<i32>) -> R;
    let ff = unsafe { *f_ptr };
    let _ = get_ref(&owner, ff);
}

The only issue I see here is if we will assign ref to Vec to global variable inside provided f. But this operation already needs to be inside unsafe.

No, because the Vec<i32> will be destroyed as owned() returns. The references inside R will then be pointing into deallocated memory.

EDIT: I misread the example. I suspect it still has a problem, but I'm not quite sure what.

But I do not return R from owned(). So it will be destroyed too.

No, that's wrong. Trying to circumvent lifetime checking with unsafe is almost always wrong. You should just follow @alice 's advice and use a higher-rank quantification (for<'b>…) or just leave off the lifetime of the function argument altogether, and let lifetime elision figure it out.

However, if you want to ensure that R outlives the temporary references inside owned, you'll still have to clone the vector first in the closure. Iterators over slices still refer to the items being iterated over, as they don't (and can't) move them by-value.

In short, do this.

2 Likes

Lifetime annotations aren't derived from or affected by the body of the function being annotated. The compiler doesn't care what your function actually does; it only checks that its callers can always be sure it will work correctly in all cases where it can potentially be called according to its signature.

The compiler doesn't go "Ha! This return value is never actually used so I'm going to gratuitously extend/erase the declared lifetimes". That would make Rust functions and generics work like C++ templates do (IOW "horribly unreliable"). One couldn't be sure that once a generic function typechecks, it will indeed work correctly in all circumstances.

That unsafe trick doesn't work.

No, it does not need unsafe. Sure, using static mut does, but there are safe ways to mutate globals, which are safe because they use e.g. a Mutex to ensure thread safety.

Another way it could fail is if the function starts a new thread.

1 Like

Well, for<'b> doesn't solve my problem. It does not allow to return reference from the provided closure.
In the actual code it is some more complicated structure than a simple Vec. Unfortunately It can't be simply converted to iterator by-value (otherwise there will be no problem at all :slight_smile: ).

It seems like there is no safe solution to this kind of problem and I need to rethink the structure of my code.

1 Like

Of course it does not. But now you actually want a return value? I'm lost as to what your requirements are. Can you give us a more realistic example, at least with some more evocative names? It's hard to solve a problem if we don't know what the problem is.

2 Likes