rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
98.7k stars 12.75k forks source link

No syntactic way to specify GATs with where bounds in where clauses #95268

Open joe-hauns opened 2 years ago

joe-hauns commented 2 years ago

Hi!

I was trying to use GATs in one of my projects, and ran into the following issue.

#![feature(generic_associated_types)]

trait ModuleConfig {

    type Type1;

    type Type2<'a>
        where Self::Type1: 'a;

}

trait ModuleConfigSpec<T>
    where Self: for<'a> ModuleConfig<
        Type2<'a> = &'a T
        >,
{ }

fn main() {
    println!("Hello, world!");
}

This code does not compile, because the for<'a> bound in ModuleConfigSpec is not restricted by a where Self::Type1: 'a, which makes sense. But as there is no way to specify this where bound, this means that GATs with where bounds cannot be passed to traits as concrete arguments. I expected it to be possible to specify where bounds locally for every GAT specified. Like in this case I expected to be able to write something like


trait ModuleConfigSpec<T>
    where Self: ModuleConfig<
        for<'a> Type2<'a> = &'a T where Self::Type1: 'a,
        >,
{ }

Interestingly the code complies if we replace trait ModuleConfigSpec by

fn takes_spec_module_config<M, T>() 
    where M: for<'a> ModuleConfig<
        Type2<'a> = &'a T
    >
{  }

which specifies the very same trait bounds.

Meta

rustc --version --verbose:

rustc 1.61.0-nightly (5f3700105 2022-03-22)
nikomatsakis commented 2 years ago

This is an interesting problem. Not blocking, but it's clear we need a more expressive way to talk about where clauses.

TheButlah commented 2 years ago

Related: https://sabrinajewson.org/blog/the-better-alternative-to-lifetime-gats

CJKay commented 2 years ago

I've also run into this, and as far as I've been able to ascertain there is no workaround short of placing the lifetime bound on B:

#![feature(generic_associated_types)]

use std::slice::Iter;

pub trait A {
    type Y<'a> where Self: 'a;
    type Z<'a>: Iterator<Item = Self::Y<'a>> where Self: 'a;

    fn iter(&self) -> Self::Z<'_>;
}

pub trait B: for<'a> A<Z<'a> = Iter<'a, <Self as A>::Y<'a>>> {}
error[E0311]: the parameter type `Self` may not live long enough
   |
   = help: consider adding an explicit lifetime bound `Self: 'a`...
   = note: ...so that the type `Self` will meet its required lifetime bounds...
note: ...that is required by this bound
  --> <source>:12:24
   |
12 | pub trait B: for<'a> A<Z<'a> = Iter<'a, <Self as A>::Y<'a>>> {}
   |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error
cynecx commented 2 years ago

I've always wanted to write higher-ranked trait bounds the following way:

for<'a> {
    // put multiple bounds here, that refer to the same 'a
}

The code from the og becomes:

trait ModuleConfigSpec<T>
where for<'a> {
    Self: ModuleConfig<Type2<'a> = &'a T>,
    <Self as ModuleConfig>::Type1: 'a,
} {}

and @CJKay's becomes:

pub trait B: for<'a> {
    Self: 'a,
    A<Z<'a> = Iter<'a, <Self as A>::Y<'a>>>
} {}

This also helps in async code where you can write something like:

async fn run_with_ctx<Ret, Fn, Fut>(a: Fn) -> Ret
where
    for<'a> {
        Fn: FnOnce(&'a mut i32) -> Fut,
        Fut: Future<Output = Ret> + 'a
    }
{
    a(&mut 0i32).await
}

(Without this, you'd end up with something like: https://github.com/rust-lang/rust/issues/70263#issuecomment-983147868)