перестань бороться с 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-строчный тест-кейс выявляет, что на самом деле идёт не так, быстрее, чем дебаг основного проекта.