-
Notifications
You must be signed in to change notification settings - Fork 13.4k
generic existential types should (at most) warn, not error, if a type parameter is unused #54184
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
Comments
Relevant code: rust/src/librustc_typeck/check/mod.rs Line 5275 in 994cdd9
|
This is a bug. The current behavior with the error deviates from the specification in RFC 2071.
I don't think this should even be a warning. cc @cramertj |
Agreed, this shouldn't warn. |
"Assigning" this to @alexreg to fix per the spec in RFC 2071. |
I'm not quite sure I get this. Why can't we just remove the type parameter? It's not needed in this case, even if we wrap the iterator in a |
@alexreg perhaps not in this case; but there's still a bug as compared to the specification of RFC 2071 so it should be fixed. |
I went too far when I was trying to simplify the example (to cut down the number of lines; whoops). here is a variant example that does need the type parameter for #![feature(existential_type)]
fn main() {
let v = vec![1, 2, 3, 4];
let y = demoit(v.iter().cloned());
for (i, elem) in y.enumerate() {
println!("processing y[{}]: {:?}", i, elem);
}
let v = vec![1, 2, 3, 4];
let y = choose(v.iter().cloned());
for (i, elem) in y.enumerate() {
println!("processing y[{}]: {:?}", i, elem);
}
}
existential type WhyArgNeeded<I>: Iterator<Item = usize>;
fn demoit<I: Iterator<Item = usize>>(x: I) -> WhyArgNeeded<I> {
// If you remove `I` from `WhyArgNeeded`, this will break.
return Wrapped(x);
}
existential type MaybeWrapped<I>: Iterator<Item = usize>;
fn choose<I: Iterator<Item = usize>>(x: I) -> MaybeWrapped<I> {
// `demoit` above shows that variant below would work.
// return Wrapped(x);
return x.collect::<Vec<usize>>().into_iter();
}
/// Trivial wrapper around an iterator.
struct Wrapped<I: Iterator>(I);
impl<I: Iterator> Iterator for Wrapped<I> {
type Item = I::Item;
fn next(&mut self) -> Option<I::Item> { self.0.next() }
} (In particular, here we need the type parameter in |
I love playing devil's advocate, so: Here's an argument for "why a warning might be desirable": we warn today about unused parameters in methods/functions/closures, even when the parameter's presence is forced upon us in order to satisfy external APIs.
Now, do I think these two cases (of functions vs existential types) are really comparable? Meh, not really. But its possible that some developers will find it useful to get feedback like this, and would prefer to explicitly write (via e.g. |
@pnkfelix My view here is mostly that it is way too early to tell if we should warn or not with no substantial usage experience to talk of. I'd like to first stabilize at some point and then we can always add lints if it turned out to be necessary. Being a tad more empirical doesn't hurt =P However -- in the interim, the RFC does not specify that there should be any warnings, so we should first implement what is decided and then implement other things once other decisions are made. |
@pnkfelix Yeah, that makes more sense now. Regarding your "devil's advocate" argument, I'm actually of the opinion the error should remain when the concrete type is not using it, and disappear entirely when it is using it (no warning/lint). |
@alexreg wrote:
Wait, that makes it sound like you are saying that the issue as I originally described is not a bug? I had hoped my example illustrated why this is a bug: requiring someone to remove the type parameter based on the particulars of the concrete type makes this abstraction "leaky." It would be analogous to requiring a function to actually reference all of its arguments in the function body (which is what my devil's advocate argument was implying would be a bad thing...) |
(I interpret @alexreg's response as an implicit vote for why one might want the warning when concrete type is not using it...) |
The abstraction is already "leaky" though, in that it requires a type parameter when the concrete type does. So I either maintain the vote for my proposal, or alternative we make |
Your example there is using an odd definition of "leaky", from my point of view. When a function body needs an input, then it makes sense to require it as a function parameter. In most programming languages (i.e., lexically-scoped languages, where you cannot inspect the variables in the environment of one's caller via dynamic scope), it is the only option. When a function body does not need an input, then one may or may not include the input as a parameter on the function's signature, depending on factors like "what might I change in the future about this function body", or "where do I pass this function around as a first class value" |
I don't think the analogy to functions is very apt. The point is, the definition of "leaky" is that an abstraction exposes information about its concrete (implementation) details. Having a type parameter for the iterator type as in your above example is clearly an implementation detail, since the consumer of the existential type has no need to know about it. |
When I implemented it, my reasoning for making it a hard error was that
is also a hard error. But what we have here is more along the lines of trait Foo<T> {} which does not error. |
Hmm. The reason unused types are an error in structs is mainly because otherwise they’d be given Bivariant variance, and @nikomatsakis and/or I felt that was likely to not be the users intention: better to force the user to indicate a variance, if only via PhantomData I assume that existential types force Invariant variance on their type parameters. Is this true? If so, then the analogy with |
I really don’t see the purpose of the error |
What if there are lifetimes attached to |
(actually ... maybe i'm wrong about the necessity of making such connections explicit... I haven't worked out all the details here yet, but I did just remember that we certainly allow things like |
Has any consensus been reached here lately? |
My current thinking is that we need to either get rid of the I don't know which path of the two is the right course, but my gut says that if we can get rid of |
(Also I probably need to find out the answer to this question:)
|
If those are the only two options, then the decision is clear (at my level of understanding of the type theory beind it): We remove the "unused type parameter" error. The error about
is not born out of a sense for minimalism like the "unused generic parameter" error. The problem is that the following code does not give us a concrete type when using the name existential type Foo: Bar;
fn foo<T: Bar>(t: T) -> Foo {
t
} It gets even more confusing if we start thinking about let mut x = foo(42);
x = foo("bar"); which, according to the types, checks out. |
Consider the following code (play):
The compiler errors, with the diagnostic
But it should not be illegal to leave a type parameter unused. It is useful, from the view-point of the implementor, to have the option of returning concrete types that do not use the given type.
(Also, I find the diagnostic itself a bit less helpful than it could be; when I first read it, I thought it was complaining about not seeing
I
on the trait-bound inexistential type <NAME>: <TRAIT_BOUND>
; I needed @eddyb to point out to me that the issue is in the body offn choose
itself.)The text was updated successfully, but these errors were encountered: