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

Reply via email to