9

I noticed the code in Microsoft's Take your first steps with Rust:

fn count_letters(text: &str) -> usize {
    text.chars().filter(|ref c| c.is_alphabetic()).count()
}

It is clear that the code still works well without ref.

Why is ref used here? And when should I use ref? Is there any idiomatic programming practice here?

chenzhongpu
  • 6,193
  • 8
  • 41
  • 79
  • 6
    `ref` was necessary back in old times. Today you'll probably never fall on a situation where you *must* use it. It's a vestige. – Denys Séguret Jun 21 '21 at 06:57
  • 4
    I don't really see what to answer here except copy+pasting from this excellent chapter of the book: https://doc.rust-lang.org/1.33.0/book/ch18-03-pattern-syntax.html#legacy-patterns-ref-and-ref-mut – Denys Séguret Jun 21 '21 at 06:58
  • @DenysSéguret TIL - I actually didn't know `ref` is now considered legacy! I understand the replacement is to just use a borrow to begin with, e.g. `if let Some(inner) = &option` instead of `if let Some(ref inner) = option`. I suspect that in the case quoted in the question the `ref` was never necessary, at least as of Rust 1.0, because a `char` is `Copy`, and you could always call methods on owned values. – user4815162342 Jun 21 '21 at 07:29
  • @user4815162342 it's not even an issue of `char` being `Copy`: [`Iterator::filter`] necessarily passes its parameter by reference, so the ref creates an `&&char`, which is completely useless. – Masklinn Jun 21 '21 at 07:31
  • 2
    @Masklinn It looks like the author intended to write `.filter(|&c| ...)` (which is often seen when calling `filter` with a closure that involves manual comparisons of `c`) and wrote `ref` by mistake. – user4815162342 Jun 21 '21 at 08:46
  • Related: [What are Rust's exact auto-dereferencing rules?](https://stackoverflow.com/q/28519997/9716597) – L. F. Jun 21 '21 at 08:47
  • @DenysSéguret In some cases, [`ref` is still necessary today](https://internals.rust-lang.org/t/is-ref-more-powerful-than-match-ergonomics/12111/11). I don't agree with the book calling it "legacy". (And I dislike the complexity and confusion match ergonomics introduced, but that's a different matter.) – Sven Marnach Jun 21 '21 at 12:03
  • @SvenMarnach thus example don't make much sense, using the right tool could be https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=eb35087c98bd30c4d3ac33217ab59d59 this is not exactly the same behavior of the example but without a concrete one it's hard to correctly argue. Find a odd case full of return and unreachable (quite bad code) will not convince me that ref is still necessary. The borrow checker could solve this in time I guess. – Stargateur Jun 21 '21 at 14:17
  • Does this answer your question? [What does pattern-matching a non-reference against a reference do in Rust?](https://stackoverflow.com/questions/70641939/what-does-pattern-matching-a-non-reference-against-a-reference-do-in-rust) – Stargateur Jan 09 '22 at 15:13

1 Answers1

6

To quote from the Rust Book:

In older versions of Rust, match would assume that you want to move what is matched. But sometimes, that’s not what you wanted. For example:

let robot_name = &Some(String::from("Bors"));

match robot_name {
    Some(name) => println!("Found a name: {}", name),
    None => (),
}

println!("robot_name is: {:?}", robot_name);

Here, robot_name is a &Option<String>. Rust would then complain that Some(name) doesn’t match up with &Option<T>, so you’d have to write this:

let robot_name = &Some(String::from("Bors"));

match robot_name {
    &Some(name) => println!("Found a name: {}", name),
    None => (),
}

println!("robot_name is: {:?}", robot_name);

Next, Rust would complain that name is trying to move the String out of the option, but because it’s a reference to an option, it’s borrowed, and so can’t be moved out of. This is where the ref keyword comes into play:

let robot_name = &Some(String::from("Bors"));

match robot_name {
    &Some(ref name) => println!("Found a name: {}", name),
    None => (),
}

println!("robot_name is: {:?}", robot_name);

The ref keyword is like the opposite of & in patterns; this says “please bind name* to be a &String, don’t try to move it out." In other words, the & in &Some is matching against a reference, but ref creates a reference. ref mut is like ref, but for mutable references.

Anyway, today’s Rust doesn’t work like this. If you try to match on something borrowed, then all of the bindings you create will attempt to borrow as well. This means that the original code works as you’d expect.

Because Rust is backwards compatible, we couldn’t remove ref and ref mut, and they’re sometimes useful in obscure situations, where you want to partially borrow part of a struct as mutable and another part as immutable. But you may see them in older Rust code, so knowing what they do is still useful.


*This was a minor correction, the book says ref instead of name

Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • the key part here (to me) is that `ref` **creates** a reference to the matched thing, while `&` **compares against** a reference. – onlycparra Jan 07 '22 at 16:33
  • 1
    @onlycparra `&` in a pattern matches a reference, but the value will also get _dereferenced_ when it is bound to an identifier. That's why the middle example above results in a move. – Peter Hall Jan 08 '22 at 15:47