Type coercion for Arc

fn foo(v : &Vec<u8>) -> ()
    println!("len/capacity {}/{}", v.len(), v.capacity());

fn main() 
   let a_v = Arc::new(Vec::with_capacity(64));

What coercion causes this to compile?

I think that is Deref coercion.

Arc<U> implements Deref with Target = U, thus &Arc<U> coerces to U:

If T implements Deref<Target = U> , and x is a value of type T , then:

  • […]
  • Values of type &T are coerced to values of type &U
  • […]

Thanks it definitely is but what confuses me is shouldn't Deref trait come into play when one uses the * operator?

I mean if had something like:

let arc = Arc::new(U::new());
*arc should leverage Deref trait. What am I missing here?

Deref caused big confusion to me too.

In immutable contexts, *x (where T is neither a reference nor a raw pointer) is equivalent to *Deref::deref(&x)

This is very confusing because the first * refers to the actual use in the code. The second * refers to the actual dereference operation taking place.

What Deref::deref does here isn't the actual dereference, but just the implicit coercion being part of it.

Note that:

fn deref(&self) -> &Self::Target

I.e. the deref method doesn't do any dereferencing (it gets a reference and returns a reference). Confusing, eh? :grinning_face_with_smiling_eyes:

Edit: Depending on the point of view, this isn't exactly right, see @steffahn's comment below and also my follow up below.

1 Like

Using the * operator deliberately isn't a coercion, it's just dereferencing. Coercions are automatic and inserted by the compiler in places where both the from-type and the to-type are known (coercion sites).

But it's worth noting that using * may imply an additional coercion through Deref::deref (or DerefMut::deref_mut in mutable contexts). That's not the only place where coercion through Deref/DerefMut happens, though.

I'd like to add that coercions are only done between certain types. See a full list in the reference. Particularly, From and Into do not enable coercions, but require an explicit .into() (or T::from) call.

1 Like

Deref coercion is implicitly applied to the arguments of a function call expression like foo(&a_v). (These are among the "coercion sites" mentioned by @trentj.)

1 Like

Note for completeness, that Deref really enables three things.

One is deref coercion

another is the desugaring of prefix * operator

and last but not least, Deref plays a role in method resolution: When you have a value foo of type T implementing Deref<Target = U>, then you can also call any method fn bar of U directly like foo.bar(). I. e. there's no need to write (*foo).bar(). This even works transitively. This feature is related to how Rust doesn't have (or need) any -> operator.


This interpretation seems a bit inaccurate as well IMO. Really, the thing here is that the syntax *x transforms place expression into place expression while Deref::deref transforms reference into reference. Both things do actually dereference one level — the extra * in *Deref::deref(&x) is balanced out by the extra &, *x goes from T to U while the underlying Deref::deref call goes from &T to &U.

But place expressions are a somewhat weird concept when you first think about them more deeply. When you do things like assigning let y = x, then the RHS expression x is evaluated to a value, possibly with effects like moving that value out of x, whereas in other contexts e. g. x += 1 the expression on the left hand side only refers to the place x.

A x += 1 operation corresponds to a call add_assign(&mut x, 1) suddenly for taking about the place of x behind a function abstraction, we need to introduce a mutable reference. Really, with deref it's more ore less just the same.

The connection to deref coercion works IMO better conceptually if you think about it the other way: Instead of considering *x to mean *coerce(&x) (i. e., take reference, then coerce, then remove reference), it might be better to consider * the more primitive operation and to think of the coercion of some y: &T into &U as the compiler sparing you the need to write out the expression &**y.

  • In the expression &**y, the &T turns into *y: T, into **y: U and then into &**y: &U. Only the middle * converting T to U is not some built-in deref operation for a reference, so it desugars to the *Deref::deref(&…) expansion, giving &**y the desugaring &*Deref::deref(&*y). Finally, we can simplify by removing redundant &* combinations, and see how &**y is essentially the same as Deref::deref(y).

One good reason why “considering *x to mean *coerce(&x)” might be suboptimal is because coercion works transitively, you can coerce &T into &V when T: Deref<Target = U> and U: Deref<Target = V>, whereas an expression *x always only does a single level of Deref::deref call; you'll need **x to turn T into V.

  • For **x, the desugaring *Deref::deref(&*Deref::deref(&x)) can again be reduced (by removing redundant &*) to just *Deref::deref(Deref:deref(&x)). Similarly, ***x would be *Deref::deref(Deref::deref(Deref:deref(&x))). This neatly shows how the change from “reasoning with place expressions” to “working with references”, and back, respectively only needs to happen once in such a chain.

So I guess you can say that writing *x doesn't really involve a coercion, because Deref::deref (if implemented) is called in any case.

I think that might not be 100% correct. Isn't it only the immutable methods (i.e. methods taking &self) in case of Deref, and the mutable methods (i.e. methods taking &mut self) in case of DerefMut? Calling methods that work on self (without reference) doesn't work (see playground).

And isn't this just a special case of the following coercion listed in the reference?

  • &T or &mut T to &U

Not sure if the receiver of a method call is subject through coercion though (regarding the coercion sites mentioned by @trentj)? The reference says:

For method calls, the receiver (self parameter) can only take advantage of unsized coercions.

