Is the 'env early-bound or late-bound?

The code is taken from thread::scope, to my understanding, 'env should be late bound.

//scope::<_>(f); // function takes 2 generic arguments but 1 generic argument was supplied

shows it is late bound too, but both the scope::<'_, _, _>(f) and scope::<_, _>(f) can be compiled.

I don't know why the three generic parameters version can be compiled.

struct Scope<'a, 'b: 'a> {
    a: &'a i32,
    b: &'b i32,
}

fn scope<'env, F, U>(f: F) -> U
where
    F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> U,
{
    let n = 0;
    let s = Scope { a: &n, b: &0 };
    f(&s)
}

fn f<'a, 'b>(_s: &'a Scope<'a, 'b>) -> () {
    ()
}

fn main() {
    //scope::<_>(f); // function takes 2 generic arguments but 1 generic argument was supplied
    scope::<'_, _, _>(f);
    scope::<_, _>(f);
}

And, if 'env is late-bound, then the scope function can be desugar something like that:

for<'env> FnOnce(F) -> U

meaning any 'env can be used, but the following code was refused:

struct Scope<'a, 'b> {
    a: &'a i32,
    b: &'b i32,
}

fn scope<'env, F, U>(f: F) -> U
where
    F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> U,
{
    let m = 0;
    let n = 0;
    let s = Scope { a: &n, b: &m };
    f(&s)
}

fn f<'a, 'b>(_s: &'a Scope<'a, 'b>) -> () {
    ()
}

fn main() {
    scope::<_, _>(f);
}
1 Like

It's early-bound. Which I think answers all of your questions?

Except maybe "why is it early bound". The reason is, it appears in this where clause:

    F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> U,
1 Like

If it is early bound, why the function takes 2 generic arguments instead of three?

And where will a function call like scope::<_, _>(f) capture 'env from?

If the 'env is not explicit, then it is common to take from the input arguments, but here, no 'env can be provided by scope::<_, _>(f).

Instead, seems &m can be used to specify a lifetime, but the code was refused saying m does not outlives 'env

fn scope<'env, F, U>(f: F) -> U
where
    F: for<'scope> FnOnce(&'scope Scope<'scope, 'env>) -> U,
{
    let m = 0;
    let n = 0;
    let s = Scope { a: &n, b: &m };
    f(&s)
}

You can elide lifetime parameters and in function bodies, they'll be inferred. (In signatures they follow the standard elision rules.)

Same as with nominal structs.

So, the 'env is inferred from function bodies, I can only see it was inferred as &m, but obviously 'env is longer that &m.

Maybe this is a rule: any lifetime specified in signatures outlives the function stack value liveness scope?

They can be elided, but it is that the generic parameters can be ignored when typing the code, the early-bound lifetime should still be part of the type.

As you said earlier, if 'env is early-bound, scope<'env, F, U> can be implemented as something like that

struct fn_scope<'a, F, U>(PhantomData<'a, F, U>)
impl <'a, F, U> FnOnce for  fn_scope<'a, F, U> { ... }

Then, there are three generic parameters in fn_scope, not two.
But the rustc tell me function takes 2 generic arguments

(On mobile so replies may be terse or slow)

Note also that '_ didn't use to exist, so elision was the only possibility for unnameable lifetimes... such as every borrow shorter than the function body.

That's an interesting topic I can't tackle on my phone properly. Hopefully later!

Before anything else, change your playground to match the invariance of the original struct.

(I haven't looked closely at the borrow errors.)

Any lifetime nameable in a function outlives the function body, yes.

1 Like

Ah! Good catch. It takes two generic type parameters. Or non-lifetime parameters. The error is phrased incorrectly.

Future readers, see the excellent answer in this other thread.

Ah, I see you've linked that here immediately - and thanks for the kind words - I hadn't even noticed the second thread with the similar question (well, at least as a follow-up question) of what 'env is for, yet.

1 Like

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.