Skip to content

TAIT: hidden type cannot be another opaque type from the same scope #96406

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Dirbaio opened this issue Apr 25, 2022 · 1 comment
Open

TAIT: hidden type cannot be another opaque type from the same scope #96406

Dirbaio opened this issue Apr 25, 2022 · 1 comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. F-type_alias_impl_trait `#[feature(type_alias_impl_trait)]` P-low Low priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@Dirbaio
Copy link
Contributor

Dirbaio commented Apr 25, 2022

I wanted to write a function that statically allocates an async fn future: playground

fn task(arg: Foo) -> &'static mut impl Future {
    async fn task_inner(arg: Foo) {
        // code
    }

    type Fut = impl Future;
    static mut FUT: MaybeUninit<Fut> = MaybeUninit::uninit();
    unsafe { FUT.write(task_inner(arg)) }
}

This fails with the following error:

error: opaque type's hidden type cannot be another opaque type from the same scope
  [--> src/lib.rs:8:47
](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021#)   |
8  |   fn task(arg: Foo) -> &'static mut impl Future {
   |  _______________________________________________^
9  | |     async fn task_inner(arg: Foo) {
10 | |         // code
11 | |     }
...  |
15 | |     unsafe { FUT.write(task_inner(arg)) }
16 | | }
   | |_^ one of the two opaque types used here has to be outside its defining scope
   |
note: opaque type whose hidden type is being assigned
  [--> src/lib.rs:8:35
](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021#)   |
8  | fn task(arg: Foo) -> &'static mut impl Future {
   |                                   ^^^^^^^^^^^
note: opaque type being used as hidden type
  [--> src/lib.rs:13:16
](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021#)   |
13 |     type Fut = impl Future;
   |                ^^^^^^^^^^^

One workaround is to move the Fut to the top level, but that's undesirable because it makes the entire file to be the defining scope, which interferes with other code.

A better workaround suggested by @oli-obk is to wrap the inner future in a "dumb" wrapper, so that the types are no longer equal (the RPIT can't be another opaque type itself, but it can still contain one): playground

struct FutureWrapper<F>(F);
impl<F: Future> Future for FutureWrapper<F> {
    type Output = F::Output;
    fn poll(
        self: Pin<&mut Self>,
        cx: &mut std::task::Context<'_>,
    ) -> std::task::Poll<Self::Output> {
        unsafe { Pin::new_unchecked(&mut self.get_unchecked_mut().0) }.poll(cx)
    }
}

fn task(arg: Foo) -> &'static mut impl Future {
    async fn task_inner(arg: Foo) {
        // code
    }

    type Fut = impl Future;
    static mut FUT: MaybeUninit<FutureWrapper<Fut>> = MaybeUninit::uninit();
    unsafe { FUT.write(FutureWrapper(task_inner(arg))) }
}

First, it would be interesting to see if these "nested opaque types" can be allowed in some cases such as this one.

@oli-obk we could possibly allow your case (which has an obvious order of which opaque type should be the hidden type of the other one, or just by equating their hidden types)
The fixes to just allow it are likely not complex in implementation, but require quite some design work to make sure we don't paint ourselves into a corner
We could just put it behind a feature gate tho

Second, it would be helpful if the diagnostic suggested the "wrapper" workaround. It would've saved me a lot of time at least :)

See also: Zulip thread

@oli-obk oli-obk added A-diagnostics Area: Messages for errors, warnings, and lints A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. F-type_alias_impl_trait `#[feature(type_alias_impl_trait)]` labels Apr 26, 2022
@oli-obk
Copy link
Contributor

oli-obk commented Apr 26, 2022

One further way to break the connection would be to use an identity function that just passes the value through but makes it opaque:

#![feature(type_alias_impl_trait)]

use core::future::Future;
use core::mem::MaybeUninit;
use core::pin::Pin;
struct Foo;

fn foo(f: &'static mut impl Future) -> &'static mut impl Future { f }
fn task(arg: Foo) -> &'static mut impl Future {
    async fn task_inner(arg: Foo) {
        // code
    }

    type Fut = impl Future;
    static mut FUT: MaybeUninit<Fut> = MaybeUninit::uninit();
    foo(unsafe { FUT.write(task_inner(arg)) })
}

bors bot added a commit to embassy-rs/embassy that referenced this issue Apr 26, 2022
732: macros: simplify task macro using "TAIT laundering". r=Dirbaio a=Dirbaio

This brings the macro to the state before the nightly update #729 #730, with a much cleaner workaround for the opaque type error, from rust-lang/rust#96406

Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
@oli-obk oli-obk added the P-low Low priority label May 3, 2022
@oli-obk oli-obk moved this from Todo to Can do after stabilization in type alias impl trait stabilization Sep 9, 2022
@Noratrieb Noratrieb added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Apr 5, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-diagnostics Area: Messages for errors, warnings, and lints A-impl-trait Area: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch. F-type_alias_impl_trait `#[feature(type_alias_impl_trait)]` P-low Low priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
Status: Can do after stabilization
Development

No branches or pull requests

3 participants