That's just lifetimes, except you tie them to parameters instead of to types, and make them less expressive by the way, as you cannot model lifetimes that are not tied to parameters this way.
I understand that it is just another way to denote lifetimes. But for most cases, isn't this enough? Are there any common cases where this syntax does not work?
What if you're returning a struct containing a reference (or multiple references), a generic type containing a reference (e.g. Vec<&'a str>), you need to refer to a lifetime in a trait (e.g. MyTrait<'a>) etc etc? You need something more flexible than just an ad-hoc relation between inputs and outputs.
This doesn't mean that lifetimes must be the only way to make this work, there have been ideas of more directly relating borrows to places instead, see e.g. Borrow checking without lifetimes · baby steps
I think connecting output to inputs is much more easy for most programmers because this is what we already do when programming a function; making a new output from a bunch of inputs.
I understand that my annotation is basically same as the assignments in the function body. But I think this is much easier to understand. And this similarity is very interesting in a language design perspective.
This is exactly my point. That example code you provided looks very complicated. It took me some time to understand what those lifetimes mixed with type signature is doing. More complex functions will only make it worse.
I understand that lifetimes allows expressive annotations. But do we need these complex annotations most of the time.
We often say that many nested indentation is bad code and we need to simplify things. There are also other examples in programming such as long complicated functions, expressions, etc.
Here, there are 3 new identifiers and they are interspersed with type signatures. I think it makes it harder to understand, especially for a non-trivial function other than this.
Has anyone looked into this kind of design I proposed?
Well the thing that jumps out to me most is that there can be multiple different lifetimes stored in a single type, and thus in a single parameter, so you'd always at least need enough to distinguish that -- perhaps by naming them separately, which would bring you right back to basically the same thing again.
More likely, I think, would be a shorthand for "well there's only one lifetime in that type" so you could do something like
This has the same problem as saying “all structs with lifetime parameters shall have exactly one lifetime parameter”: it means that it’s no longer possible for the struct to distinguish long-lived references from short-lived ones — for example, if I call make_pair() on one &'static str and one &'a str, it forgets which one is 'static. This can be a meaningful limitation when working with shared references, but it's fatal for structs containing mutable references to partially-borrowed data. Imagine some data structure like
What I meant is that structs can have multiple lifetimes, but all of them are taken care by the compiler. Programmers only need to describe the dependencies between values.
It seems like you're really focused on improving the ergonomics of functions that need to be annotated with lifetimes. The problem is, that's only just one facet of what lifetimes are for, and it's probably both the least important facet, and the easiest to understand. In @kpreid's example
the 'stack and 'data lifetimes are there to express restrictions on how a value of this type can be used (most importantly, how long it can exist, hence "lifetime"). The exact consequences of the lifetimes in this struct definition are subtle enough that I'm not sure I fully understand them myself.
A proposal for redoing lifetime annotations really needs to grapple with all uses of lifetime annotations—so: what's your replacement for this struct? This struct specifically, not the functions that use it.
This is more complex than what you think. Let's take the Pair example, what the programmer describes is this:
... -> Pair<&>
return depends_on x & y
What are the dependencies the programmer actually described? Which lifetime of Pair should depend on x and which on y? Do they both depend on both? Or does each one depend on a different one? Does Pair even have two lifetimes here or just one? Whatever the answer is, how do we write the other cases?
And note that we don't want to have the compiler infer this based on the function body, because this has both technical issues (it would create cyclic dependencies for borrow checking, and what do we do with types?) and social issues (it would make breaking changes much more subtle).
Because having a signature that actually tells you how the value can be used is important. You don't want the compiler determining that from the body -- especially if that body is currently todo!() so you're allowed to do anything then all the callers break when you actually implement it.