1

对于以下代码:

  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(我想知道是否可以简化此代码)

4

2 回答 2

3

您可以&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
};
于 2021-10-07T07:48:20.253 回答
2

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

您可以选择两个值,获取的互斥锁或直接值。这通常称为 "either",并且有一个流行的 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();
    });

(完整示例。)

这种类型曾经存在于标准库中,但由于不经常使用而被删除,因为制作更具描述性的特定于域的类型相当简单。我同意这一点,你可以这样做:

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();
});

(完整示例。)

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


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

但是,这不适用于spawn_blocking。引用的生命周期是临时对象的生命周期——将这样的引用传递给生成的任务是一个悬空引用。

这就是为什么错误消息(形式为“借来的值不够长”和“参数需要locked借来的'static”)引导你走上尝试移动lockedloaded进入闭包以进入最后休息的道路放置,然后形成参考。那么参考就不会悬空。

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

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

于 2021-10-07T09:17:00.340 回答