Why are `&` and `mut` annotations required at the invocation (i.e. use site) of a function instead of implicit conversion?

Why are & and mut annotations required at the invocation (i.e. use site) of a function instead of implicit conversion?

I don't understand why the compiler wasn't designed to implicitly convert without the annotations? Is there some ambiguity that requires it? Or is there some readability added by all that noisy verbosity (which in my opinion makes it more difficult to read the code)?

The syntax let ref y = x; is synonymous with let y = &x;, i.e. the annotation is only done once either on the source or destination, so why with functions do we have to do annotations on both source and destination?

Was it just a subjective opinion?

I am hoping for some feedback on the justification for this syntax design decision. Thanks.

1 Like

Here are some relevant design discussions from a few years ago:

https://mail.mozilla.org/pipermail/rust-dev/2013-November/thread.html#6849

and

https://github.com/rust-lang/rust/issues/10504

1 Like

Some additional context: Those threads are about a decision to remove certain implicit conversions such as Box<T> to &T. Much later a new similar implicit conversion was added back to the language, except the new conversion is from e.g. &Box<T> to &T.

Not mentioned much is the self argument, which is implicitly referenced and dereferenced as many times as needed. I haven't yet found any discussion of this particular inconsistancy.

This is not quite true, however, for the equivalent function calls. Passing an argument by value moves the argument, even if the parameter binds by reference:

    fn consume(ref y: Vec<u8>) {}
    
    let x = vec![];
    consume(x);
    println!("{:?}", x); // error: use of moved value

while passing by reference, of course, does not:

    fn borrow(y: &Vec<u8>) {}
    
    let x = vec![];
    borrow(&x);
    println!("{:?}", x); // no error

Without type inference, let y = &x; would need to be written: let y: &Vec<u8> = &x;. This is exactly equivalent to my second example above. The difference is that local type inference allows us to elide the type for the let statement but not for the function. (If you replace the function with a lambda expression, then you can elide its parameter type too.)

1 Like

Thanks for that clever insight. I will play devil's advocate so our logical analysis is complete.

