On 22.05.2014 17:54, Felix S. Klock II wrote:
Michael (cc'ing rust-dev)-
On 22 May 2014, at 16:32, Michael Woerister <[email protected]> wrote:
Lately I've been thinking that it might be nice if one could omit the lifetimes
from the list of generic parameters, as in:
fn foo<T>(x: &'a T, y: &'b MyStruct) -> (&'b int, &'a uint)
instead of
fn foo<'a, 'b, T>(x: &'a T, y: &'b MyStruct) -> (&'b int, &'a uint)
Especially for otherwise non-generic functions, having to explicitly list
lifetimes seems a bit redundant, as they are unambiguously defined in the
function signature anyway (as opposed to type parameters, which don't have the
special `'` marker).
Note that this is not true in the general case. Consider e.g. methods with an
type impl or a trait impl: in that scenario, one could bind lifetimes on the
method itself, or in the type/trait being implemented. This distinction
matters (in terms of what limits it imposes on the clients of those
types/traits).
(I provide a concrete example after my signature.)
There are other changes one could make to still accommodate a change like you
suggest in the general case, such as inferring some binding site (e.g. the
nearest one) if a lifetime is otherwise found to be unbound. But I personally
do not think such changes improve the character of the language (IMHO); I'd
rather have a straight-forward rule that one applies in every context.
Cheers,
-Felix
Concrete Example:
The variants below are quite different.
#[deriving(Show)]
struct S<'a> {
p: &'a int
}
impl<'a> S<'a> {
#[cfg(variant1)]
fn foo(&mut self, arg: &'a int) -> &'a int {
let old = self.p;
self.p = arg;
old
}
#[cfg(variant2)]
fn foo<'a>(&mut self, arg: &'a int) -> &'a int {
let old = self.p;
self.p = arg;
old
}
#[cfg(variant3)]
fn foo<'a>(&mut self, arg: &'a int) -> &'a int {
arg
}
#[cfg(variant4)]
fn foo<'a>(&mut self, arg: &'a int) -> &'a int {
self.p
}
#[cfg(variant5)]
fn foo<'a>(&self, arg: &'a int) -> &'a int {
self.p
}
#[cfg(variant6)]
fn foo<'a>(&'a self, arg: &'a int) -> &'a int {
let _ = arg;
self.p
}
}
#[allow(unused_mut)]
fn main() {
let x = 3;
let mut s = S{ p: &x };
let y = 4;
println!("begin: {:?}", s);
let z = s.foo(&y);
println!("later: {:?} z: {:?}", s, z);
}
Half of them do not compile (for differing reasons). Variants 1, 3, and 6 do
run (and each produces a different output), but the interesting points come
from understanding why the other cases do not compile.
(FYI: It is probably easier to talk about the example if you first alpha-rename
the method-bound lifetimes to something other than `'a`.)
Thanks for the detailed response, Felix. You are right, I didn't think
about nested cases. It *would* still be possible to leave the lifetime
parameters out of the list, but the proposition loses some of its
allure. I'm not sure, though, if the outcome would be that bad. Nesting
is not arbitrarily deep in practice and assuming that unbound variables
belong to their nearest enclosing scope, as you suggest, seems like a
reasonable idea from a program-readability perspective.
I find variant #6 from your example rather interesting! At first glance,
it seems that it shouldn't be possible to relate the lifetimes of `self`
(lets call it 'b) and `self.p` (let's stay with 'a) in this case. Does
the borrow checker infer a sub-typing relationship between the two
lifetimes (i.e. 'a <: 'b)? It would make sense to say that a reference
contained in some aggregate will always live at least as long as any
reference to the aggregate, iff it is guaranteed that the contained
reference always outlives the aggregate... as in:
L(&A) < L(A) && L(A) < L(A.x) ===> L(&A) < L(A.x)
I think, I just learned something new here :)
Cheers!
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev