I'm trying hard to get on board with Rust syntax and learning when I need to explicitly specify that I want to pass a reference. But needing to do that on a literal number seems really strange!
fn main() {
let range = 3..7;
println!("{}", range.contains(&5)); // Why can't the compiler handle passing just 5?
}
Range<T>::contains() takes a reference because you might be trying to check for range containment using types that can't be copied (e.g. bigints or bigfloats) or cloned, and in that case, taking ownership would either be wasteful or worse, it would make the tested value impossible to use afterwards.
I guess I'm wondering why the Rust compiler can't take care of this automatically. Couldn't it accept a literal number when it is being passed as an argument to a function that expects a reference to a number? What would be the downsides of it doing that?
You can write this today without changes to the compiler: Rust Playground
But I think it's not really worth it, because you end up with a type signature that's much more difficult to read:
fn contains_generic<T: Borrow<U>, U, V>(collection: Vec<V>, value: T) -> bool
where V: PartialEq<U> {
let value = value.borrow();
collection.iter().any(|v| v == value)
}
There are a lot of things that could be done automatically. Every once in a while, there are requests on the Internals forum as to why things X and Y and Z are not implicit in Rust. The answer is simple: it's a design decision. It is the experience of approximately 70 years of programming that magic creates bugs, therefore Rust usually errs on the side of being explicit rather than magical.
Perhaps adequate is this famous quote that applies very well to programming language design:
The analogous idea for non-Copy types is sometimes called "discarding ownership", and is mentioned in this 2017 blog post. The idea is that values would autoreference for the function call and then be dropped immediately afterwards.
Not everyone is happy with the idea of making moves/drops implicit, of course, and consistency is an argument against having autoreference for Copy types only. That said, the ideas were postponed due to lack of movement/stewardship only, so perhaps autoreference will be revisited in the future.
One benefit of forcing you to explicitly reference the value is that now you (and anyone reading this code) know that Range::contains takes a reference. It's better to teach you the right way to do it up front, so you don't have to wonder if the lack of & is just because you used a Copy type, or because the function actually takes ownership.
And the downside is minimal, especially given the quality of compiler errors. I frequently use cargo check or rust-analyzer to tell me what I need to do next, so if I get it wrong, I'm just one failed build / red squiggly away from fixing the problem. In fact, rust-analyzer will fix this one for you, so it's as easy as <Ctrl>-., <Enter>.
If you use references, you can use them with any type so it provides less cognitive complexity to the programmer. One doesn't need to choose if passing by reference or by values is needed.
As for runtime effeciency, I think, compiler optimizes this to just copying number as value during argpromotion pass