英文:
Why do I need to implement `From` for both a value and a reference? Shouldn't methods be automatically dereferenced or borrowed?
问题
Rust方法调用的行为在The Rust Reference中有描述。它声明“在查找方法调用时,接收者可能会自动解引用或借用以调用方法”。行为在本章的后面更详细地解释了。
在示例中,您可以看到into_char
将Suit
移动,而to_char
借用了Suit
。
-
into_char
,它被值和引用调用,引用会自动解引用。 -
to_char
,它也被值和引用调用,值会自动借用。
From
为Suit
的值和引用都实现了。因此,当被值调用时,会调用Into<Suit>::into()
,当被引用调用时,会调用Into<&Suit>::into()
。
但是,我难道不只需要实现其中一个trait吗?当我注释掉其中一个实现时,Rust编译器不会编译...
这个过程似乎是这样的...首先生成一个“候选接收者类型”的列表。这是通过“重复解引用”获得的,并在最后“尝试非大小的强制转换”。此外,对于每个“候选类型T”,在T之后立即添加&T和&mut T。然后,对于这些“候选接收者类型”中的每一个,查找“直接在T上实现的方法”和“由T实现的可见trait提供的方法”。
考虑只为char
实现了From<Suit>
。然后应该为char
实现Into<Suit>
。
-
当调用
let c: char = value.into();
时,“候选接收者类型”应该至少包含Suit
、&Suit
和&mut Suit
。然后,Into<T>::into()
很容易为列表中的第一个项解析。因此,调用Into<Suit>::into()
。 -
但是,当调用
let f: char = reference.into();
时,“候选接收者类型”也应至少包含&Suit
、&&Suit
、&mut &Suit
、*&Suit = Suit
、&Suit
(再次)和&mut Suit
。然后,Into<T>::into()
无法找到&Suit
、&&Suit
和&mut &Suit
的实现,但随后找到了*&Suit = Suit
的实现。因此,调用Into<Suit>::into()
。
我的逻辑正确吗?如果我注释掉其中一个From
实现,为什么这不起作用?
#[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);
}
编辑:正如下面讨论的那样,我创建了一个更好的问题重现(https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=2b46d12ea13f660bc87ea09e90315daa),它有助于缩小造成此行为的原因。
编辑:我已经为Rust编译器创建了一个GitHub问题(https://github.com/rust-lang/rust/issues/112951)。
英文:
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
&Suit
and&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
,&&Suit
and&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.
答案1
得分: 2
这是因为在匹配特性时,编译器不考虑后续的类型推断。因此,虽然我们知道into()
应该生成B
,但编译器还不知道这一点。所以它只看到我们要一个Into
的实现 Suit as Into<_>
,对于某个类型_
。因为它不知道类型_
是什么,这与编译器看到的 impl<U, T: From<U>> Into<T> for U
匹配,编译器看到它为 Into<_> for _
,因为它无法验证 where T: From<U>
,因为_
可以是任何类型。因此,编译器选择了这个实现(因为它没有使用自动引用,所以优先级较高),不再搜索impl Into<_> for &Suit
。
稍后,当编译器再次考虑这个决策时,已经知道我们期望一个char
,它意识到这个实现不再匹配 - 但它不会回头搜索自动引用类型的实现;它已经完成了。所以我们最终没有匹配的实现。
英文:
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 produce B
, the compiler doesn't know that yet. So all it sees is that we want an implementation of Into
Suit as Into<_>
, for some type _
. Because it doesn't know what type _
is, this matches impl<U, T: From<U>> Into<T> for U
, seen by the compiler as Into<_> for _
, as it is unable to verify the where 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 for impl 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论