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

Inconsistent constraint validation on derived trait specializations with associated types #22675

Closed
knz opened this Issue Feb 22, 2015 · 7 comments

Comments

Projects
None yet
3 participants
@knz
Copy link

knz commented Feb 22, 2015

In the code below, rustc should either reject all 3 uses of ApplyT, or none. Currently it only rejects the 3rd one, which is inconsistent:

use std::marker::PhantomFn;
trait TTrans<I> : PhantomFn<(Self,I)> { type O; }
type ApplyT<T, A> = <T as TTrans<A>>::O;

trait Reusable<H> {
  type E;
  fn ap<R, F : Fn(Self::E)->R>(self, f : F) -> ApplyT<H, R>;
}
trait Other<H>    {
  type E;
  fn ap2<R, F : Fn(Self::E)->R>(self, f : F) -> ApplyT<H, R>;
}
impl<H,T> Reusable<H> for T where T : Other<H>    {
  type E = <Self as Other<H> >::E;
  fn ap<R, F : Fn(<Self as Other<H>>::E)->R>(self, f : F) -> ApplyT<H, R> {
    self.ap2(f)
  }
}
fn main() {}

Note that in all 3 uses of ApplyT, the first parameter H is open. Intuition would dictate that rustc should delay checking whether ApplyT<H,R> is valid until H is known. However, rustc inconsistently does so: the first 2 uses of ApplyT cause no error (suggesting the check is indeed delayed), while the last generates an error:

error: the trait `TTrans<R>` is not implemented for the type `H` [E0277]

Of course it would be best if the 3rd case would be delayed as well, but if for any reason that proves difficult, at least for the sake of consistency the behavior should be the same in all 3 cases.

Meta

rustc 1.0.0-dev (2b01a37ec 2015-02-21) (built 2015-02-21)
commit-hash: 2b01a37ec38db9301239f0c0abcf3c695055b0ff
commit-date: 2015-02-21
build-date: 2015-02-21
host: x86_64-unknown-linux-gnu
release: 1.0.0-dev
@knz

This comment has been minimized.

Copy link
Author

knz commented Feb 22, 2015

Example desired use in program:

struct Option_;
impl<T> TTrans<T> for Option_ { type O = Option<T>; }
impl<T> Other<Option_> for Option<T> {
  type E = T;
  fn ap2<R, F : Fn(T)->R>(self, f : F) -> Option<R> {
    match self {
      None => None,
      Some(x) => Some(f(x))
    }
  }
}
fn main() {
  let x : Option<isize> = Some(3);
  let v : Option<isize> = x.ap(|x| { x + 3 });  // must use the generic ap -> ap2 adapter
  match v {
    None => {},
    Some(y) => { println!("{}", y); }
  }
}
@edwardw

This comment has been minimized.

Copy link
Contributor

edwardw commented Feb 22, 2015

I believe this is a non-issue.

In the given example, the first two are traits, rustc merely cares what relationships you want to express. As for whether there's really an implementation TTrans<R> for H, it doesn't matter there. The third one, however, it is a concrete implementation. So if there's no TTrans<R> for H, you can not possibly create an instance of it. It is an error.

@knz

This comment has been minimized.

Copy link
Author

knz commented Feb 22, 2015

Ok but then any suggestion as to how to express that H must implement TTrans<A> for all A in the following:

  impl<H,T> Reusable<H> for T where T : Other<H> , H : TTrans<???> { ...       
                                                             ^^ here
@edwardw

This comment has been minimized.

Copy link
Contributor

edwardw commented Feb 22, 2015

One thing I don't understand about your design is why TTrans needs that phantom parameter in the first place? It probably can be removed all together. The following compiles if that's what you intended:

use std::marker::PhantomFn;
trait TTrans : PhantomFn<(Self)> { type O; }
type ApplyT<T> = <T as TTrans>::O;

trait Reusable<H> {
  type E;
  fn ap<R, F : Fn(Self::E)->R>(self, f : F) -> ApplyT<H>;
}

trait Other<H>    {
  type E;
  fn ap2<R, F : Fn(Self::E)->R>(self, f : F) -> ApplyT<H>;
}

impl<H,T> Reusable<H> for T where T : Other<H> , H : TTrans {
  type E = <Self as Other<H> >::E;
  fn ap<R, F : Fn(<Self as Other<H>>::E)->R>(self, f : F) -> ApplyT<H> {
    self.ap2(f)
  }
}

fn main() {}
@knz

This comment has been minimized.

Copy link
Author

knz commented Feb 22, 2015

Thanks Edward for taking the time to look at this.
You see, your proposed solution won't do because simply using ApplyT<H> loses the type information for R. If you look at my example code in the 1st comment, you will see that the use case calls for the actual return type of ap (and ap2) to select the alternative instance Option<R>, which would not be possible unless ApplyT also gets R as parameter.

I believe that the idea to pass the handle to the trait as parameter (H in the example) would provide this desired genericity, but this crucially requires to either defer the validity of the instantiation of ApplyT to the moment the actual parameters are known, or an appropriate extra constraint on H that it implements TTrans. It seems to me that the current behavior of the type system is overly restrictive.

@edwardw

This comment has been minimized.

Copy link
Contributor

edwardw commented Feb 23, 2015

Well, you could let Option_ carry that extra type parameter:

struct Option_<T> { _marker: PhantomData<T> }

Anyway, I don't think a ticket is the right place to continue this discussion. You may want to try http://internals.rust-lang.org/ instead.

@steveklabnik

This comment has been minimized.

Copy link
Member

steveklabnik commented May 24, 2016

Triage: it seems that the original filing here was more of a feature request than a bug, or rather, it's not clear that it's a bug. PhantomFn does not even exist anymore. Given these factors, and the lack of an update in over a year, I'm going to give this a close. I would suggest opening a thread on internals, as @edwardw mentioned, to continue any discussion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.