Help in this type coercion (I don’t know how title this properly)

Sorry if I am being incompetent;

let a = String::from(“aaa”) ;
let b: &str = &a;

Here this is “tried” simple-reference-coercion example but with custom types

 struct A {
    a: u32,
}

impl core::convert::AsRef<u8> for A {
    fn as_ref(&self) -> &u8 {
        &5
    }
}
fn main() {
    let a: A = A {a: 5};
    let b: &u8 = &a;
}

This errors, I predict you suggest by simply change the line with “let b: &u8 = &a” into “let b: &u8 = a.as_ref()”
But I want is that is there a trait that; without need of using method onto custom type (without b.as_ref(), etc) that can implicitly coerce 1 reference of custom type to another reference custom type or is there no such thing as applies to compiler’s primitive types like references of a string coerces into str without need of method onto.

Also if it does; in

does the “coercion” of custom types work when the custom type is in argument of a function or method in his example or is it compiler monomorphisation

Automatic coercion requires the Deref trait.

struct A {
    a: u32,
}

impl core::ops::Deref for A {
    type Target = u8;
    fn deref(&self) -> &u8 {
        &5
    }
}
fn main() {
    let a: A = A {a: 5};
    let b: &u8 = &a;
}

This

let b: &str = &a;

works due to what we call "deref coercion", where &T can coerce to &<T as Deref>::Target.

So Deref is the trait in question.

Note that with Deref, there's only one possibility. You can implement AsRef<T> for as many T as you like.

Deref coercion applies to function arguments. Method resolution is different than deref coercion, though it covers the same cases (and more).

What exactly happens depends on the signature. If the argument type is generic, it won't deref coerce without something like a turbofish.[1] You need a concrete "target type". (If there are no generics, there is no monomorphization).

If T: AsRef<X>, then &T: AsRef<X> too, recursively (&&&&&&T: AsRef<X>). So if you pass a &&&&String to some function expecting S: AsRef<str>, you'll monomorphize on &&&&String. Deref coercion doesn't apply here.

Here are some examples.


In that example it's monomorphization; the types they're passing in meet the AsRef<_> bound.

    takes_user(&user);
    takes_user(&moderator); // yay

Incidentally I suggest that -- if you are going to be generic -- use this pattern.

fn takes_user<U: ?Sized + AsRef<User>>(user: &U) {
    fn takes_user_internal(&User) {
        // ...
    }
    takes_user_internal(user.as_ref())
}

The internal function is to minimize the impact of potentially a lot of monomorphization. I.e. to maximize the chance that call sites will just inline as if they called as_ref (and if not, at least you're monomorphizing a bunch of tiny functions).

As for taking by reference: If all you have is an U: AsRef<User>, the only useful thing to do with an owned U is to pass a reference to the owned value to AsRef::as_ref. So you might as well take a reference to begin with. Otherwise,

  • It's possible to accidentally pass in e.g. your owned moderator and have the function call compile, but then have something else fail to compile because you gave away ownership. This can in turn lead to unnecessary cloning.
  • With AsRef<str> and the like, the <S: AsRef<str>>(s: S) pattern means that when you pass a &str, you call <&str as AsRef<str>>:as_ref by passing a &&str. With the pattern above, you call <str as AsRef<str>>::as_ref by passing &str. (Note that str needed AsRef<Self> for this to work...)
    • The ?Sized bound is needed for AsRef<str> to work as intended when taking the argument by reference. But it can be useful with AsRef<T> when T: Sized too due to trait objects and the like.

As you've noted, impl AsRef<User> for User seems a little pointless since you can just do &user.

It's not useless in a generic context. The only reason there is no impl<T: ?Sized> AsRef<T> for T in std is that it conflicts with the blanket implementation so that T: AsRef<U> implies &T: AsRef<U>.


  1. AFAIK ↩︎