Застрял с синтаксисом async/await в Rust, есть ресурсы?

Я уже месяц учу Rust и постоянно упираюсь в async/await. Каждый раз, когда я пытаюсь написать асинхронную функцию, которая вызывает другую асинхронную функцию, я получаю эти запутанные ошибки lifetime'ов. Есть ли какие-то хорошие туториалы или паттерны, которые это нормально объясняют вместо того, чтобы просто выкидывать ошибки компилятора?

4 ответа

★ Лучший ответ

Ошибки времени жизни - это не то, на что вы наталкиваетесь, это симптомы чего-то более простого. Прежде чем вы начнёте ломать голову над borrow checker, попробуйте вот что: напишите свои async функции с явными типами возврата `Box<dyn Future<Output = YourType>>` вместо синтаксиса `impl Future`, по крайней мере пока учитесь. Это заставит вас увидеть ровно то, что захватывается, и сделает сообщения компилятора менее запутанными относительно того, что на самом деле не работает. Как только у вас получится рабочий вариант таким способом, вы сможете рефакторить обратно к `impl Future` и поймёте, *почему* компилятор это теперь принимает, вместо того чтобы просто менять что-то, пока ошибки не исчезнут.

Ты используешь `.await` в местах вызова, или просто вызываешь асинхронную функцию без ожидания? Ошибки lifetime часто сводятся к тому, что компилятор видит заимствованную ссылку, которая живёт короче, чем нужно фьючеру - и это не волшебство асинхронности, это просто borrow checker Rust'а делающий свою работу через await-точки. В официальной книге Async Rust (на сайте Rust) есть раздел, который разбирает ровно это, и он действительно понятнее большинства туториалов. Если ты ловишь конкретные сообщения об ошибках, попробуй выделить один маленький пример, который падает, выложи его на r/rust или на форум Rust - люди там терпеливо объясняют саму суть проблемы вместо того, чтобы шумели вокруг ошибки.

James_1984 автор вопроса Да, я использую `.await` в местах вызова. Про borrow checker через await points имеет смысл - проверю этот раздел в книге Async Rust.

перестань бороться с borrow checker'ом и вместо этого думай об async/await как о создании конечного автомата - каждая точка await'а по сути это пауза, где Rust'у нужно знать, какие данные переживут до следующей паузы. вот почему ты получаешь ошибки lifetime'ов. компилятор не тупит, он действительно не уверен, будут ли твои позаимствованные данные ещё валидны, когда выполнение возобновится.

официальная книга Rust по async (rust-lang.org/what/wasm/book) вообще хороша для этого, но мне больше помог туториал Tokio, потому что там реальные примеры вместо абстрактной теории. видеосерия Jon Gjengset'а на YouTube'е по async Rust'у щёлкает для многих - он проводит через ментальную модель пошагово, вместо того чтобы просто сваливать синтаксис. один паттерн, который развеял всё для меня: когда цепляешь async вызовы, полагайся на `tokio::spawn` или используй цепочки `.await` последовательно, чтобы не смешивать позаимствованные ссылки с async границами.

практическая штука - выпиши, какие данные твоей async функции действительно нужно держать между await'ами, потом передавай только это явно. не передавай ссылки, если можешь передать owned значения. ага, иногда это кажется расточительным, но это заставляет тебя понять issue с lifetime'ами вместо того, чтобы драться с сообщениями об ошибках. ещё поэкспериментируй на playground'е (play.rust-lang.org) с крошечными примерами перед тем, как браться за реальный код - иногда 5-строчный тест-кейс выявляет, что на самом деле идёт не так, быстрее, чем дебаг основного проекта.

Главное, что у меня наконец дошло - это понимание того, что async функции на самом деле не возвращают то, что ты видишь в сигнатуре. Они возвращают Future, который *в конце концов* произведёт это значение, а borrow checker нужно знать, что каждая ссылка, которую ты держишь, останется валидной, когда этот Future действительно выполнится. Я неделями боролась с ошибками lifetime'а в вложенных async вызовах, пока не начала выписывать, от каких данных зависит каждая точка `.await`, почти как будто я вслух рассказывала о жизненном цикле Future. Попробуй туториал Tokio и особенно их раздел про spawning задач - конкретные примеры того, что перемещается в spawned задачу и что заимствуется через await points, помогают намного больше, чем абстрактные объяснения. Async глава в Rust book тоже стала лучше, так что не пропускай её даже если ты уже читала части основной книги.

Ваш ответ

Войдите, чтобы ответить.