Here the types don't line up at all. If you had written let (a, b) = &(a, b); then match ergonomics would have kicked in, but that's merely the compiler trying to outsmart you; it shouldn't work based only on the types.
If you want a reference of one field and a copy of another, then be explicit and make types actually match:
The way to express the “reference of one element but a copy of another” is, like @H2CO3 explained as well, to avoid the “match ergonomics” and write a & before the tuple pattern, so instead of let (c, &d) = &(a, b); (which does not compile), you’ll want let &(ref c, d) = &(a, b);.
As I explained in the other response there is no syntax in Rust, if you are using match ergonomics, to change the binding mode back to moves, except for the fact that the mut binding mode can be (ab-)used to do this (see towards the end of the linked post), but that wouldn’t be very nice code.
I personally feel like for more complex cases, avoiding match ergonomics is a fine approach, and introducing new keywords to write the same thing using different syntax only adds confusion and complication. The thing I would probably want to change about the current situation is to allow any binding mode annotations in places where the default binding mode is altered by match ergonomics, so the confusing effects that ref x or ref mut x might be equivalent to x in some places, or that mut x changes the binding mode back to by-value, could be avoided, simply by means of disallowing (or at least linting against) such syntax in the relevant places in the first place.
But at that point, you are (a) using two different kind of features intermixed, and (b) you have complicated it even more by introducing more syntax. You should really just make the types match, it's not any harder or longer.
It is an obvious inconsistency that there is no way to explicitly indicate the move binding mode when the default binding mode can be changed. What you are saying here makes it sound like match ergonomics themselves are a misfeature.
The only other syntax that does make some sense to me is to use &x after all, like you imagined it… kind of following the premise of my linked answer which starts out
What you probably imagine is that let (PAT1, PAT2) = r; with r: &(S, T) will create a &S and match it against PAT1 ; as well as creating a &T and matching it against PAT2 . That’s not what happens.
and thus has me wondering, what if it was actually what happens? Would that be a nice and consistent experience? (At least it would avoid the need for more keywords.. I have thought so little about this idea that I might very well overlook good reasons against such an approach.)
Of course given the precedent of the syntax currently having different meaning from that interpretation means a lot of breaking changes, presumably over an edition, and that raises question if that would be worth the churn.
Right, as I indicated above, I personally believe it might be worth a change to lint in general against any ref x, ref mut x or mut x that appears in places where match ergonomics have changed the default binding mode. Linting against redundantref x in shared-reference default binding mode contexts, or against ref mut x in mutable-reference default binding mode contexts, seems like a very safe first step that might not even need much discussion, but merely an implementation; if nothing else, i.e. even if there are reasons against making it a rustc lint, it’s a strong contender for a clippy lint.