Ok. Suppose the current syntax is frozen. But I talked about two things:
syntax for explicit lifetimes (mainly point 3, altough a bit also in
points 1 and 2) and defaults for when no explicit (named) lifetimes are
used.
Even if no syntax change is made, for the cases when named lifetimes are
used, what I find specially painful is the default rule when no explicit
lifetimes are used:
"an implicit fresh lifetime for each parameter and result".
This rule, specially when a result is involved, which is exactly one of
the cases when stating lifetime is needed, seems to make little sense,
and will force named lifetimes to be used in many cases, causing noise
and distraction and decreasing legibility.
In what way is the default that I propose, which basically is:
"one implicit fresh lifetime for each type parameter and another for all
the remainder parameter types"
confusing or a possible source of obscurity? I find it quite natural
that, e.g., if I am returning a borrowed reference (not a copy, not an
owned, not a @) of type T and I have received one or more borrowed
references to T, i.e.:
fn f<T>(p1: &T, p2: &T, ...) -> &T { ... }
the *natural* thing to expect is that the result comes from one of the T
that I received as parameters, and the default that I propose is not
obscure at all, but makes the code cristal clear. I find having to write
fn f<'r, T>(p1: &'r T, p2: &'r T, ...) -> &'r T { ... }
because of the current defaults, needlessly painful and distracting. Is
the current default less obscure? Will it cause less surprise? Or it is
the other way around ...
Regards,
Paulo
On 4/23/13 4:11 PM, Niko Matsakis wrote:
Thanks for your proposal. We've been through a number of rounds with the
lifetime syntax, including an earlier scheme that was quite similar to
what you propose in some regards (there was a single anonymous lifetime
parameter that was used whenever you wrote `&T`, which meant that yes
you often did not need to use explicit lifetime parameter names).
However, we found that this was ultimately more confusing to people than
helpful. One of the goals of the current syntax was to make the
mechanism of named lifetimes more explicit and thus more clear to the
reader. Smarter defaults have the disadvantage that they often obscure
the underlying system, making it harder to learn what's really going on.
In practice,named lifetimes don't seem to be that common, at least for
me. We should do some numerical analysis, but I've found subjectively
that most functions simply consume data and do not return pointers. This
is particularly true for "non-library" code, and even more true with
judicious use of `@` pointers (it is often not worth the trouble to
avoid the GC; it can make life much easier, that's what it's there for).
So in summary I do not think it likely we will change the current syntax.
Niko
On Mon, Apr 22, 2013 at 12:21 PM, Paulo Sérgio Almeida <p...@di.uminho.pt
<mailto:p...@di.uminho.pt>> wrote:
Hi all,
(My first post here.)
First, congrats for what you are trying to achieve with Rust. I
arrived very late to the party, and so I am not sure that what I
will say can be of use. But as I understand, lifetimes is one of
those things that are not completely solidified, and anyway, better
late than never.
Looking at some code, expressing lifetimes of borrowed references is
one of those things that is somewhat bewildering, making the code
somewhat noisy. I think it can be improved through a better choice
of defaults and a slight change in explicit lifetimes.
The main thing I think is "wrong" is the defaults for function
parameters. From the tutorial: "the compiler simply creates a fresh
name for the lifetime automatically: that is, the lifetime name is
guaranteed to refer to a distinct lifetime from the lifetimes of all
other parameters."
This default is not very useful. For example, it is wrong basically
everytime we want to return a borrowed pointer (unless, for a global
variable?). The more common case is returning something with the
same lifetime as some parameter. In many cases we don't need to
distinguish parameters, and specify which we are returning, in
others we want to split parameters in two equivalence classes, the
one from which we are returning, and everything else.
When type parameters are involved, a return type typically says
where the result comes from most of the times. Again, the default
should be different. E.g., when we are returning a borrowed
reference to a "key" of type parameter K, the default should be
"other things also of type K in parameters".
Finally, with better defaults, in the remaining cases where we need
to explicitly express lifetimes, having to "invent" identifiers is a
nuisance. Also the space which must be used between the lifetime
identifier and the type is too distracting and makes it cumbersome
(for humans) to "parse" the type of some identifier.
I have been thinking about this and have the following proposal. (Of
course there may be inumerous things that must have escaped me, but
here it goes anyway.)
---
Regarding types for borrowed references in function parameters or
result, and type parameters:
1) each type-parameter has an associated implicit lifetime, which by
default is different from the lifetimes of other type-parameters or
normal function parameter types, but it can be qualified in each
declaration or use with an explicit lifetime;
2) function parameter types or return type that are not type
parameters have all the same implicit lifetime by default, but they
can be qualified explicitly with some lifetime.
3) explicit lifetimes are written identifier' instead of
'identifier; a null identifier is allowed, as in &'T, to qualify a
reference &T with lifetime '.
---
(Another useful possibility would be allowing several ' at the end,
allowing e.g., &T, &'T, &''T as borrowed references to the same type
but with different lifetimes. In practice, a single ' plus 1) and 2)
will cover "99%" of cases. We could even get rid of using
identifiers, using only several '. But this is not relevant for the
main proposal.)
1) and 2) are about defaults for implicit lifetimes, which currently
are "fresh lifetime for each parameter", and 3) is about simplifying
the remaining cases of explicit expression. The motivation for 3) is
to remove both the need of a space separating lifetime from type and
also the need to "invent" lifetime identifiers. Rewriting examples
from the tutorial and elsewhere, under this proposal, would make
code more legible and standard, reducing a lot the need for explicit
lifetime expression. Things would "just work" as wanted most times.
--------------------------------------------------------------------------------
From the Rust Borrowed Pointers Tutorial
---
fn get_x<'r>(p: &'r Point) -> &'r float { &p.x }
becomes
fn get_x(p: &Point) -> &float { &p.x }
---
fn select<'r, T>(shape: &'r Shape, threshold: float,
a: &'r T, b: &'r T) -> &'r T {
if compute_area(shape) > threshold {a} else {b}
}
becomes
fn select<'T>(shape: &'Shape, threshold: float,
a: &'T, b: &'T) -> &'T {
if compute_area(shape) > threshold {a} else {b}
}
(not very useful case)
---
fn select<'r, T>(shape: &Shape, threshold: float,
a: &'r T, b: &'r T) -> &'r T {
if compute_area(shape) > threshold {a} else {b}
}
becomes
fn select<T>(shape: &Shape, threshold: float,
a: &T, b: &T) -> &T {
if compute_area(shape) > threshold {a} else {b}
}
--------------------------------------------------------------------------------
Other examples:
---
struct Iterator<'lt, T> {
source: &'lt [T],
index: uint
}
fn has_next<'a, 'b, T>(iter: &'a Iterator<'b, T>) -> bool {
iter.index + 1 < iter.source.len()
}
fn next<'a, 'b, T>(iter: &'a mut Iterator<'b, T>) ->
Option<&'b T> {
iter.index += 1;
if iter.index < iter.source.len() {
Some(&iter.source[iter.index])
} else {
None
}
}
becomes:
struct Iterator<'T> {
source: &'[T],
index: uint
}
fn has_next<T>(iter: &Iterator<T>) -> bool {
iter.index + 1 < iter.source.len()
}
fn next<T>(iter: &mut Iterator<T>) -> Option<&T> {
iter.index += 1;
if iter.index < iter.source.len() {
Some(&iter.source[iter.index])
} else {
None
}
}
---
impl<'b, T> Iterator<'b, T> {
fn has_next<T>(&self) { /* same as before */ }
fn next<T>(&mut self) -> Option<&'b T> { /* same as before
*/ }
}
becomes
impl<T> Iterator<T> {
fn has_next<T>(&self) { /* same as before */ }
fn next<T>(&mut self) -> Option<&T> { /* same as before */ }
}
---
Regards,
Paulo
_______________________________________________
Rust-dev mailing list
Rust-dev@mozilla.org <mailto:Rust-dev@mozilla.org>
https://mail.mozilla.org/listinfo/rust-dev
_______________________________________________
Rust-dev mailing list
Rust-dev@mozilla.org
https://mail.mozilla.org/listinfo/rust-dev