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强制转换的触发前提是:当前类型是引用类型,且该引用指向的类型实现了Dereftrait。- 当你写
&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
相关产品推荐
相关产品推荐

