Stylistic practices on borrows of Copy values

When I posted about the suspicious clone pattern on Discord, pie_flavor had this to say:

it is not just the relatively unimportant fact that you have a borrow, it is the relatively important fact that you can move out of it
meaning it is not moving at all, but rather copying
if seeing this is not a reflex for you, that is fine, you will pick it up, but it is for those experienced.

Now, I disagree. I've been writing rust for a long time. And personally, it is rather uncommon that I use *ptr to copy a Copy value. As I see it, this usually comes up when somebody uses match sugar on a reference of a struct with a mix of Copy and non-Copy fields. And honestly, I find this to be dirty and messy. I much prefer to rewrite these patterns using & and ref so I can grab the Copy values by value.

i.e. rather than stuff like this:

let Struct { big_thing, an_int } = some_ref;
takes_an_int(*an_int);

let last = vec.last().unwrap();
takes_an_int(*last);

I vastly prefer to write stuff like this:

let &Struct { ref big_thing, an_int } = some_ref;
takes_an_int(an_int);

let &last = vec.last().unwrap();
takes_an_int(last);

Because that way, I don't need to change my code between takes_an_int(an_int) and takes_an_int(*an_int) based on irrelevant details like "some other field we wanted 5 lines above in a match branch was !Copy". How do others feel about this?

I don't know that I have a strong preference on avoiding *ptr to do a copy or not. I think I have a slight preference for doing it with a pattern if possible, e.g. it comes up with iterator chains a lot.

.filter(|&i| i == 0) // I'm more likely to do this
.filter(|i| *i == 0) // than this
.filter(|i| i == &0) // or this

But in the context of "mixed matches"...

I prefer your approach when I need a mix of moving/copying and reference taking.

I imagine it is or is becoming a niche preference or even ability due to binding modes removing much of the need to understand patterns as duals. If you have a solid understanding of pattern matching, it's natural to just write the pattern that reflects your end goal. If you aren't, it's probably easier to go with the "let the compiler reference everything and I'll dereference what I need to afterwards" (or "move everything and I'll create a reference where I need it afterwards") approach. You may not even be aware that there's a cleaner alternative.

1 Like

I would feel like that the desired way to write this would be to need neither of the & pattern nor ref pattern nor * expression, but that doesn’t work. As it’s – as far as “business logic” is concerned – mostly redundant boilerplate, I might perhaps prefer to choose by repetition.

In your example with Struct { big_thing, an_int } there’s a trade-off between either writing both the & and the ref in the pattern, vs. the single * for the usage. 2 things vs. 1 thing might be an argument in favor of the * expression, but that’s still fine either way I guess.

If however, the struct had 10 fields we are using and 9 of them need to be captured by reference, then saving all those ref patterns might be a big win. On the other hand, if *an_int would appear 10 times, that’s ugly, to. If we had both at the same time, it may be even worth it to throw in an extra line let an_int = *an_int;.

let Struct { field_a, field_b, field_c, field_d, field_e, an_int } = some_ref;
let an_int = *an_int;
takes_field_refs(field_a, field_b);
also_takes_field_refs(field_c, field_d, field_e);
calculate(an_int, 2 * an_int, an_int * an_int, an_int + 42);

[1]


Regarding the let last = vec.last().unwrap(); example, there’s a 3rd alternative of doing

let last = *vec.last().unwrap();

which I’d probably prefer over

let &last = vec.last().unwrap();

  1. Another, particularly ugly hack would be to use

    let Struct { field_a, field_b, field_c, field_d, field_e, mut an_int } = some_ref;
    

    as the mutability will also make an_int be matched by-value again. This is almost-a-bug level of crappy language design we’re relying on here though :sweat_smile: :sweat_smile: :sweat_smile: ↩︎

As far as I'm aware, it is a bug and not intentional design.

Goodness. Hm. Personally, historically I have gone with adding ref to 7 or more fields just because one field is Copy. And that does suck. I think I like this idea of let an_int = *an_int; immediately after the pattern, because that keeps the reasoning relatively local about what needs to be dereferenced.

Related aside: I have a similar concern for types that are almost always taken by reference. I sometimes like to write let ref x = , like:

let ref jump_data = gather_jump_data(instrs, &ctx.defs, hooks)?;

so that I don't need to worry about adding or removing ampersands at use sites when refactoring.

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.