Skip to content
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

Parse and accept type equality constraints in where clauses #20041

Open
jroesch opened this issue Dec 19, 2014 · 45 comments
Open

Parse and accept type equality constraints in where clauses #20041

jroesch opened this issue Dec 19, 2014 · 45 comments
Labels
A-lazy-normalization Area: Lazy normalization (tracking issue: #60471) A-type-system Area: Type system C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC S-tracking-design-concerns Status: There are blocking design concerns. S-tracking-unimplemented Status: The feature has not been implemented. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@jroesch
Copy link
Member

jroesch commented Dec 19, 2014

Implement the missing type equality constraint specified in RFC 135.

Examples

fn sum<I: Iterator>(_: I) -> i32
where
    I::Item == i32 // or `I::Item = i32`
{
    // ...    
}
jroesch added a commit to jroesch/rust that referenced this issue Dec 23, 2014
Adds support for all variants of ast::WherePredicate in clean/mod.rs. Fixes rust-lang#20048, but will need modification when EqualityPredicates are fully implemented in rust-lang#20041.
bors added a commit that referenced this issue Dec 25, 2014
…hton

Add support for all variants of ast::WherePredicate in clean/mod.rs. Fixes #20048, but will need modification when EqualityPredicates are fully implemented in #20041.
@kennytm

This comment has been minimized.

@japaric

This comment has been minimized.

@kennytm

This comment has been minimized.

@jroesch

This comment has been minimized.

@steveklabnik

This comment has been minimized.

@sgrif
Copy link
Contributor

sgrif commented Aug 30, 2015

I don't think associated type bindings are quite the same as this, since they aren't taken into account for determining overlapping impls (which appears to have been intentional) http://is.gd/em2JNT

@cramertj
Copy link
Member

cramertj commented Jan 27, 2017

@sgrif Doesn't that make this a kind of duplicate of rust-lang/rfcs#1672?

Edit: obviously with semantic differences, but I believe they allow for expressing the same types of bounds.

@tupshin
Copy link

tupshin commented Feb 4, 2017

I just ran into this limitation pretty hard while trying to do type level operations on HLists. Any work planned?

The only possible workaround I see is this bitrotted brilliant abomination
https://github.com/freebroccolo/unify.rs

@brson brson mentioned this issue May 20, 2017
7 tasks
@Mark-Simulacrum Mark-Simulacrum added the C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC label Jul 22, 2017
@dhardy
Copy link
Contributor

dhardy commented Aug 5, 2017

Show question (answered below) Taking this idea further (`where` on associated types), I want to do the following:
/// Helper trait for creating implementations of `RangeImpl`.
pub trait SampleRange: PartialOrd {
    type T: RangeImpl where T::X == Self;
}

/// Helper trait handling actual range sampling.
pub trait RangeImpl {
    /// The type sampled by this implementation.
    type X: PartialOrd;
    
    /// Construct self.
    /// 
    /// This should not be called directly. `Range::new` asserts that
    /// `low < high` before calling this.
    fn new(low: Self::X, high: Self::X) -> Self;
    
    /// Sample a value.
    fn sample<R: Rng+?Sized>(&self, rng: &mut R) -> Self::X;
}

The latter trait on its own does all the work. The first one is just there to make the following work without explicitly specifying the type implementing RangeImpl.

#[derive(Clone, Copy, Debug)]
pub struct Range<T: RangeImpl> {
    inner: T,
}

pub fn range<X: SampleRange, R: Rng+?Sized>(low: X, high: X, rng: &mut R) -> X {
    assert!(low < high, "distributions::range called with low >= high");
    Range { inner: X::T::new(low, high) }.sample(rng)
}

@sgrif
Copy link
Contributor

sgrif commented Aug 5, 2017

@dhardy What you want is just type T: RangeImpl<X = Self>;

@dhardy
Copy link
Contributor

dhardy commented Aug 5, 2017

@sgrif that actually works, thanks!

dhardy added a commit to dhardy/rand that referenced this issue Aug 5, 2017
@golddranks
Copy link
Contributor

I would very much like to see this happen. Writing highly generic code is a pain, if you can't rename associated types inside traits, and this helps to do it, by asserting that a simple associated type is the same type as some monstrous thing: type TableQuery and where Self::TableQuery == <<Self::DbTable as HasTable>::Table as AsQuery>::Query.

@pnkfelix
Copy link
Member

pnkfelix commented Mar 4, 2022

@rustbot label +T-lang +I-lang-nominated +S-tracking-design-concerns +S-tracking-unimplemented

(@jackh726 says that niko has had some concerns regarding arbitrary where clause equality constraints.)

@rustbot rustbot added I-lang-nominated Nominated for discussion during a lang team meeting. S-tracking-design-concerns Status: There are blocking design concerns. S-tracking-unimplemented Status: The feature has not been implemented. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Mar 4, 2022
@nikomatsakis
Copy link
Contributor

We discussed this in the lang-team meeting today. I am indeed concerned about general T = U constraints, for the reasons expressed in the minutes, but we were thinking that perhaps we could accept a more limited form of where T::Item = U or <T as Iterator>::Item = U, which would be equivalent to today's T: Iterator<Item = U>... thoughts?

@jackh726
Copy link
Member

jackh726 commented Mar 9, 2022

Accepting where T::Item = U I think is pretty easy and should only require changes at the ast and hir levels. Once astconv is called, we store a ProjectionPredicate for that.

@nikomatsakis nikomatsakis removed the I-lang-nominated Nominated for discussion during a lang team meeting. label Mar 15, 2022
@LegionMammal978
Copy link
Contributor

LegionMammal978 commented Apr 27, 2022

When was it decided to use single = instead of double == for these constraints? I'd strongly prefer the latter. With where T: Iterator<Item = U>, I see that T is an Iterator, with its Item type assigned to U, not unlike the syntax for default type parameters. And with where <T as Iterator>::Item == U, I see that we are imposing a boolean predicate on the two types. But with where <T as Iterator>::Item = U, we seem to assign a type to the projection, which seems as odd to me as assigning f() = 123.

To me, it ultimately comes down to the symmetry of the operator. <T as Iterator>::Item == U is equivalent to U == <T as Iterator>::Item, and this is clarified by the equality syntax. Conversely, T: Iterator<Item = U> is not equivalent to T: Iterator<U = Item>, and this is clarified by the assignment syntax. This indication of symmetry would be especially useful in bounds such as <T as Foo>::Assoc == <T as Bar>::Assoc, where currently we must write an unwieldy T: Foo<Assoc = <T as Bar>::Assoc>. So why should we use a single = in these top-level bounds?

@ireina7
Copy link

ireina7 commented Nov 26, 2022

A working demo of simple type equality bounds in stable Rust:

Rust Playground

use std::marker::PhantomData;

trait TyEq {}

impl<T> TyEq for (T, T) {}

fn require_ty_eq<A, B>() where (A, B): TyEq {}

struct Z;
struct S<N>(PhantomData<N>);

fn compiles() {
    require_ty_eq::<Z, Z>();
    require_ty_eq::<S<Z>, S<Z>>();
}

fn does_not_compile() {
    require_ty_eq::<S<Z>, S<S<Z>>>();
}

@scottjmaddox But this constraint only works on Sized types? any idea to relax this?

@Jules-Bertholet
Copy link
Contributor

Jules-Bertholet commented Nov 26, 2022

But this constraint only works on Sized types? any idea to relax this?

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=40979a4328a4157253dbf78f501e213c

@oskgo oskgo mentioned this issue Jan 6, 2023
JohnTitor pushed a commit to JohnTitor/rust that referenced this issue Jan 11, 2023
remove E0280

After looking at rust-lang#61137 I tried my hand at E0280. I'm unable to find a reasonable example that emits the error. There are a couple of old examples that compile with the current compiler ([rust-lang#26217](rust-lang#26217), [rust-lang#42114](rust-lang#42114), [rust-lang#27113](rust-lang#27113)) and there is a [bug with chalk](https://github.com/rust-lang/rust/blob/b7cdb635c4b973572307ad288466fba64533369c/src/test/ui/chalkify/bugs/async.rs) that makes it emit the error, with a couple more chalk bugs on zulip.

It seems like the error is supposed to be emitted from unfulfilled where bounds, of which two are related to borrow checking (error in where T: 'a or where 'a: 'b) and thus tend to emit errors like "lifetime may not live long enough" from borrow checking instead. The final case is with type equality constraints (where <T as Iterator>::Item == u32), which is unimplemented ([rust-lang#20041](rust-lang#20041)). That such different problems are supposed to have the same error code also seems strange to me.

Since the error seems to only be emitted when using chalk I propose to remove it and replace it with an ICE instead. A crater run might be warranted.

Pinging `@jackh726` due to removal of chalk test that now ICEs.
@dhardy
Copy link
Contributor

dhardy commented Jan 13, 2023

The TyEq "working demo" doesn't really work though:

#![allow(unused)]

trait TyEq {}

impl<T: ?Sized> TyEq for (*const T, *const T) {}

fn convert<In, Out>(x: In) -> Out
where
    (*const In, *const Out): TyEq,
{
    // error[E0308]: mismatched types
    x
}

Most of the time, working around the lack of type equality constraint isn't too hard, but in more complicated cases it's still a problem:

struct Something<F: Foo>(F);
impl<F: Foo> Something {
    fn new<B>(b: B) -> Self
    where
        Wrap<B> == F,
    {
        Something(Wrap(b))
    }
}

The only recourse here seems to be to pass in Wrap<B> instead of B.

estebank added a commit to estebank/rust that referenced this issue Feb 7, 2024
```
error: equality constraints are not yet supported in `where` clauses
  --> $DIR/equality-bound.rs:50:9
   |
LL |         IntoIterator::Item = A,
   |         ^^^^^^^^^^^^^^^^^^^^^^ not supported
   |
   = note: see issue rust-lang#20041 <rust-lang#20041> for more information
help: if `IntoIterator::Item` is an associated type you're trying to set, use the associated type binding syntax
   |
LL ~     fn from_iter<T: IntoIterator<Item = A>>(_: T) -> Self
LL |     where
LL ~
   |

error: equality constraints are not yet supported in `where` clauses
  --> $DIR/equality-bound.rs:63:9
   |
LL |         T::Item = A,
   |         ^^^^^^^^^^^ not supported
   |
   = note: see issue rust-lang#20041 <rust-lang#20041> for more information
help: if `IntoIterator::Item` is an associated type you're trying to set, use the associated type binding syntax
   |
LL ~     fn from_iter<T: IntoIterator<Item = A>>(_: T) -> Self
LL |     where
LL ~
   |
```

Fix rust-lang#68982.
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Feb 13, 2024
Provide more suggestions on invalid equality where bounds

```
error: equality constraints are not yet supported in `where` clauses
  --> $DIR/equality-bound.rs:50:9
   |
LL |         IntoIterator::Item = A
   |         ^^^^^^^^^^^^^^^^^^^^^^ not supported
   |
   = note: see issue rust-lang#20041 <rust-lang#20041> for more information
help: if `IntoIterator::Item` is an associated type you're trying to set, use the associated type binding syntax
   |
LL ~     fn from_iter<T: IntoIterator<Item = A>>(_: T) -> Self
LL ~
   |

error: equality constraints are not yet supported in `where` clauses
  --> $DIR/equality-bound.rs:63:9
   |
LL |         T::Item = A
   |         ^^^^^^^^^^^ not supported
   |
   = note: see issue rust-lang#20041 <rust-lang#20041> for more information
help: if `IntoIterator::Item` is an associated type you're trying to set, use the associated type binding syntax
   |
LL ~     fn from_iter<T: IntoIterator<Item = A>>(_: T) -> Self
LL ~
   |
```

Fix rust-lang#68982.
rust-timer added a commit to rust-lang-ci/rust that referenced this issue Feb 13, 2024
Rollup merge of rust-lang#120751 - estebank:issue-68982, r=nnethercote

Provide more suggestions on invalid equality where bounds

```
error: equality constraints are not yet supported in `where` clauses
  --> $DIR/equality-bound.rs:50:9
   |
LL |         IntoIterator::Item = A
   |         ^^^^^^^^^^^^^^^^^^^^^^ not supported
   |
   = note: see issue rust-lang#20041 <rust-lang#20041> for more information
help: if `IntoIterator::Item` is an associated type you're trying to set, use the associated type binding syntax
   |
LL ~     fn from_iter<T: IntoIterator<Item = A>>(_: T) -> Self
LL ~
   |

error: equality constraints are not yet supported in `where` clauses
  --> $DIR/equality-bound.rs:63:9
   |
LL |         T::Item = A
   |         ^^^^^^^^^^^ not supported
   |
   = note: see issue rust-lang#20041 <rust-lang#20041> for more information
help: if `IntoIterator::Item` is an associated type you're trying to set, use the associated type binding syntax
   |
LL ~     fn from_iter<T: IntoIterator<Item = A>>(_: T) -> Self
LL ~
   |
```

Fix rust-lang#68982.
@fmease fmease added T-types Relevant to the types team, which will review and decide on the PR/issue. and removed T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Feb 21, 2024
@Windabove
Copy link

Windabove commented May 13, 2024

A simple workaround

trait TyEq<T> {
    fn rw(self) -> T;
    fn rwi(x: T) -> Self;
}

impl<T> TyEq<T> for T {
    fn rw(self) -> T {
        self
    }
    fn rwi(x: T) -> Self {
        x
    }
}

fn f<T, U>(x: T) -> U where T: TyEq<U> { // ... where T == U
    x.rw()
}

fn g<T, U>(x: T) -> U where U: TyEq<T> { // ... where U == T
    U::rwi(x)
}

@lierdakil
Copy link

Most of the time, working around the lack of type equality constraint isn't too hard, but in more complicated cases it's still a problem

Actually, isn't as much of a problem, at least in the example posed. Here's a full example:

trait TyEq
where
    Self: From<Self::Type> + Into<Self::Type>,
    Self::Type: From<Self> + Into<Self>,
{
    type Type;
}

impl<T> TyEq for T {
    type Type = T;
}

struct Wrap<B>(B);

trait Foo {}
struct Something<F: Foo>(F);
impl<F: Foo> Something<F> {
    fn new<B>(b: B) -> Self
    where
        Wrap<B>: TyEq<Type = F>, // or F: TyEq<Type = Wrap<B>>, or both
    {
        Something(Wrap(b).into())
    }
}

One quirk of this approach is that it's not commutative. So F: TyEq<Type = Wrap<B>> by itself does not imply Wrap<B>: TyEq<Type = F> and vice versa (it has to hold, the compiler just has no idea). Which isn't a bad thing necessarily, and can be exploited for fun and profit (mandatory visible type applications, anyone?).

Another quirk in this particular implementation is that all types have to be Sized. Which is not that terrible of a limitation, but can be slightly annoying to deal with.

Not at all surprising at the end of the day, but not something you'd necessarily expect from a true equality constraint either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lazy-normalization Area: Lazy normalization (tracking issue: #60471) A-type-system Area: Type system C-tracking-issue Category: An issue tracking the progress of sth. like the implementation of an RFC S-tracking-design-concerns Status: There are blocking design concerns. S-tracking-unimplemented Status: The feature has not been implemented. T-lang Relevant to the language team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.