In general, I’m not a fan of &*, and I like auto-borrowing (autoref sounds like 
it turns T into &T, not ~T into &T). I understand the arguments to get rid of 
it though.

BTW, you said that the current proposal still includes autoref for function 
invocation. That sounds to me like autoref would still apply to x in foo(x). Is 
there something else you mean by that?

As I see it, the two interesting cases to consider for removing it are:

1. let x = ~Foo; foo(x); // looks like it moves x when it just borrows it

2. let mut a = ~[ … ]; sort(a); // mutates a

To me, the second case seems pretty clear that it shouldn’t auto-borrow a ~T to 
a &mut T, and instead require sort(&mut *a). It’s pretty ugly, but it makes it 
clear what’s going on. This case isn’t particularly common, which is why losing 
autoref isn’t very tragic.

The first case is the most common case, and it seems a shame to require &*x 
everywhere. I understand the argument that it looks like x is moved until you 
read the function signature of foo(), but I’m not convinced that’s important 
enough to uglify the code. Arguably, you already have to know what foo() does 
to understand what the code is doing.

The basic argument, as I see it, is that the type signature of foo() shouldn’t 
change how the calling code treats its argument. If foo() changed from `fn 
foo(_: &T)` to `fn foo(_: ~T)` then `x` would start being moved instead of 
borrowed, which could cause errors in subsequent code in the caller.

But the problem with this argument is that the type signature of foo() already 
changes how the calling code treats its argument, in a fashion that will 
surface errors in later code rather than at the call site. Notably, if the 
argument is not fully constrained to a specific type by the time foo(x) is 
called, it will further constrain the type that may not cause a problem until 
later. For example:

fn foo(_: uint);
fn bar(_: uint);

let x = 1;
foo(x);
bar(x);

This compiles just fine, but if foo() changes its argument to `int`, then the 
call to bar() will fail with a type error. More complex situations can be 
created using generic functions as well.

In the case of autoref, the question is whether the value will be moved, rather 
than the type of the value, which is a slightly different issue but I’m not 
convinced it’s different enough that it needs to be specially addressed.

The other problem with this is that, even with autoref removed, you can still 
hit the same problem of a seemingly-benign change elsewhere causing a compiler 
error in your code. Namely, if a struct has no destructor, and you rely on the 
ability to implicitly copy it (e.g. calling bar(x) where foo is fn bar(_: T)), 
then adding a destructor to the struct will cause your code to fail to compile, 
in just the same way that foo changing from `fn foo(_: &T)` to `fn foo(_: ~T)` 
will today.

Overall, there just doesn’t seem to me to be a compelling reason to remove the 
ability to auto-borrow ~T into &T. Removing the ability to auto-borrow ~T into 
&mut T is more sensible though.

FWIW, a lot of why I like auto-borrowing is actually due to auto-slicing of 
~[T] into &[T] and ~str into &str. But I assume that when (if) DST happens, 
auto-slicing will just be a case of auto-borrowing. And until that happens I’d 
really like to avoiding having to say foo(v.as_slice()).

-Kevin

On Nov 19, 2013, at 2:08 PM, Alex Crichton <[email protected]> wrote:

> Hello rust-dev!
> 
> Everyone's had their fair share of issues with autoref and autoderef,
> and it's worth considering removing certain portions of it from the
> compiler. The discussion around this has been rooted in the past, but
> has recently been brought up as part of
> https://github.com/mozilla/rust/issues/10504.
> 
> The current proposal is to remove all autoref except for function
> invocations and indexing operations. The method of creating &T from ~T
> would be `let foo: &T = foo` or `&*foo`. Vectors and strings can't
> currently benefit from the `&*foo` syntax, but they will hopefully be
> able to do such once DST lands. In the meantime coercion via type
> ascription will work and they also have `as_slice` methods.
> 
> There are a few reasons backing this proposal:
> 
> 1. It's inconsistent to have magical autoref in some places, but not
> in other places.
> 2. The camp of "less compiler magic is better" can fly their flag over
> this change.
> 3. Code readability does not necessarily benefit from autoref on arguments:
> 
>  let a = ~Foo;
>  foo(a); // reading this code looks like it moves `a`
>  fn foo(_: &Foo) {} // ah, nevermind, it doesn't move `a`!
> 
>  let mut a = ~[ ... ];
>  sort(a); // not only does this not move `a`, but it mutates it!
> 
> The basic idea is that reasoning about code is no longer a local
> function decision, but rather you must understand all the pointer-ness
> of the called signatures to understand when a move happens or not.
> With no autoref, the code would look like
> 
>  let a = ~Foo;
>  foo(&*a); // clearly not passing by value
> 
>  let mut a = ~[ ... ];
>  sort(a.as_mut_slice()); // clearly loaning a mutable reference
> 
> 
> That being said, this proposal is not yet set in stone. I don't think
> that there are many people that are fans of `&*foo` or `&mut *foo`;
> both cases look fairly ugly. So far the general agreement is that
> local small ugliness is the price we pay for local readability, and
> the discussions have been unable to unearth a better system.
> 
> I'm curious if others have a better idea of how to go about doing
> this, or if others just think it's a terrible idea in the first place.
> _______________________________________________
> Rust-dev mailing list
> [email protected]
> https://mail.mozilla.org/listinfo/rust-dev

_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to