Rust in Everything
基于 tokio-metrics 0.5.2

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()。

💬 关联讨论

发起讨论
加载中...