I will instead argue that (necessarily because the destination, i.e. function declaration site, can't be inferred differently for every use site) the inference is now inverted from destination to source, i.e. the &x could be inferred.

And ditto, the ref on the destination is in the non-analogous position (i.e. on the destination), since we've inverted the opportunity for inference from destination to source.

In both of these old conversations I see a syntax I'm not familiar with after reading the Rust book for the first time recently: let foo = ~1

Is this a removed syntax? What did it mean?

~T is the old name for Box<T> and ~1 is the old syntax for Box::new(1).

No doubt that the more things that are implied, the more opportunities for ambiguities. Please refer to the bolded sentence of mine.

We did not play with that feature in practice, but I find C++'s reference semantics very confusing - I have no way of seeing whether a random function that takes a local is going to store a reference to it, mutate it, or something else.

You can see the problem with autoderef on iterators (aka Why Iterators Are Not Copy) - methods that take Self by value would not advance a Copy Iterator but rather a copy. I would prefer to limit that ambiguity to methods, where that sugar helps readability significantly.

Besides, adding a & to function arguments is not such of a big deal. Because of deref-coercions, you can add it even when it is not needed and things will work out fine.

Given Rust's borrowing protections, you don't need to know whether it will store a reference or not. The only difference it could make is if it is moved, then the compiler will tell you that you can't access it. No confusion is possible, unless you are making a point about viewing code that hasn't been compiled. The invalid argument I found on that point from the linked mailing list discussion, is that by not annotating the & at the invocation (aka use or call) site, then if the function declaration site is changed to add the &, then some new compilers errors could occur. But the compiler errors would occur in that case even if the & was explicitly declared at the call site.

We don't force casting our struct types to trait object types either, because it would be noisy and it isn't really needed since no ambiguities arise.

So thus (logically since the annotation doesn't guarantee a reference was required at the function declaration site) annotating with & at the call site conveys no unambiguous information and is entirely pointless noise.

P.S. I did agree with the mailing list discussion that a stronger argument can be made for requiring the mut annotation at the call site. Since I lack experience with Rust, I refrain from claiming to know the correct stance on mut.

You've made your opinion clear on that. But please respect that for many of us, it is not pointless noise: we prefer the status quo because it gives us (not talking about the compiler here) direct visual feedback on what exactly is happening with respect to ownership when reading the code.

5 Likes

No it does not:

It wasn't an subjective opinion. It is an unarguable objective statement of fact about ambiguity thus by definition of (Shannon) information (signal versus noise), entirely pointless noise.

P.S. note I typically favor explicitness at the call site where the alternative is very challenging to read, e.g. I prefer arguments wrapped in parenthesis unlike Haskell which requires me to memorize the declaration site signatures.

You're stepping into troll territory here. To deny birkenfeld's observation that sigils convey useful information to us looks just a bit disingenious, given that what you present as explanation doesn't really explain anything.

Stop arguing, start coding.

4 Likes

Huh??? What part of the fact that annotating with & doesn't guarantee that the function declaration site doesn't necessarily require a reference did you not understand or agree with?

How can it be trolling to point out that annotating with & is ambiguous?

Please enlighten me. I am trying to have a respectful and factual discussion. Why are you personalizing this? Did I say anything about any person here? Yet you want to attack me personally because you don't like what the facts are. How much more irrational can one be. Please. Don't. Go. There. Stay on fact and let's keep this professional.

This is really short-sighted. We reason about design and we code with less refactoring because we put the effort into thinking everything out. To insinuate that I don't code or that I am here to argue, is really insulting.

I am here to understand the design decisions made, those future ones that may be made, and to understand what Rust is and will be or could be. How is that disingenuous.

Please remember that Ego is for Little People. You clearly showed that your ego was hurt. If rather you stayed purely on fact, you would show that you are solely concerned with accomplishment.

I hope you will follow a policy of mutual respect from here on out. I didn't disrespect any person here.

I would recommend trying to be a little less forceful in arguments about a language that you do not yet have first-hand experience with.

In particular, there are two ways in which your "unarguable objective statement of fact" are incorrect. For one, @arielb1 was being a little loose when saying "you can add it even when it is not needed." There are cases in which you can add it in which it is not needed, but there are other cases in which you cannot:

fn foo(_x: u32) {}

fn main() {
    let a = 12;
    foo(&a);
}

Gives you:

<anon>:5:9: 5:11 error: mismatched types:
 expected `u32`,
    found `&_`
(expected u32,
    found &-ptr) [E0308]
<anon>:5     foo(&a);
                 ^~
<anon>:5:9: 5:11 help: see the detailed explanation for E0308
error: aborting due to previous error

In addition, even if you could always add it when it is not needed, that still wouldn't mean that it's pure noise; since there could still be cases in which it was required to add it, so the absence of the & would convey information that the argument was not being passed by reference.

So, in those two ways, & is not pure noise. It does convey information. It tells you when an argument is being passed in by value, which is very important for types that are not Copy as that means the value is consumed and so you cannot later use the variable in your function, and even for types which are Copy it can tell you if you care copying the whole type around, or whether the argument is being passed in as just a pointer.

Language design involves tradeoffs. There are some things that you can infer, and some things that need to be made explicit, and many different ways to move around what parts you make implicit and what parts are inferred. Rust went through many iterations of different types of syntax, and different rules about what is explicit, what is inferred, and how coercions work. The current result is the result of many years using and tweaking those rules to find something that strikes a good balance between explicitness and ease of use.

I would recommend that you spend some more time trying out the language, and less time in language design discussions on aspects of the language that are fairly well settled and stabilized.

2 Likes

I don't think you should cover your slightly offended ego with a recommendation (a typical warning that will later become a command or censorship or just all elbows and acrimony) for me to not state what I believe are the facts. Instead I urge you to make factual counter-arguments and leave the useless ego noise for the b-listers:

When we become so in love our project that we lose our objectivity because no one can make a statement of fact about an inanimate object without it being perceived as an attack on our person, then we lose our ability to be productive in discussion. Please just step back from that ledge. We can mutually respect each other and have open discussion.

Note I respect the wealth of knowledge here. Why do you think I am posting here? It is to tap your knowledge and to share mine.

Edit: I am here to get work done. My reason for being here is because I am trying to choose the programing language for my upcoming social app network project, and this is a major decision for me. And I really would like to leap to something with ad hoc polymorphism and so I want to understand what the requirements should be. I am also hoping to have compilation to Javascript, and preferably very tightly coupled. So most likely Rust is not the best fit, but I am keeping an open mind because I don't know of any other language that has ad hoc polymorphism in a non-Haskell clone type language. I am also looking at PureScript. i really don't want to have to create my own language, as that is typically a multi-year project. And I also don't think I am experienced enough to create my own language, so I am testing myself here as well on that point. There are a mix of reasons that I am here at this time and none of those reasons are to ridicule Rust, so please don't construe my discussion in that light.

That is not a counter-argument, because I need to know the function declaration site in order to know if my & is ambiguous. But if I need to inspect the declaration site, then I also know whether a reference is needed, thus the & annotation added no information.

Which afair is not true for trait object arguments, thus invalidating your point unless you are just referring to the borrowing semantic and not the duplicitous meaning of & (tangentially which in my opinion may be confusing to new people learning the language). But even if Rust does require the & annotation at the call site for trait object arguments or you are just making the point for the borrowing semantic of & only, not passing borrowing (i.e. moving) is going to generate a compiler error if you attempt to access it. And afaics otherwise there is no reason to know.

So your only remaining counter-argument appears to be for the non-compiled code case and for the borrowing semantic only. Well uncompiled code could have kinds of errors (my Rust examples being prime examples, lol), so I don't see how that is a valid counter-argument.

Then please get on with it.
The only way you can make that choice is by actually doing some Rust programming.
It would be lovely if we could choose a language by brute intellectual force.
But that is not possible.
It would be great if you switched from using the forum topic editor to your code editor to get a practical feel for the Rust programming language.

Huh???

arielb1 pushed the discussion. I asked to leave to leave alone. See the bolded below.

Please stop trying to blame your ego problems on me. I said the decision has already been made and I am not pushing for discussing it. How much more clear can I be???

Why are you trying to railroad me when you don't like the facts that arielb1 forced me to discuss?

Maybe it is not possible for you. Please don't presume you know my capabilities.

I definitely understand what is going on here. Not Invented Here works like this:

"Here is this guy that only started learning Rust 3 days ago, hasn't even bothered to read all the docs nor install the compiler and he has the audacity to think he can tell us about anything to do with the design of Rust when we've invested months or years into Rust".

It is entirely at odds with the Linus law of open source, which is that with enough inexperienced, dumb monkeys looking at the code and system, all bugs are shallow.

Edit: you want this kind of introspection now while only at 1.0 and not later when it is entirely impossible to change.

Edit#2: discouraging discussion on Rust and treating a new and very serious person with disdain will not at all help Rust. It is different if I was being disrespectful to others or spouting off complete nonsense. I am not. And I have not asked for special treatment. I just want to have a factual discussion devoid of useless noise about not offending egos.

Is it possible to respect and appreciate all factual discussion? Or is it necessary to be accepted by the tribe before we can speak about aspects of Rust that might offend anyone?

Edit#3: it is also presumptious to not acknowledge that this might be my first stage analysis and later I will move to coding in Rust if my first stage analysis says to proceed. It is about efficiency which I can bring to bear because of the experience I have with numerous programming languages. It doesn't mean I think I can know everything about Rust just from compiling Rust semantics in my head.