最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

rust - Using enumerate with async blocks - Stack Overflow

programmeradmin0浏览0评论

I have the following Rust code

use std::error::Error;

#[derive(Debug)]
enum MyEnum {
    First,
    Second,
}

fn do_something(index: usize, m: &MyEnum) {
    eprintln!("do_something: {} {:?}", index, m);
}

async fn test() -> Result<(), Box<dyn Error>> {
    let myvec = vec![MyEnum::First, MyEnum::Second];
    let c = String::from("cap");

    let futures = myvec.iter().enumerate().map(|(index, el)| async {
        eprintln!("Element: {}", &c);
        do_something(index, el);
    });

    futures::future::join_all(futures).await;
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    test().await?;
    Ok(())
}

Compiler complains that:

error[E0373]: async block may outlive the current function, but it borrows `index`, which is owned by the current function
  --> src/main.rs:17:62
   |
17 |     let futures = myvec.iter().enumerate().map(|(index, el)| async {
   |                                                              ^^^^^ may outlive borrowed value `index`
18 |         eprintln!("Element: {}", &c);
19 |         do_something(index, el);
   |                      ----- `index` is borrowed here
   |

Why it's not complaining about the el variable?

And how to fix this code? I would like to keep using the iterator and not for loop. I can change other parts of code, e.g. do_something() can have different prototype.

I've put the c variable here to forbid the easy solution with async move {...}. The string c should not be moved.

Without enumerate() (and without index) it works. I would expect that index (which has usize type) is easily copy-able.

I have the following Rust code

use std::error::Error;

#[derive(Debug)]
enum MyEnum {
    First,
    Second,
}

fn do_something(index: usize, m: &MyEnum) {
    eprintln!("do_something: {} {:?}", index, m);
}

async fn test() -> Result<(), Box<dyn Error>> {
    let myvec = vec![MyEnum::First, MyEnum::Second];
    let c = String::from("cap");

    let futures = myvec.iter().enumerate().map(|(index, el)| async {
        eprintln!("Element: {}", &c);
        do_something(index, el);
    });

    futures::future::join_all(futures).await;
    Ok(())
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    test().await?;
    Ok(())
}

Compiler complains that:

error[E0373]: async block may outlive the current function, but it borrows `index`, which is owned by the current function
  --> src/main.rs:17:62
   |
17 |     let futures = myvec.iter().enumerate().map(|(index, el)| async {
   |                                                              ^^^^^ may outlive borrowed value `index`
18 |         eprintln!("Element: {}", &c);
19 |         do_something(index, el);
   |                      ----- `index` is borrowed here
   |

Why it's not complaining about the el variable?

And how to fix this code? I would like to keep using the iterator and not for loop. I can change other parts of code, e.g. do_something() can have different prototype.

I've put the c variable here to forbid the easy solution with async move {...}. The string c should not be moved.

Without enumerate() (and without index) it works. I would expect that index (which has usize type) is easily copy-able.

Share Improve this question edited Feb 8 at 0:46 cafce25 27.4k5 gold badges43 silver badges55 bronze badges asked Feb 8 at 0:14 MartinhMartinh 133 bronze badges New contributor Martinh is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 5
  • 1 "the easy solution with async move" is the correct solution, why do you want to avoid it? You can always let c = &c; before the async block to avoid moving c. – cafce25 Commented Feb 8 at 0:30
  • I didn't know about the let c = &c; trick. This really solves it. But why is compiler not complaining about the el variable? And why it works without enumerate()? – Martinh Commented Feb 8 at 0:39
  • because it borrows from myvec which is not owned by the closure. – cafce25 Commented Feb 8 at 0:40
  • index as well as myvec are both defined inside the test() function. Where is the difference? Or is the current function actually the closure inside the map? (thank you @cafce25 very much for your answers) – Martinh Commented Feb 8 at 0:55
  • Yes, exactly index is defined in and owned by the closure, the innermost function, not in test. – cafce25 Commented Feb 8 at 0:57
Add a comment  | 

1 Answer 1

Reset to default 2

I would expect that index (which has usize type) is easily copy-able.

That's exactly the problem, because usize is copy, for it to move the compiler only needs to capture a shared reference it can just copy the bytes when it needs a value. It'll use the weakest kind of capture it can, first &, then &mut and only if these don't work it'll move.

The problem is that index is a local to the closure, which is gone when it returns the future.

el doesn't have that same problem because it is a reference to the outer myvec which is owned by test, which does live long enough, hence any references to it also can live long enough.

To fix it you simply apply the "easy solution" you found yourself, any captures that you do not want to move you can simply shadow1 with the desired binding kind (& or &mut):

async fn test() -> Result<(), Box<dyn Error>> {
    let myvec = vec![MyEnum::First, MyEnum::Second];
    let c = String::from("cap");

    let futures = myvec.iter().enumerate().map({
        let c = &c;
        move |(index, el)| async move {
            eprintln!("Element: {c}");
            do_something(index, el);
        }
    });

    futures::future::join_all(futures).await;

    eprintln!("c is still available: {c}");
    Ok(())
}

1 of course you don't have to shadow it, but shadowing allows you to not worry about a new name and is quite idiomatic here.

发布评论

评论列表(0)

  1. 暂无评论