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

Regression from stable to nightly: nested impl trait is not allowed #57979

Open
KodrAus opened this Issue Jan 29, 2019 · 13 comments

Comments

Projects
None yet
7 participants
@KodrAus
Copy link

KodrAus commented Jan 29, 2019

I have some code that looks a bit like this:

use std::borrow::Borrow;

pub struct Data<TBody>(TBody);

pub fn collect(_: impl IntoIterator<Item = impl Borrow<Data<impl AsRef<[u8]>>>>) {
    unimplemented!()
}

On 1.32.0 (and the current beta 1.33.0-beta.4 2019-01-24 635817b9db20ecdcd036) this compiles successfully.

On the current nightly 1.34.0-nightly 2019-01-28 d8a0dd7ae88023bd09fa this fails with:

error[E0666]: nested `impl Trait` is not allowed
 --> src/main.rs:5:61
  |
5 | pub fn collect(_: impl IntoIterator<Item = impl Borrow<Data<impl AsRef<[u8]>>>>) {
  |                                            -----------------^^^^^^^^^^^^^^^^--
  |                                            |                |
  |                                            |                nested `impl Trait` here
  |                                            outer `impl Trait`

A workaround is just to use generics. Did we mean to change the rules around nested impl trait here?

@jonas-schievink

This comment has been minimized.

Copy link
Member

jonas-schievink commented Jan 29, 2019

Good: rustc 1.33.0-nightly (19f8958 2019-01-23)
Bad: rustc 1.33.0-nightly (01f8e25 2019-01-24)

Commits in that range

@jonas-schievink

This comment has been minimized.

Copy link
Member

jonas-schievink commented Jan 29, 2019

Likely cuplrit: #57730 (this comment seems relevant)

cc @Zoxc

Zoxc added a commit to Zoxc/rust that referenced this issue Jan 29, 2019

Zoxc added a commit to Zoxc/rust that referenced this issue Jan 30, 2019

@Zoxc Zoxc referenced this issue Jan 30, 2019

Merged

Fix #57730 #57981

@Centril Centril added the T-lang label Jan 30, 2019

@Centril

This comment has been minimized.

Copy link
Contributor

Centril commented Jan 30, 2019

Also nominated for T-Lang's Thursday meeting; from what I can tell, this shouldn't have been allowed in the first place and appears to have been allowed due to a bug in the nested-impl-trait checking logic.

IOW, the snippet above could be interpreted as:

pub fn collect(_: impl IntoIterator<Item = impl for<T: AsRef<[u8]>> Borrow<Data<T>>>) {
    unimplemented!()
}
@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Jan 31, 2019

I do not know that we have to discuss in the @rust-lang/lang meeting, but I'm not opposed. I agree with @Centril that this should not have been accepted.

In particular, we decided to forbid nested impl trait of this kind:

fn foo() -> impl Foo<impl Bar> { .. }

Basically there are two hidden variables here, let's call them X and Y, and we must prove that:

X: Foo<Y>
Y: Bar

However, the user is only giving us the return type X. It's true that in some cases, we can infer Y from X, because X only implements Foo for one type (and indeed the compiler does inference of this kind), but I do recall that we intended to forbid this -- in part because such inference is fragile, and there is not necessarily a way for the user to be more explicit should the inference fail (so you could get stuck with no way to port your code forward if, for example, more impls are added to an existing type).

The same seems to apply in this situation. Here there are three impl traits, so we have

X: IntoIterator<Item = Y>
Y: Borrow<Data<Z>>
Z: AsRef<[u8]>

Now, based on X, we can determine Y, but knowing Y does not help us to know Z.

So I would argue that this is a bug fix and we should tag the issue as E-needstest (I'd prefer not to issue a warning period unless we know that this affects a broader swath of code).

@Centril Centril removed the I-nominated label Jan 31, 2019

@KodrAus

This comment has been minimized.

Copy link
Author

KodrAus commented Jan 31, 2019

This issue is more subtle than the compiler error message leads us to believe:

nested `impl Trait` is not allowed
$ rustc --explain E0666
error: no extended information for E0666

and is a bit perplexing without the explanation @nikomatsakis gave, given it can be worked around by mechanically replacing impl Trait with generics. It wasn't obvious to me that this was footgun prevention (but to be fair if it had worked as-intended right away it would probably be a bit different).

I'd prefer not to issue a warning period unless we know that this affects a broader swath of code

Hmm, I'm not sure how we could reasonably determine that given the dark matter closed-source floating around? It certainly affected our product code. Is there a reason not to issue a warning period?

@joshtriplett

This comment has been minimized.

Copy link
Member

joshtriplett commented Feb 1, 2019

Why can we not support "I return something implementing Iterator where the items implement Foo"? That seems useful.

@Centril

This comment has been minimized.

Copy link
Contributor

Centril commented Feb 1, 2019

@joshtriplett We can and we do (playground):

fn foo() -> impl Iterator<Item = impl core::fmt::Debug> { 0..10 }

This issue is about something else. The distinction is between impl Alpha<impl Gamma> and impl Alpha<Beta = impl Gamma>. The former was never intended to be allowed (for reasons mentioned by @nikomatsakis and myself) and the latter was, is, and will be allowed.

@cramertj

This comment has been minimized.

Copy link
Member

cramertj commented Feb 1, 2019

+1 to "this was a bug"

nikomatsakis added a commit to Zoxc/rust that referenced this issue Feb 1, 2019

nikomatsakis added a commit to Zoxc/rust that referenced this issue Feb 1, 2019

@KodrAus

This comment has been minimized.

Copy link
Author

KodrAus commented Feb 3, 2019

This isn't just a bug, it's also stabilized behaviour. I'm all for patching this up, but am not so keen on an immediate hard error on the next stable release.

@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Feb 14, 2019

is this going to be addressed (in some manner) by PR #57981? Is the manner in which that PR addresses this also "acceptable"?

Its not 100% clear whether a warning-cycle makes sense in this case, versus jumping to an immediate hard error. I suppose it depends on what semantics is being assigned when this code is accepted in the first place.

@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Feb 14, 2019

triage: P-high, assigning to self.

@pnkfelix pnkfelix self-assigned this Feb 14, 2019

@pnkfelix pnkfelix added the P-high label Feb 14, 2019

@Centril Centril removed the T-lang label Feb 14, 2019

TheBiggerGuy added a commit to TheBiggerGuy/rust that referenced this issue Feb 17, 2019

@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Feb 19, 2019

@nikomatsakis wrote above:

I do recall that we intended to forbid this -- in part because such inference is fragile, and there is not necessarily a way for the user to be more explicit should the inference fail (so you could get stuck with no way to port your code forward if, for example, more impls are added to an existing type)

In case people want further illustration of this point, here is an example of the above scenario (play):

#![allow(dead_code)]

pub trait Quux { type Assoc; }
pub trait Foo<T> { }
pub trait Bar { }

struct S1;
struct S2;
struct S3;
struct S4;

impl Quux for S1 { type Assoc = S2; }
impl Foo<S3> for S2 { }
impl Bar for S3 { }
impl Bar for S4 { }

// The presence/absence of this line dictates whether
// we get inference failures or not
impl Foo<S4> for S2 { }

pub fn foobar(_: impl Quux<Assoc=impl Foo<impl Bar>>) {

}

fn main() {
    foobar(S1);
}

(and, as far as I know, there's no way to add type annotations to the call site within main to disambiguate whether we want the impl Foo<S3> or impl Foo<S4> for the associated S2 implied by impl Quux for S1.)

@pnkfelix

This comment has been minimized.

Copy link
Member

pnkfelix commented Feb 19, 2019

Having said that, it seems like we at least attempt to do inference when encountering the code in question (at least back when we managed to sneak it pass the compiler's initial static checks).

This leads me to wonder if a warning cycle is at least possible via a relatively small delta to the rustc source code.

@pnkfelix pnkfelix referenced a pull request that will close this issue Feb 20, 2019

Open

Warning period for detecting nested impl trait #58608

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment