如何避免为 MutexGuard 接口移动可能未初始化的变量?

How to avoid move of possibly-uninitialized variable for MutexGuard interface?

以下代码:

  let conversation_model =
    if lsm { CONVMODEL.lock().await } else {
      conv_model_loader()
    };

CONVMODEL.lock().awaitMutexGuard<T>conv_model_loader() 只是 T

我需要这两者的通用接口,所以我不能为两种情况复制粘贴我的代码,因为它只会与这种类型不同,其他都是一样的。

编辑:

有代码...(至少我想做的)

  let (locked, loaded);  // pun not intended
  if lsm {
    locked = CONVMODEL.lock().await;
  } else {
    loaded = conv_model_loader();
  };
  let mut chat_context = CHAT_CONTEXT.lock().await;
  task::spawn_blocking(move || {
    let conversation_model = if lsm { &*locked } else { &loaded };

但我失败了,因为

use of possibly-uninitialized variable: `locked`\nuse of possibly-uninitialized `locked`

所以问题实际上是如何让 MutexGuard 与接口 &T 一起使用,但在 spawn_blocking 内部以及 #[async_recursion]

中使用它

编辑:

  let (mut locked, mut loaded) = (None, None);
  if lsm {
    locked = Some( CONVMODEL.lock().await );
  } else {
    loaded = Some( conv_model_loader() );
  };
  let mut chat_context = CHAT_CONTEXT.lock().await;
  task::spawn_blocking(move || {
    let (lock, load);
    let conversation_model =
      if lsm {
        lock = locked.unwrap();
        &*lock
      } else {
        load = loaded.unwrap();
        &load
      };

以下代码有效但实际上非常难看 XD (我想知道是否可以简化这段代码)

您可以从两者中提取 &mut T 并使用它。像下面这样的东西应该可以工作:

let (locked, loaded);  // pun not intended
let conversation_model = if lsm {
    locked = CONVMODEL.lock().await;
    &mut *locked
} else {
    loaded = conv_model_loader();
    &mut loaded
};

每当您对一个值有一些选择时,您就想达到 enum。例如,在 Rust 中我们不做 let value: T; let is_initialized: bool; 之类的事情,我们做 Option<T>.

您有两个值可供选择,要么是获得的互斥量,要么是直接值。这通常被称为“任一个”,并且有一个流行的 Rust crate 包含这种类型:Either。对你来说,它可能看起来像:

    use either::Either;

    let conv_model = if lsm {
        Either::Left(CONVMODEL.lock().await)
    } else {
        Either::Right(conv_model_loader())
    };

    tokio::task::spawn_blocking(move || {
        let conversation_model = match &conv_model {
            Either::Left(locked) => locked.deref(),
            Either::Right(loaded) => loaded,
        };

        conversation_model.infer();
    });

(Full example.)

此类型曾经存在于标准库中,但已被删除,因为它不常被使用,因为创建更具描述性的特定于域的类型相当简单。我同意这一点,你可能会这样做:

pub enum ConvModelSource {
    Locked(MutexGuard<'static, ConvModel>),
    Loaded(ConvModel),
}

impl Deref for ConvModelSource {
    type Target = ConvModel;
    
    fn deref(&self) -> &Self::Target {
        match self {
            Self::Locked(guard) => guard.deref(),
            Self::Loaded(model) => model,
        }
    }
}

// ...

let conv_model = if lsm {
    ConvModelSource::Locked(CONVMODEL.lock().await)
} else {
    ConvModelSource::Loaded(conv_model_loader())
};

tokio::task::spawn_blocking(move || {
    conv_model.infer();
});

(Full example.)

这更具表现力,并将“如何填充它”从使用它的地方移开。


在常见情况下,您确实希望使用 user4815162342 显示的更简单的方法。您将存储其中一个临时对象,形成对它的引用(知道您刚刚初始化了它),然后将其交还。

然而,这不适用于 spawn_blocking。引用的生命周期是临时对象的生命周期 - 将这样的引用移交给生成的任务是悬空引用。

这就是为什么错误消息(形式为“借用的值寿命不够长”和“参数要求 locked'static 借用”)引导您继续尝试将 lockedloaded 移动到闭包中以使其位于最终位置的路径, 然后 形成参考。那么引用就不会悬空了。

但这意味着您将可能未初始化的值移动到闭包中。 Rust 不理解您正在使用相同的检查来查看填充了哪个临时值。 (你可以想象第二次检查时出现错字!lsm,现在你已经切换了。)

最终,您必须将值的来源移动到生成的任务(闭包)中,以便您形成具有可用生命周期的引用。 enum 的使用基本上是将你的 boolean case 检查编码成 Rust 理解的东西并且会自然地解压。