External Publication
Visit Post

Method auto-(de)ref (and lack of it) in RFC 132 ufcs

Rust Internals [Unofficial] February 25, 2026
Source

If T::method(x, ...) can mutate x (autoref to &mut T), that's a design issue in my opinion1]. It's the case today that x as a value expression is always a move[[2]. Technically x is a place expression that gets coerced to a value in argument position, but it would very confusing to make an existing value expression place treat place expressions differently. In method syntax x.method(...), x is treated as a place expression.

Thus the only version of autoref on argument position I could support would autoref not to &[mut] $expr semantics but instead to &[mut] { $expr } semantics, which puts the expression still in a value expression position, meaning that the source place gets moved from.

Additionally, in Rust as its designed today, methods aren't special; they're just standard functions that can be called just like any other associated function. It's method call syntax which has the special behavior. That's why I even support not just method disambiguation but full unified method call syntax, allowing any function value to be invoked with method call semantics, leaning into the method call syntax being what's special, not the method item. Autoref for UMCS receivers would make method items into special function items again in more than just availability to method name lookup.

However, I definitely have felt the desire for the ability to apply method-style autoref in macro expansions, where you typically want to call fully qualified names to ensure that the macro is not dependent on the namespace context that it's invoked in. This is probably a very niche use case, but I'd even appreciate the ability to do match k#autoref $x { x => { /* ... */ }} so that m!(x) only evaluates the expression argument a single time with the caller's lifetime extension context but can still apply autoref if applicable.[3]

I could, on the other hand, potentially support automatically reborrowing non-reference argument places. &mut argument places are special in that they're always passed as &[mut] *x, meaning that the binding x remains initialized after, even bypassing the mutability of the binding. This doesn't occur for other impl Deref; e.g. you can't do f(x) where x: Box<i32>, f: fn(&i32), you need to introduce the reference yourself as f(&x) in order for autoderef to project through. I remember the change that mostly eliminated the need to ever write &*. I didn't fully understand exactly how that works back then, but I think I now understand that it enabled autoderef to project into custom impl Deref if autoderef is already started (i.e. by a top-level primitive reference). I think nowadays we could justify removing this restriction, thus allowing automatic reborrows of non-reference types, at least non-mutably.

To make sure people understand my term usage, my mental model is, given place p:

  • autoref: &[mut] p
  • autoderef: &[mut] **p, potentially recursively
  • reborrow: &[mut] *p

In practice method name lookup is fundamentally intertwined with autode]ref, but extracted out of method syntax, autoref is just adding a correctly-mutable reference[[4] that then can get autoderef'd to the correct pointee type.


  1. In the time up to and around 1.0, avoiding the concept of argument passing modes that C++ has was a much more prominent feature of Rust's design, so that potentially explains why RFC 132 doesn't feel the need to mention autoref. Autoref is what allows &self methods to be selected by method syntax, "obviously" it wouldn't apply to function call syntax. I'd even hazard a guess that the ability for method syntax to select <&T>::foo on a receiver of type T was unintentional; you couldn't impl for &T outside std until RFC 1023 was implemented. ↩︎

  2. Yes, even when T: Copy. The only difference is that moving from a Copy value does not invalidate the source place. Well... except for the special case for reborrowing &mut. ↩︎

  3. Yes, this is the exact semantics that I don't support when applying autoref to qualified method UFCS. ↩︎

  4. Obviously only if the target type is a reference. Notably, it does not require the self argument to be a reference; x.foo() can resolve to <&T>::foo(self). &_ has no inherent methods, so this has to be a trait method, and T::foo would be selected before <&T>::foo if there are any potentially valid trait impls for T. playground] [↩︎

Discussion in the ATmosphere

Loading comments...