…and going from T to U is already the dereference (considering T is a smart pointer to U), thus Deref::deref going from &U to &T can/should be seen as sort of "dereference" too… :exploding_head: That is an interesting way to see it. (Hope I got it right now.)

I guess the confusing thing here is that both

  • going from T to U
  • and going from &U to U

is a dereference.

Actually we got two dereferences (and one reference) happening when we write *x, because *x expands to *Deref::deref(&x):

  • &x goes from T to &T,
  • Deref::deref goes from &T to &U,
  • and * goes from &U to U.

Overall, we go from T to U, which is our overall "dereference" (and Deref::deref plays the integral part here).

Note that technically Deref::deref is not called at all when dereferencing built-in things like &T, &mut T or Box<T>. The situation is similar for other operators, e.g. a + b for e.g. i32 doesn’t actually call the Add::add method. But also

  • that’s an implementation detail, it doesn’t really make a difference to the user whether *x calls Deref::deref for something like &T
  • in a generic function, Deref::deref is called after all, so e.g. fn foo<T: Deref>(x: T) where T::Target: Copy { let y = *x; } will introduces a call to Deref::deref even if T is something like &i32.

fn foo(self) methods are also considered. If the receiver type isn’t Copy, this might however result in error messages saying something about that a value cannot be moved out of, unless you’re dealing with x: Box<T> which supports moving the T out of *x.

For the full picture, take a look at the link I provided above

which contains an explanation of exactly which methods are considered in which order.

Not it’s not a special case since the receiver of a method call is not subject to coercion. Also method resolution does more than just dereferencing. It’s also capable of introducing one layer of references. (This is e.g. why &self methods don’t require you to write (&x).foo().)

interesting phrasing in the reference; slightly confusing IMO because dereferencing is also connected to coercions, though arguably the dereferencing in method resolution is not the kind of dereferencing supported by coercions (again &T->&U vs T->U). Again, read the entry on “method resolution” I’ve linked above for the full picture.

1 Like

This is just a drive-by comment about understanding place expressions via one's existing intuition around indexing expressions.

If you're following this discussion, I'm sure you're used to working with indexing operations involving non-Copy objects.

fn f(slice: &[String]) {
    // Fine
    // let len = slice[0].len();

    // Also fine
    // let s /* : &String */ = &slice[0];

    // error[E0508]: cannot move out of type `[String]`, a non-copy slice
    let s = slice[0];
    let len = s.len();

    println!("{}", len);

But note that Index is a trait-based operation. Clearly the implementation doesn't rely on the contents being Copy, since we can index into the slice and then call a method on the returned value (slice[0].len()). And we can take references to the contents too (let s = &slice[0]); this just isn't a precedence thing either (try let s = (&slice)[0]).

How is the implementation sometimes moving and sometimes not? Is this compiler magic for slices maybe? Spoilers: It's not the implementation and it's not magic -- it's place expressions.

Looking closer, the Index trait is:

    type Output: ?Sized;
    fn index(&self, index: Idx) -> &Self::Output;

Still confusing. This is a reference-to-reference conversion, like Deref. So why is there moving at all? And the types don't line up -- slice[0] is returning String in the examples above, not a reference.

The key is that

container[index] is actually syntactic sugar for *container.index(index) , but only when used as an immutable value. If a mutable value is requested, IndexMut is used instead. This allows nice things such as let value = v[index] if the type of value implements Copy .

Aha -- like *t for non-references and non-pointers, except you get a method call resolution thrown in there too.

This is why the associated type is called Output -- even though the implementation returns a reference, when used as an operator that reference is immediately dereferenced -- and the result of that derefencing is a place expression of type Output. It can even be a non-Sized type (consider indexing on a Range). But nothing has moved yet -- whether or not that is attempted depends on the context of the place expression.

E.g. if you try to assign it, you'll try to move it. Which other places? All of those that lead to errors about moving out of a non-copy slice -- something you probably already have a feel for.

In summary, index expressions like container[index] are place expressions, and you probably already have an intuition around how to work with those. The same thing applies to field accesses as well, incidentally. I find the Index trait and operator definition more useful for explanation though, as

  • it supplies some explicit signatures to (hopefully) better understand what's going on
  • it ties back to how the unary * operator works and the fact that a dereference is a place expression
  • you can sometimes move a non-Copy type out of a field access (leaving the parent structure partially uninitialized), making things a little more complicated

because of method resolution's ability to automatically introduce a layer of references, e.g. when container: T and T: Index<usize> the expression *container.index(index) can further desugar to *Index::index(&container, index), making this desugaring of container[index] even more similar to the desugaring of *x as *Deref::deref(&x).

However the fact that full method resolution was involved also highlights some differences between indexing and dereferencing. E.g. if container: &T where T: Index<usize>, then the method resolution will not introduce any extra referencing, making the desugaring *Index::index(container, index) in this case; furthermore the indexing notation can also follow auto-dereferencing (and an unsized coercion) due to method resolution: e.g. if container: T, T: Deref<Target = U> and U: Index<usize>, then container[index], i.e. *container.index(index), becomes *Index::index(&*container), which of course because the * converts T to U further desugars to *Index::index(&*Deref::deref(&container)) which can be simplified as *Index::index(Deref::deref(&container)) (because the &* on a &U is redundant).

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.