A borrow operator


#1

In this comment I propose changing & from an address-of operator to a borrow operator. Does this sound sensible/appealing enough that I should write up an RFC, or is it crazy?

I propose it as an alternative for deref-based coercions, which in turn are an alternative for cross-borrowing coercions (I think we need better language to differentiate those last two :slight_smile: )


#2

I’m a bit confused. If &foo – where foo: String – is an &str, how do I get an &String?


#3

Would there be any change to &mut? It might be a bit odd or confusing if &mut and & have differences other than the aliasing guarantees. It also seems like it would lose the symmetry with the &Foo type representation.

For String, Vec and similar types, I would also find it odd to use one syntax (&) for borowing the whole thing as a slice, while using the new slice syntax for reduced slices, and using methods for other kinds of borrows depending on the container.


#4

My reasoning is that it is rare to borrow an owning type, rather than borrow the owned content. I.e., that it is very rare that you want &String or &Box<T> etc. How to do this when you do need it is something I need to flesh out. One idea is to use &(foo) to mean exactly address-of rather than borrow, but that is not the best syntax for obvious reasons.


#5

I don’t know - do you think there should? I could imagine &mut working the same was as & as a borrow operator.

For String etc., I agree - I would prefer not to have two ways here. Perhaps the best thing to do is not to implement Deref for String and Vec.


#6

Maybe I misunderstood something: Is the idea to have & and &mut be completely different, or to be overridable by traits like Deref? I could see a use-case for the latter, and from a reread of the discussion it seems like that is the plan (&T not changing meaning sans specialization).

I do usually want &str instead of &String, but I do want &mut String even though I want &mut [T] instead of &mut Vec<T>.

One other thing that came to my mind is how you are going to construct a &String if you do indeed need one, because you want to pass it as an argument to a generic function wanting a &T where T equals String.


#7

I’ll admit it’s rare. The main case where I could see it being valuable to get references to Deref objects is when you want to potentially clone a smart pointer. e.g. passing around an &Rc<T> to functions that might want to clone it. You can’t do that with a &T. I could see it being important for very generic code, but I’m at a loss for a non-silly example.

This might work out as elegant for some collections stuff, though. E.G. if I store Box<T>'s in a TreeMap, I guess this proposal implies I search with &T's and not &Box<T>'s, which would presumably be awkward to do.

Edit: stupid html


#8

I don’t believe repurposing & is a good idea, but if we need a dedicated borrow/deep deref magic operator, how about … ~?

I suppose I might be crazy too.


#9

Having separate borrow and reference operators sounds great to me. It supports both ergonomics and explicit cost, allowing the user to decide which is more important in any specific bit of code.


#10

Actually I think we can make ~ a generic “coercion” marker. The language will define a set of coercion rules, but only the “auto deref when calling methods” rule will implicitly apply. All other coercions must be opted-in by using ~.

Often we don’t care what kind of coercions happen, only marking that “coercions happen” is good enough.

It is like why we mark all macros with !. (Well, for macros, this is not the only reason. ;))


#11

I dont like the idea. The current rule is pretty simple: & operator turns T into &T. By looking at random code, what is the type of expression &x? And what would it be with proposed change? With the [] slicing operator it is also quite obvious that type of x[] expression is some &[T]. I dont like the idea to merge semantics of & and [] together.


#12

The slicing thing is a semi-separate issue - that seems a totally valid opinion. I’m on the fence here too.

In the way you ask your question, we actually make things simpler - &x always has type &Twhere T is a ‘base’ type as opposed to a reference type (e.g., &U or some smart pointer). The hypothesis here is that it is not so important in Rust to know the exact level of hypothesis as it is to know about ownership of the data (I’m not sure if I agree with this hypothesis, but it seems reasonable). This approach does keep a strict separation between values and references which is important.


#13

The problem with repurposing & is:

  1. It goes (too much) against the expectations of C/C++ programmers. (&a == &b already does so, but that may not be too much.)

  2. It is natural for Rust programmers to expect, if foo has type Bar, then &foo has type &Bar, and &mut foo is a &mut Bar. Applying magic here would go against this expectation too.

If we did not have &T and &mut T as types, then I would have thought this repurposing could be a good idea, as it would make the behavior of &a == &b seem natural.


#14

On second thought, even if we did not have &T and &mut T types, going too much against C/C++ here would still not be a good choice.

Note: By "not having &T and &mut T", I mean having “more explicit” type names like Ref<T> and RefMut<T> (not the std::cell versions, and all references would be either a Ref<T> or a RefMut<T>).


#15

I made an RFC for this: https://github.com/rust-lang/rfcs/pull/248