Opposite of &'static

You can already do that: Higher-rank trait bounds

type SubWin = for<'a> SubWindow<(&'a mut EdScene, &'a mut MTool)>;
type BSubWin = Box<SubWin>;

(Playground)

sure I remember the 'for<..>' notation from advice on the 'iterator' case aswell,

what would be perfect however is if this could be done in one mental step, at the site of the reference. if there was a "'shortest'" (hopefully a handier short mnenonic) , you'd save that preceding construct, and the creation of the extra name.

I count every name, every set of brackets etc as 'extra mental steps'. writing 'a different type of pointer' is neater. (for the record I also really miss the ~T.. box is an extra step because it has a nesting level)

I know there's more information to express here but the question is can we streamline these common cases as far as possible.. and reserve the creation of labels for the case where you need to relate specific escaping references.

That may work for @dobkeratops's use case (I haven't looked closely) but it is not the same thing in general.

trait Trait<'a> {}
impl<'a> Trait<'a> for u64 {}

fn receive<'a, T: Trait<'a>>(_: &'a str) -> T {
    unimplemented!();
}

// Type of f is for<'a> fn(&'a str) -> u64,
// but caller passes a concrete lifetime fn(&'x str) -> u64.
fn invoke(f: fn(&str) -> u64) {
    let s = String::new();
    f(s.as_str());
}

fn main() {
    // This works.
    let s = String::new();
    receive::<u64>(s.as_str());

    // This does not work.
    // If invoke took fn(&'shortest str) -> u64, presumably it would work.
    invoke(receive::<u64>);
}

it is not the same thing in general.

I would contend that it is the same thing conceptually. The compiler ought to be able to conclude that

impl<'a> Trait<'a> for u64
T == u64
f: for<'a, T: Trait<'a>> fn(_: &'a str) -> T
-----------------------------------------
f: for<'a> fn(&'a str) -> u64

but for some reason it doesn't seem capable of doing that. This is weird because one could just trivially work around the issue by wrapping the function:

fn wrapped_receive<'a>(x: &'a str) -> u64 { receive::<u64>(x) }

invoke(wrapped_receive);

I don't get why the compiler can't treat them as the same though.

Moreover, I translate the example into Haskell, it compiles just fine:

{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, RankNTypes #-}

data StrRef a = StrRef

class Trait a t

instance Trait a Int

receive :: forall a t. Trait a t => StrRef a -> t
receive _ = undefined

invoke :: (forall a. StrRef a -> Int) -> ()
invoke _ = undefined

main :: IO ()
main = pure (invoke receive)
1 Like

‘for all lifetimes’, does that implies ‘shortest’ is the only fit without further detail in the for<…> ?

I still think a direct way of expressing intent in the simplest case would be useful: and it could be discovered by example; the problem is needing to go through the extra mental steps (looking to one side, going through another construct) for something that is a simple use case.

I think between shortest, self, static you can do the vast majority of cases.

I still wonder if this could be worked into the defaults somehow, but I accept that may not be possible, or take time to figure out (it might clash with the already good 'self assumptions).

What I can imagine is that this sort of thing might eventually be inferable by example for extreme compiler guidance on this issue, e.g.

if you write some concrete impls of a generic function, the compiler can retroactively figure and suggest out the correct bounds for the signature from those.

IMO until we have such advanced pain-free means of doing things, it would be useful to have an option to just flag everything in the file as unsafe (borrow checker=warnings, all functions in this file can only be called from other unsafe blocks)

‘for all lifetimes’, does that implies ‘shortest’ is the only fit without further detail in the for<…> ?

No, the meaning is highly sensitive to where the for-all is placed. The for-all determines who has the freedom to choose the lifetime.

fn outer<'a>(s: &'a str, inner: fn(&'a str) -> Y) -> X
// outer: for<'a> fn(&'a str, fn(&'a str) -> Y) -> X

Here, the for-all tells us that the caller of outer has the freedom to choose 'a. Therefore the body of outer can't secretly stash away references to s, whereas inner could if it collaborates with the caller of outer.

Within outer, Rust will automatically typecheck the function body as if the lifetime 'a is the shortest lifetime that encloses the body and nothing else.

fn outer(inner: for<'a> fn(&'a str) -> Y) -> X
// outer: fn(for<'a> fn(&'a str) -> Y) -> X

Here, the for-all instead grants the freedom of choosing 'a to the body of outer. Therefore the inner function is subject to the whims of the outer function and can't secretly stash away references to that str.

Within inner, Rust will automatically typecheck the function body as if the lifetime 'a is the shortest lifetime that encloses the body and nothing else.

4 Likes

ok thanks, I think I did understand it ok: I think that simply points to 'further control' from the 'a ..I did get that for<> creates a label.

for<'a> fn outer( s:& 'a str , inner: for<'b> fn(&'b str)->Y , other_context:Z ) -> X

this example highlights why I think a straightforward 'shortest (or 'temp, whatever ideas there are for a catch name) would be highly beneficial

Imagine if you could just write

fn outer(s:&'temp str , inner: fn(&'temp str)->Y, other_context:Z) -> X

...and you don't have to read/write back and forth to locate the meaning of 'a, 'b in this example. Specifically to create my personal 'default' expectation, I would have to have created two labels in this example. What I'm suggesting is to reduce the need for specific labels to shared, escaping lifetimes.

It's not so much the character count: it's the fact that creating and referencing specific labels (which themselves must appear inside angle brackets off to one side) introduces extra mental steps in comprehension. I personally find that moving in and out of nesting bracket levels 'costs' a lot more than the simple character count might suggest .. (this is why I'm always going on about the original sigils, I think removing those was a terrible loss)

Another idea would be an equivalent temp_ref<T> type that can just be used/renamed as you want.. thats' messier because there's a nesting involved again, but at least the intent is still localized and you don't need to name something onsite

fn outer(s:temp_ref<str> , inner: fn(temp_ref<str>)->Y, other_context:Z) -> X

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.