cannot move out of a mutable reference
主线位置:任务主线开始之前。慢 poll 和长排队的阈值在建 monitor 时定下,之后每次
instrument_poll都拿它分桶。
这一条不在本卷的规划里,是在在本站中实测时给搜索重建的 monitor 设 10ms 阈值,第一次编译就撞上的。
flowchart TB b1["TaskMonitor::builder()"] --> w1["with_*:&mut self 进,&mut Self 出"] w1 --> bd1["build(self):要拿走所有权"] bd1 --> e["E0507:从可变引用里搬不出来"] b2["TaskMonitorCore::builder()"] --> w2["with_*:self 进,Self 出"] w2 --> bd2["build(self)"] bd2 --> ok["一行链到底"]
复现
use std::time::Duration;
use tokio_metrics::TaskMonitor;
fn main() {
let monitor = TaskMonitor::builder()
.with_slow_poll_threshold(Duration::from_millis(10))
.build();
println!("{:?}", monitor.slow_poll_threshold());
}
error[E0507]: cannot move out of a mutable reference
--> src/bin/builder.rs:5:17
|
5 | let monitor = TaskMonitor::builder()
| _________________^
6 | | .with_slow_poll_threshold(Duration::from_millis(10))
| |________________________________________________________^ move occurs because value has type `tokio_metrics::task::TaskMonitorBuilder`, which does not implement the `Copy` trait
7 | .build();
| ------- value moved due to this method call
|
note: `tokio_metrics::task::TaskMonitorBuilder::build` takes ownership of the receiver `self`, which moves value
--> <registry>/tokio-metrics-0.5.2/src/task.rs:649:18
|
649 | pub fn build(self) -> TaskMonitor {
| ^^^^
help: consider cloning the value if the performance cost is acceptable
|
6 | .with_slow_poll_threshold(Duration::from_millis(10)).clone()
| ++++++++
为什么:两套 builder,一个按引用,一个按值
TaskMonitorBuilder 的三个设置方法都收 &mut self、返回 &mut Self,build 收 self:
impl TaskMonitorBuilder {
// ...
pub fn with_slow_poll_threshold(&mut self, threshold: Duration) -> &mut Self {
self.0.slow_poll_threshold = Some(threshold);
self
}
// ...
pub fn build(self) -> TaskMonitor {
TaskMonitor {
base: Arc::new(self.0.build()),
}
}
}
TaskMonitor::builder() 返回的临时值在链里被借成 &mut,with_slow_poll_threshold 返回的还是这个引用,build 要从引用里拿走值,借用检查不允许。
同一个文件里的 TaskMonitorCoreBuilder 是另一种风格:设置方法是 const fn with_slow_poll_threshold(self, ...) -> Self,按值进按值出,为的是能在 static 的初始化表达式里调用。TaskMonitorBuilder 的 &mut self 签名在 0.4.8 就是这样;0.4.9 拆出 TaskMonitorCore 时加了按值的这一套,原来那一套的签名没有改(改了就是破坏性变更),于是同一个库里两个 builder 的链式写法一个能用、一个不能(设计取舍「两个变体」一节提到的「两套 builder」)。
编译器建议的 .clone() 能编译(TaskMonitorBuilder 派生了 Clone),但它克隆了 builder 再丢掉原来那个,读代码的人会以为有什么理由非克隆不可。
修法
use std::time::Duration;
use tokio_metrics::{TaskMonitor, TaskMonitorCore};
static CORE: TaskMonitorCore = TaskMonitorCore::builder()
.with_slow_poll_threshold(Duration::from_millis(10))
.build();
fn main() {
// 1. 先绑定,再逐个设置
let mut b = TaskMonitor::builder();
b.with_slow_poll_threshold(Duration::from_millis(10))
.with_long_delay_threshold(Duration::from_millis(1));
let a = b.build();
// 2. 只改慢 poll 阈值时有现成的构造函数
let c = TaskMonitor::with_slow_poll_threshold(Duration::from_millis(10));
// 3. TaskMonitorCore 的 builder 是按值的 const fn,可以一路链下去
println!("{:?} {:?} {:?} {:?}", a.slow_poll_threshold(), a.long_delay_threshold(), c.slow_poll_threshold(), CORE.slow_poll_threshold());
}
10ms 1ms 10ms 10ms
- 两个阈值都要改、或者要
publish_scheduling_delay(),用写法 1。 - 只改慢 poll 阈值,
TaskMonitor::with_slow_poll_threshold一个调用就够;本站实测里用的就是它。 - monitor 本来就要放进
static,直接用写法 3。
小结
TaskMonitorBuilder的设置方法收&mut self,build收self,一行链下来就是E0507。TaskMonitorCoreBuilder是按值的const fn,同样的链能编译;两套风格并存是 0.4.9 拆分的遗留。- 修法:先绑定再设置,或者用
TaskMonitor::with_slow_poll_threshold,或者改用TaskMonitorCore;不要照编译器的建议加.clone()。
💬 关联讨论
发起讨论加载中...