You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中为何需用&触发Deref强制转换?相关疑问解析

Rust Deref强制转换常见疑问解答

Deref强制转换会将实现Deref trait的类型的引用转换为另一种类型的引用

代码示例

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

fn hello(name: &str) {
    println!("Hello, {name}!");
}

fn main() {
    let m = MyBox::new(String::from("Rust"));

    // 疑问1:为何`&`是触发deref隐式调用的必要条件,m难道不是引用吗?
    hello(&m);

    // 以下代码无需&即可运行,底层完成MyBox<String> -> String -> &str的转换
    hello(m.deref().deref());

    // 疑问3:为何不能使用如下代码,无需&传递m?例如:hello(m);
}

编译器报错信息

Compiling 
error[E0308]: mismatched types
  --> src/bin/tmp3.rs:38:11
   |
38 |     hello(m);
   |     ----- ^ expected `&str`, found `MyBox<String>`
   |     |
   |     arguments to this function are incorrect
   |
   = note: expected reference `&str`
                 found struct `MyBox<String>`
note: function defined here
  --> src/bin/tmp3.rs:19:4
   |
19 | fn hello(name: &str) {
   |    ^^^^^ ----------
help: consider borrowing here
   |
38 |     hello(&m);
   |           +

For more information about this error, try `rustc --explain E0308`.
error: could not compile `luhn` (bin "tmp3") due to previous error

疑问解答

1. 为何必须使用&才能触发deref的隐式调用?m难道不是一个引用吗?

  • m是MyBox<String>类型的所有权值,不是引用。Deref强制转换的触发前提是:当前类型是引用类型,且该引用指向的类型实现了Deref trait。
  • 当你写&m时,得到的是&MyBox<String>类型的引用,此时编译器会检查这个引用是否能通过Deref转换为函数需要的&str,从而触发隐式的deref调用链。
  • 如果直接传m,它是所有权值,不符合Deref强制转换的触发条件——Deref强制转换只处理引用的转换,不处理所有权值的直接转换。

2. hello(m.deref().deref())无需&即可运行,底层会完成MyBox<String> -> String -> &str的转换;若在转换序列前添加&,会得到&MyBox<String> -> &String -> &&str,为何在hello(&m)的场景中最终会发生&&str -> &str的引用折叠?

  • 先看m.deref().deref():第一次调用deref()返回&String(因为MyBox<T>的deref返回&T,这里T是String),第二次调用String的deref(String默认实现了Deref<Target=str>)返回&str,所以最终传递给hello的是&str,刚好匹配参数类型,不需要额外的引用。
  • 而hello(&m)的转换链是:&MyBox<String> →(Deref)&String →(Deref)&&str。此时编译器会触发引用折叠规则:在类型推导过程中,&&T会自动折叠为&T,这是Rust为了避免多层引用冗余而设计的规则,最终就得到了hello需要的&str。
  • 引用折叠的核心逻辑是:当编译器遇到多层不可变引用(&&&...&T)时,会自动将其合并为一层&T,这是语言内置的类型简化机制,无需手动处理。

3. 为何不能直接使用hello(m),必须要加&来传递m?

  • hello函数的参数是&str,是一个引用类型,而m是MyBox<String>的所有权值。Rust不会自动将所有权值直接转换为引用类型——除非你显式借用(用&)或者类型实现了Into等转换trait,但这里MyBox<String>并没有实现Into<&str>。
  • 直接传递m意味着你要把MyBox<String>的所有权转移给hello,但hello需要的是&str,类型完全不匹配,编译器无法完成这种跨类型的所有权转换,因此报错。

附加疑问:将m传入hello(m)时,它不是已经被借用了吗?

  • 没有。传递m是转移所有权,不是借用。只有当你使用&m或者&mut m时,才是创建一个引用(借用)。在hello(m)中,你是把m的整个所有权交给函数,但函数参数类型不接受这个类型,所以编译器报错,和借用无关。

内容的提问来源于stack exchange,提问作者Ievgen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 08:35:29