The behaviour of a Rust method call is described in The Rust Reference. It states that "when looking up a method call, the receiver may be automatically dereferenced or borrowed in order to call a method." The behaviour is explained in more detail further into the chapter.
In the example, you can see that into_char moves Suit, while to_char borrows Suit.
into_char, it is called by a value and a reference, and the reference is automatically dereferenced.to_char, it is also called by a value and a reference, and the value is automatically borrowed.
From is implemented for both a value and a reference of Suit. Therefore, when called by a value Into<Suit>::into() is called, and when called by a reference Into<&Suit>::into() is called.
However, shouldn't I only need to implement one of these traits? When I comment out either implementation the Rust compiler does not compile...
The process appears to be as follows... First generate a list of "candidate receiver types". This is obtained by "repeatedly dereferencing", and finally "attempting an unsized coercion at the end". In addition, "for each candidate T, add &T and &mut T to the list immediately after T." Then, for each of these "candidate receiver types" look for "methods implemented directly on T" and "methods provided by a visible trait implemented by T".
Consider that just From<Suit> is implemented for char. Then Into<Suit> should be implemented for char.
When
let c: char = value.into();is called, the "candidate receiver types" should contain at leastSuit&Suitand&mut Suit. Then,Into<T>::into()is easily resolved for the first item in the list. Hence,Into<Suit>::into()is called.But, when
let f: char = reference.into();is called, the "candidate receiver types" should also contain at least&Suit,&&Suit,&mut &Suit,*&Suit = Suit,&Suit(again) and&mut Suit. ThenInto<T>::into()cannot find an implemented for&Suit,&&Suitand&mut &Suit, but does then find an implementation for*&Suit = Suit. Hence,Into<Suit>::into()is called.
Is my logic correct? Why doesn't this work if I comment-out one of the From implementations?
#[derive(Clone, Copy)]
pub enum Suit {
Club,
Diamond,
Heart,
Spade,
}
pub use Suit::*;
impl Suit {
#[inline(never)]
pub fn into_char(self) -> char {
match self {
Club => 'C',
Diamond => 'D',
Heart => 'H',
Spade => 'S',
}
}
#[inline(never)]
pub fn to_char(&self) -> char {
match self {
Club => 'C',
Diamond => 'D',
Heart => 'H',
Spade => 'S',
}
}
}
impl std::convert::From<Suit> for char {
fn from(suit: Suit) -> Self {
suit.into_char()
}
}
impl std::convert::From<&Suit> for char {
fn from(suit: &Suit) -> Self {
suit.to_char()
}
}
fn main() {
let value = Club;
let reference = &value;
let a: char = value.into_char();
let b: char = value.to_char();
let c: char = value.into();
println!("{}, {}, {}", a, b, c);
let d: char = reference.into_char();
let e: char = reference.to_char();
let f: char = reference.into();
println!("{}, {}, {}", d, e, f);
}
EDIT: As discussed below, I created a better reproduction of the problem, which does help narrow down what causes this behaviour.
EDIT: I have created a GitHub issue for the Rust compiler.
This happens because when matching traits the compiler does not take later inference types into account. So, while we know that
into()is expected to produceB, the compiler doesn't know that yet. So all it sees is that we want an implementation ofIntoSuit as Into<_>, for some type_. Because it doesn't know what type_is, this matchesimpl<U, T: From<U>> Into<T> for U, seen by the compiler asInto<_> for _, as it is unable to verify thewhere T: From<U>because_can be any type. So the compiler picks this implementation (which has higher priority because it does not use autoref) and does not search further forimpl Into<_> for &Suit.Later, when the compiler comes back to this decision, armed with the knowledge that we expect a
char, it realizes that this impl no longer match - but it doesn't go back to search impls for autoref types; it is done with that. So we end up with no matching impl.