When I last dove into how to think about these things, I concluded that it made the most sense to make the following distinction:
- auto-deref goes from
T
to *T
- deref-coercion goes from
&[mut] T
to &[mut] *T
- Note how we have to start with and end up with a reference
- You can think of the coercion happening beneath the outer reference
With that distinction in place, we can note that:
- field access and method resolution use auto-deref
- passing arguments uses deref-coercion
This topic is about deref-coercion; nothing else I'll mention uses auto-deref.
Let's apply deref-coercion to:
// I've added more layers
fn main() {
let bbox: Arc<Box<i32>> = Arc::new(Box::new(5));
fn a(b: &i32) {
println!("{}",b);
}
a(&&&bbox);
// ||||______Arc<Box<i32>>
// |||______&Arc<Box<i32>>
// ||______&&Arc<Box<i32>>
// |______&&&Arc<Box<i32>>
}
The "search" for deref coercion is simple, because there is only one potential target type at each step, so it's just a linear search.
Want: & i32
Have: & &&Arc<Box<i32>>
|__ This is the candidate for deref coercion, under the outer `&`
Candidate: &&Arc<Box<i32>>
- It's not i32. Does it implement Deref? (it has at most one implementation)
- Yes -- and Target = &Arc<Box<i32>>
Candidate: &Arc<Box<i32>>
- Not i32, Deref? Yes, Target = Arc<Box<i32>>
Candidate: Arc<Box<i32>>
- Not i32, Deref? Yes, Target = Box<i32>
Candidate: Box<i32>
- Not i32, Deref? Yes, Target = i32
Candidate: i32
- Hey we found it
So the example ends up being transformed to something like
a(&****&&bbox);
// ||||||||______Arc<Box<i32>>
// |||||||______&Arc<Box<i32>>
// ||||||______&&Arc<Box<i32>>
// |||||________&Arc<Box<i32>> (1st *)
// ||||__________Arc<Box<i32>> (2nd *)
// |||_______________Box<i32> (3rd *)
// ||____________________i32 (4th *)
// |____________________&i32
We could further desugar things...
As @kpreid noted, there's no method lookup. Derefs on &
and Box
are builtin operations. The deref operation on the Arc
calls its trait method.
let _ = *bbox; // A deref operation on Arc notionally desugars to...
let _ = * <Arc<Box<i32>> as Deref>::deref(&bbox);
// | | |__passes the Arc by reference
// | |__&Box<i32>, the return type of Arc::deref
// |_____Box<i32>, via (built-in!) deref of the reference
// Back to the function call
a(&* * <Arc<_> as Deref>::deref(& **&&bbox ));
// | | |___the Arc<Box<i32>>
// |__________________________|__the parts added by deref on Arc
But I think approximately no one thinks in terms of the desugaring once they've gotten the hang of trait based derefs. Derefs are pretty much always a way to go "deeper" into some pointer or wrapper type.
I.e. if I even notice a deref-coercion, I think of it as a(&****&&bbox)
, not the more expanded form just above.
The main potential complication to the linear search is &mut
versus &
. But the search for a target type is still linear, because <T as DerefMut>
is required to have the same Target
as <T as Deref>
. So this is effectively some extra check that it's legal to create a mutable place at each inserted deref.