Need for & here seems strange

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?
}

(Playground)

Output:

true

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 0.56s
     Running `target/debug/playground`

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.

3 Likes

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?

1 Like

Not sure really. I guess such regularity keeps the compiler/library simpler than adapting it to all kind of edge cases like that.

Personally I like to see regularity rather than endless exceptional cases.

5 Likes

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:

12 Likes

Hmm... sounds like a great slogan for the Rust home page:

There is no magic in Rust.

More seriously, as Dennis Ritchie said:

A language that doesn't have everything is actually easier to program in than some that do.

7 Likes

Autoreferencing of Copy types has been proposed and tried out, but didn't make it to completion.

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.

13 Likes

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>.

6 Likes

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

5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.