Как избежать перемещения возможно неинициализированной переменной для интерфейса MutexGuard?

#rust #tokio

Вопрос:

для следующего кода:

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

CONVMODEL.lock().await есть MutexGuard<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 { amp;*locked } else { amp;loaded };
 

но я позвонил, потому что

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

Поэтому вопрос действительно в том, как работать MutexGuard с интерфейсом amp;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();
        amp;*lock
      } else {
        load = loaded.unwrap();
        amp;load
      };
 

следующий код работает, но на самом деле очень уродливый XD
(Интересно, можно ли упростить этот код)

Комментарии:

1. Вы должны просто использовать код из принятого ответа, т. Е. Переместить определение conversation_model из вашего закрытия и проверить только lsm один раз.

2. (Принятый ответ на момент написания комментария принадлежал пользователю 4815162342.)

3. не удается заблокировать ожидание внутри spawn_blocking

4.Это не то, что я предлагал. Я предложил перенести назначение conversation_model из закрытия, используя точно код в ответе пользователя 4815162342.

5. @SvenMarnach Я согласен, что подход пользователя 4815162342 в целом верен, но он отвечал на неполный вопрос, поскольку OP не включал полный контекст. Этот подход не будет работать, spawn_blocking так как ему необходимо перейти conversation_model к закрытию со 'static сроком службы, а это не относится к временным объектам, хранящимся в locked и loaded . Вы должны фактически переместить источник ссылок в закрытие, чтобы достичь этого, и сделать свой ссылочный пост-ход. Я отредактирую свой ответ, чтобы объяснить эту разницу.

Ответ №1:

Вы можете извлечь amp;mut T и то, и другое и использовать это. Должно сработать что-то вроде следующего:

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

Комментарии:

1. не могу задать вопрос за 90 минут LOL (я ненавижу stackoverflow), но моя идея не работает, и мне все еще нужна помощь, поэтому я отредактирую этот вопрос (я думаю)

Ответ №2:

Всякий раз, когда у вас есть какой-то набор вариантов для ценности, вы хотите достичь enum ее . Например, в Rust мы не делаем ничего подобного let value: T; let is_initialized: bool; , мы делаем Option<T> .

У вас есть выбор из двух значений: либо приобретенный мьютекс, либо прямое значение. Обычно это называется «либо», и существует популярный ящик для ржавчины, содержащий этот тип: 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 amp;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(amp;self) -> amp;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();
});
 

(Полный пример.)

Это гораздо более выразительно и отодвигает вопрос «как это заполнить» от того места, где он используется.


В общем случае вы хотите использовать более простой подход, который показал пользователь4815162342. Вы сохраните один из временных файлов, сформируете ссылку на него (зная, что вы его только что инициализировали) и передадите его обратно.

Однако это не работает с spawn_blocking , однако. Срок службы ссылки равен сроку службы временных объектов — передача такой ссылки порожденной задаче является висячей ссылкой.

Вот почему сообщения об ошибках (в форме «заимствованное значение не живет достаточно долго» и «аргумент требует, чтобы locked он был заимствован для 'static «) направляли вас на то, чтобы попытаться перейти locked к loaded закрытию, чтобы быть в месте их последнего упокоения, а затем сформировать ссылку. Тогда ссылка не болталась бы.

Но тогда это подразумевает, что вы перемещаете возможно неинициализированное значение в закрытие. Ржавчина не понимает, что вы используете идентичную проверку, чтобы увидеть, какое временное значение заполнено. (Вы могли бы представить себе опечатку во второй проверке !lsm , и теперь вы переключились.)

В конечном счете, вы должны переместить источник значения в созданную задачу (закрытие), чтобы сформировать ссылки с допустимым временем жизни. Использование перечисления в основном кодирует вашу логическую проверку регистра в нечто, что Rust понимает и будет распаковываться естественным образом.