#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 понимает и будет распаковываться естественным образом.