< 返回版块

ianfor 发表于 2024-10-16 16:55

use std::rc::Rc;
use tokio::task::yield_now;

fn spawner() {
    tokio::spawn(example());
}

async fn example() {
    let non_send = Rc::new(1);
    println!("{}", non_send);
    yield_now().await;    
}

报错信息 future cannot be sent between threads safely within impl Future<Output = ()>, the trait Send is not implemented for Rc<i32>, which is required by impl Future<Output = ()>: SendrustcClick for full compiler diagnostic lib.rs(15, 17): future is not Send as this value is used across an await spawn.rs(167, 21): required by a bound in tokio::spawn

想问下为什么non_send 在await之后已经未使用了,为什么还会捕获到future里,而不是有类似(NLL)类型效果判定其生命周期就到await之前那,不会捕获到futrue里

评论区

写评论
SleepyBoy 2024-10-17 10:45

查了下NLL相关的RFC文章,这个

在日常用语中,Rust 的生命周期一词可以两种不同但相似的方式使用:

  1. 引用的生命周期:使用该引用的时间范围。
  2. 值的生命周期:该值被释放之前(或者换句话说,在值的析构函数运行之前)的时间范围。

第二个时间范围非常重要,它描述了一个值在多长时间范围内有效。为了区分两者,第二个时间范围称为值的作用域(value scope)

生命周期和作用域是相互关联的,作用域通常相当于某个块,更具体地说,是从let声明延伸到封闭作用域结束的块的后部。Rust 的生命周期比作用域灵活得多,非词法生命周期(NLL)主要就是让引用的生命周期更加灵活的。

你说的这个问题,以前也有人提出过。从之前的经验上讲,要缩短值的作用域,一般就是手动调用drop(x)移出当前大的作用域,或者使用{}限制作用域大小。

也希望以后能支持你说的新特性吧,可以合理可靠地控制值的作用域大小,以减小边际作用影响

zylthinking 2024-10-17 10:05

就可以通过了,我猜是 NoSendHasDrop 有 Drop 之后编译器就要在作用于结尾插个 drop,这样就延长了 unused 的生命,感觉编译器是不是在 borrow check 之前没进行足够的未使用代码删减?

感觉可以到 github 提个 issue。

将 NoSendHasDrop 替换成 MutexGuard 就合理了

作者 ianfor 2024-10-16 19:38

嗯 用{}将non_send 括起来就好了,感觉是有点不合理

--
👇
TinusgragLin: 我简化了一下,试了下这段代码:

#![feature(negative_impls)]
#[test]
fn unnamed() {
    use std::{
        future::Future,
        pin::Pin,
        task::{Context, Poll},
    };

    struct NoSendHasDrop;
    impl Drop for NoSendHasDrop {
        fn drop(&mut self) {}
    }
    impl !Send for NoSendHasDrop {}

    struct SomeFut;
    impl Future for SomeFut {
        type Output = ();
        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
            Poll::Ready(())
        }
    }

    async fn fire() {
        let ununsed = NoSendHasDrop;
        SomeFut.await
    }
    fn check_send<T: Send>(_: T) {}

    check_send(fire());
}

会出现一致的编译错误,如果改成:

    async fn fire() {
        let _ = NoSendHasDrop;
        SomeFut.await
    }

或:

    async fn fire() {
        {        
            let ununsed = NoSendHasDrop;
        }
        SomeFut.await
    }

或去掉 NoSendHasDrop 的 Drop impl 或 !Send impl

就可以通过了,我猜是 NoSendHasDrop 有 Drop 之后编译器就要在作用于结尾插个 drop,这样就延长了 unused 的生命,感觉编译器是不是在 borrow check 之前没进行足够的未使用代码删减?

感觉可以到 github 提个 issue。

TinusgragLin 2024-10-16 18:52

我简化了一下,试了下这段代码:

#![feature(negative_impls)]
#[test]
fn unnamed() {
    use std::{
        future::Future,
        pin::Pin,
        task::{Context, Poll},
    };

    struct NoSendHasDrop;
    impl Drop for NoSendHasDrop {
        fn drop(&mut self) {}
    }
    impl !Send for NoSendHasDrop {}

    struct SomeFut;
    impl Future for SomeFut {
        type Output = ();
        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
            Poll::Ready(())
        }
    }

    async fn fire() {
        let ununsed = NoSendHasDrop;
        SomeFut.await
    }
    fn check_send<T: Send>(_: T) {}

    check_send(fire());
}

会出现一致的编译错误,如果改成:

    async fn fire() {
        let _ = NoSendHasDrop;
        SomeFut.await
    }

或:

    async fn fire() {
        {        
            let ununsed = NoSendHasDrop;
        }
        SomeFut.await
    }

或去掉 NoSendHasDrop 的 Drop impl 或 !Send impl

就可以通过了,我猜是 NoSendHasDrop 有 Drop 之后编译器就要在作用于结尾插个 drop,这样就延长了 unused 的生命,感觉编译器是不是在 borrow check 之前没进行足够的未使用代码删减?

感觉可以到 github 提个 issue。

dcsmf 2024-10-16 18:05

因为编译器会把整个 async 函数的上下文打包成一个状态机,会捕获所有范围内局部的变量 就算不用,也算做 Future 的一部分,要求你实现 Send

1 共 5 条评论, 1 页