Why AsRef is not implemented for T and &T?


#1

Pretty simple question - what is the reason that we don’t have in std:

impl<T> AsRef<T> for T
impl<'a, T> AsRef<T> for &'a T

#2

IIRC, it conflicts with other impls?


#3

Like which ones? It is implemented only for Rc, Arc and Box?


#4

It is implemented for quite a few types: https://doc.rust-lang.org/std/convert/trait.AsRef.html


#5

I agree, but sometimes I want to wirte a generic function which takes AsRef<T> meaning both T and &T. However if T is not in std and if AsRef was not implemented by the other library authors, which can be as easy as:

impl AsRef<T> for T {
   fn as_ref(self) -> &Self{
        return &self
   }
}

I can not achieve what I want.


#6

Like the docs say;

AsRef is to be used when wishing to convert to a reference of another type.

You would also get conflict eg

impl<T> AsRef<T> for Box<T> where
    T: ?Sized, 

This doesn’t give you &Box but the compiler would not know how to choose it over impl<T> AsRef<T> for T


#7

Those don’t conflict though. If type inference cannot determine what’s needed, the compiler would ask you to provide type annotations to disambiguate. I don’t see this being any different from Borrow having a blanket impl (as well as a impl<T> Borrow<T> for Box<T>, coincidentally).


#8

For functions the workaround is to remove the AsRef<T> bound and instead just take &T parameter. Callers that know how to obtain such a reference (regardless of whether they also implement AsRef), including an owned T they have, can just pass the reference directly. The upside is you don’t get code bloat for your function.


#9

I definitely agree, however, I still don’t see a too well compelling function of why not save an extra & being written everywhere. Also, if you do operator overloading fixing your function to be &T makes you do a + &(c + &d) which kinda of ugly. And in this case, for instance, I have quite a repeated code for both Add<T> and Add<&'a T> where the first one just calls the second.


#10

What’s wrong with this?

use std::borrow::Borrow;

struct MyStruct;


fn takes_anything<T>(borrowable: T) where T: Borrow<MyStruct>{
}

fn main(){
    let mut example = MyStruct;
    
    takes_anything(&example);
    takes_anything(&mut example);
    takes_anything(example);
}

On a related note, I’m not fully sold on the difference between AsRef and Borrow. Even their signatures are the same…


#11

Yeah, from a technical perspective they look identical. The semantic difference is that Borrow says that Eq/Hash are compatible for the Borrowed type to the type implementing Borrow, whereas AsRef makes no such stipulations. This doesn’t matter in the reflexive case, but does in the general one.


#12

hmmm…so what happens if you violate that rule? I’m reading through the HashMap source and I’m seeing this, for example:

fn search<'a, Q: ?Sized>(&'a self, q: &Q) -> InternalEntry<K, V, &'a RawTable<K, V>>
    where K: Borrow<Q>,
          Q: Eq + Hash
{
    let hash = self.make_hash(q);
    search_hashed(&self.table, hash, |k| q.eq(k.borrow()))
}

where it looks like you’re able to search the HashMap not only by the key, but by anything that can be borrowed from the key. Then I guess this functionality would be broken if you violated that rule.

Not to derail this thread, but what’s the motivation for this level of flexibility in HashMap?


#13

Right, the implication is that the code will be broken (e.g. won’t find an item in the HashMap).

The motivation is to allow things like querying a HashMap<String, ...> using &str keys - otherwise you’d need to allocate a String for the lookups.


#14

Guys, I’m still not convinced why we don’t have impl<T> AsRef<T> for T?


#15

Current trait implementation rules don’t allow that one. It conflicts with other impls. I think this is the critical one that it conflicts with:

impl<'a, T, U> AsRef<U> for &'a T where
    T: AsRef<U> + ?Sized,
    U: ?Sized,

(And Borrow does not have a converse to this one.)


#16

I might be stupid, but why is that the conflict? If I understand correctly this would imply that if we have impl<T> AsRef<T> for T we also have impl<'a, T> AsRef<T> for &'a T. That should not be a conflicting one, no?


#17

what is if T and U are the same?


#18

Yeah, that’s the conflict. But, the question really is whether it’s better to have this T to U blanket impl version or the blanket reflexive impls. I know this is water under the bridge at this point, but I think the reflexive ones would have been more useful. I suppose, though, that this reinforces the motivation described in the rust docs for AsRef where it says (emphasis mine):


#19

idk but Borrow has a impl<T> Borrow<T> for T where T: ?Sized


#20

Right. And Borrow is obviously also interested in going from a “T to U” (in fact that’s probably its whole reason for existence) with the sole semantic difference, AFAICT, of “requiring” hash/eq properties to hold . So in fact, the whole thing looks confusing :slight_smile:.