Rust: Allow generic associate types in trait paths

Created on 22 Dec 2019  路  12Comments  路  Source: rust-lang/rust

The following code should be accepted:

#![feature(generic_associated_types)]

trait X {
    type Y<'a>;
}

fn f(x: Box<dyn X<Y<'a>=&'a ()>>) {}
// or perhaps:
// fn f(x: Box<dyn for<'a> X<Y<'a>=&'a ()>>) {}

fn g<T: X<Y<'a>=&'a ()>>() {}
A-parser C-feature-accepted F-generic_associated_types T-compiler requires-nightly

Most helpful comment

Some thoughts:

  • Trait<A<'a>: Clone> should be supported as well (https://github.com/rust-lang/rust/issues/52662).
  • The syntax should be future compatible with type parameters in associated types:
    ```rust
    trait Trait {
    type A;
    }
Trait<A<T> = u8>
```

  • What is inside the angle brackets really, arguments or parameters - A<???>?
    (Arguments use already defined names and parameters introduce names.)
    Does Trait<A<u8> = u8> or Trait<A<[u8; 10]> = u8> make sense, or ??? must be a single type parameter?

The fn f(x: Box<dyn for<'a> X<Y<'a>=&'a ()>>) {} example in the top comment implies that ??? is an argument after all (use, not definition).
I assume it's equivalent to an (unimplemented) type equality predicate in a where clause (for<'a> X::Y<'a> == &'a).
Then ??? can indeed be an arbitrary type (or lifetime) like u8 or [u8; 10].

From that we see that the associated type constraint syntax is really ambiguous with generic type parameters, but also get a clue on how to approach it during parsing.
When parsing a generic argument (inside Trait<...>) we just parse a type and then look at the next token:

  • If the next token is not =, then it's a type argument.
  • If the next token is = (Trait<TYPE = ...>), then

    • if the parsed TYPE is a single-segment path, possibly with generic arguments (Trait<Ident<Args> = ...>), then we store it into AST as an associated type constraint,

    • otherwise we are reporting a syntactic error.

for<...> will be required to introduce names, but our syntax already accepts for<...> in that position

for<'a> Trait<A<'a> = &'a u8>

, so we don't have to introduce any new syntax for the name introducer like

Trait<for<'a> A<'a> = &'a u8>

(The only question is whether these two forms can be considered equivalent, it seems like yes.)

All 12 comments

I'll take at least the ast part of it for now

Am I correct in that we don't accept any type bounds on the left hand side and suggest moving them into a where clause?

Do you mean is X<Y<T: SomeTrait>=()> accepted? I would guess not because the bounds on T are already determined by the trait.

We allow repeating bounds in impl blocks though, i.e. this is permitted currently

#![feature(generic_associated_types)]
#![allow(incomplete_features)]

trait X<'b> {
    type Y<'a: 'b>;
}

struct _S {}

impl<'a> X<'a> for _S {
    type Y<'b: 'a> = ();
}

Edit: thinking about it, we probably wouldn't be able to do it even if we want to because we can't parse where clauses anyway

Is this still being handled? I am willing to look into it myself, I am currently stuck on needing this feature (and admittedly have a lot of time on my hands with recent events).

@SuperTails You are welcome to take it, Im busy with chalk work atm

https://github.com/rust-lang/rust/issues/44265#issuecomment-655759017

I found another case where this is needed: I have a trait definition which looks like this:

pub trait ViewFn<S> {
    type Output<'ast>: Sized;
    fn view<'ast>(&self, node: &'ast S) -> Self::Output<'ast>;
    // ...
}

If I want to have a trait bound on ViewFn with Output constrained to be a reference to a object which implements a specific type, I would require this, e.g. the following currently doesn't work:

/*[...]*/<T, O, F>/*[...]*/
where
    O: // omitted [...]
    F: ViewFn<T, for<'x> Output<'x> = &'x O>,

Is this a duplicate? I copied this from the RFC. Playground.

trait StreamingIterator {
    type Item<'a>;
    fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
}

fn foo<T: for<'a> StreamingIterator<Item<'a> = &'a [i32]>>(iter: T) { unimplemented!() }

@vandenheuvel I think that is the same issue, yes.

Some thoughts:

  • Trait<A<'a>: Clone> should be supported as well (https://github.com/rust-lang/rust/issues/52662).
  • The syntax should be future compatible with type parameters in associated types:
    ```rust
    trait Trait {
    type A;
    }
Trait<A<T> = u8>
```

  • What is inside the angle brackets really, arguments or parameters - A<???>?
    (Arguments use already defined names and parameters introduce names.)
    Does Trait<A<u8> = u8> or Trait<A<[u8; 10]> = u8> make sense, or ??? must be a single type parameter?

The fn f(x: Box<dyn for<'a> X<Y<'a>=&'a ()>>) {} example in the top comment implies that ??? is an argument after all (use, not definition).
I assume it's equivalent to an (unimplemented) type equality predicate in a where clause (for<'a> X::Y<'a> == &'a).
Then ??? can indeed be an arbitrary type (or lifetime) like u8 or [u8; 10].

From that we see that the associated type constraint syntax is really ambiguous with generic type parameters, but also get a clue on how to approach it during parsing.
When parsing a generic argument (inside Trait<...>) we just parse a type and then look at the next token:

  • If the next token is not =, then it's a type argument.
  • If the next token is = (Trait<TYPE = ...>), then

    • if the parsed TYPE is a single-segment path, possibly with generic arguments (Trait<Ident<Args> = ...>), then we store it into AST as an associated type constraint,

    • otherwise we are reporting a syntactic error.

for<...> will be required to introduce names, but our syntax already accepts for<...> in that position

for<'a> Trait<A<'a> = &'a u8>

, so we don't have to introduce any new syntax for the name introducer like

Trait<for<'a> A<'a> = &'a u8>

(The only question is whether these two forms can be considered equivalent, it seems like yes.)

personally I would prefer

Trait<for<'a> A<'a> = &'a u8>

(about the alternative:) I just wonder what would happen if it may clash, like:

for<'a> X<'a>: Trait<A<'a> = &'a u8>

vs.

for<'x> X<'x>: Trait<for<'a> A<'a> = &'a u8>

I think we should allow both syntax '(es), which should be considered equivalent in simple cases, but may be able to express more complex relations cleanly.

The Trait<for<'a> A<'a> = &'a u8> form introduces a new syntax and requires a more complex disambiguation because types can start with for (Trait<for<'a> A<'a>> is a type argument, Trait<for<'a> A<'a> = &'a u8> is an assoc type constraint), so I'd prefer to avoid it for now.
It can be added later if it becomes necessary.

Was this page helpful?
0 / 5 - 0 ratings