【问题标题】:Why is an explicit dereference required in (*x).into(), but not in x.my_into()?为什么 (*x).into() 需要显式取消引用,而 x.my_into() 不需要?
【发布时间】:2020-01-24 17:29:54
【问题描述】:

看了method-call expressionsdereference operatormethod lookupauto-dereferencing之后,我觉得我对这个主题有了很好的理解;但后来我遇到了一种情况,我希望自动取消引用会发生,而实际上它并没有发生。

示例如下。

#[derive(Clone, Copy, Debug)]
struct Foo();

impl Into<&'static str> for Foo {
    fn into(self) -> &'static str {
        "<Foo as Into>::into"
    }
}

fn vec_into<F: Copy + Into<T>, T>(slice: &[F]) -> Vec<T> {
    slice.iter().map(|x| (*x).into()).collect()
}

fn main() {
    let array = [Foo(), Foo(), Foo()];
    let vec = vec_into::<_, &'static str>(&array);
    println!("{:?}", vec);
}

上面的代码有效,但我认为不需要在函数vec_into 中显式取消引用(*x).into()。我的推理是,既然x: &amp;Foo,那么x.into() 会尝试找到接受类型&amp;Foo&amp;&amp;Foo&amp;mut &amp;FooFoo&amp;Foo&amp;mut Foo 的方法。

这是因为存在取消引用&amp;FooFoo 的链,并且对于此链中的每个U,我们还插入&amp;U&amp;mut U

我的直觉得到以下事实的证实:以下代码也有效,无需任何明确的取消引用。

#[derive(Clone, Copy, Debug)]
struct Foo();

trait MyInto<T> {
    fn my_into(self) -> T;
}

impl MyInto<&'static str> for Foo {
    fn my_into(self) -> &'static str {
        "<Foo as MyInto>::my_into"
    }
}

fn vec_my_into<F: Copy + MyInto<T>, T>(slice: &[F]) -> Vec<T> {
    slice.iter().map(|x| x.my_into()).collect()
}

fn main() {
    let array = [Foo(), Foo(), Foo()];
    let my_vec = vec_my_into(&array);
    println!("{:?}", my_vec);
}

这里 x: &amp;Foo 被隐式取消引用,以便调用方法 &lt;Foo as MyInto&lt;&amp;'static str&gt;&gt;::my_into

一个小例子

鉴于以上FooMyInto的定义,代码

let result: &str = (&Foo()).my_into()

有效,但是

let result: &str = (&Foo()).into()

编译失败

error[E0277]: the trait bound `&str: std::convert::From<&Foo>` is not satisfied
  --> src/bin/into.rs:34:33
   |
34 |     let result: &str = (&Foo()).into();
   |                                 ^^^^ the trait `std::convert::From<&Foo>` is not implemented for `&str`
   |
   = note: required because of the requirements on the impl of `std::convert::Into<&str>` for `&Foo`

【问题讨论】:

标签: rust traits implicit-conversion dereference


【解决方案1】:

有趣的是,在检查编译器输出后,Into 实现似乎实际上只是调用了 trait From 的方法。它正在寻找的是std::collections::From&lt;&amp;Foo&gt; for &amp;str。因此,如果我们实现该 trait,编译器确实会找到我们的函数并执行它。

使用以下声明:

#[derive(Clone, Copy, Debug)]
struct Foo();

impl std::convert::From<&Foo> for &str {
    fn from(f: &Foo) -> &'static str {
        "A &str"// This could be replaced with an actual implementation
    }
}

您的代码可以按您的意愿工作:

let result: &str = (&Foo()).into();// No compiler errors.

您想要的原始代码确实有效,并且实际上并不难实现。

【讨论】:

    【解决方案2】:

    Rust 完全按照您的描述执行方法查找,它会立即找到 .into() 的候选对象 - blanket implementation

    impl<T, U> Into<U> for T
    where
        U: From<T>,
    {
        fn into(self) -> U {
            U::from(self)
        }
    }
    

    此实现满足候选方法的所有要求——它是可见的、在范围内并为&amp;Foo 类型定义的,因为它是为any 类型T 定义的。一旦编译器选择了这个候选者,它就会注意到 U 上的特征边界不满足,并发出您看到的错误。

    MyInto 的情况完全不同,因为您没有提供基于From 的全面实现。如果你这样做,你会得到同样的错误。

    如果不满足特征边界,编译器应该跳过一揽子实现,并继续使用候选类型列表,直到找到更合适的类型。语言规范在这一点上实际上并不完全清楚,但是从错误中我们可以清楚地知道编译器实际上做了什么。

    【讨论】:

    • 我希望 Chalk™ 能解决这个问题。
    • @Shepmaster 每当我看到任何类型推断或约束求解器的怪异之处时,我都有同样的希望,但我实际上不知道确切的差异是什么,或者从它开始需要多少年登陆 rustc。
    • 感谢您的解释。如果T 上有不满意的特征界限,这不会发生,对吧?我的意思是,在这种情况下,将跳过实现,继续寻找合适的方法。我说的对吗?
    猜你喜欢
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 2013-04-01
    • 2018-03-18
    • 1970-01-01
    • 2018-07-30
    • 2011-06-18
    • 2021-04-13
    相关资源
    最近更新 更多