IIRC this has been rust behavior from the very start, so perhaps it is sacrilegious to suggest it but I don't believe that unused generic parameters on enums, structs, and traits should be a hard error, and I do not see any reason why they need to be instead of a warning.
I often find myself omitting a member from a struct that was the only instance of a generic parameter or lifetime being used to try something out. Yes, it's possible to use a phantom marker but that isn't the most ergonomic approach.
Is there a reason besides ideological purity why an unused generic parameter if implemented correctly in all references to the object must be an error?
e.g. I do not think there is actual technical reason (i.e. unsoundness or ambiguity) why the following code should fail to compile:
struct Foo<T> {
bar: String,
// baz: Vec<T>,
}
impl<T> Foo<T> {
fn new() -> Self {
Self {
bar: String::new(),
// baz: Vec::new(),
}
}
}
fn main() {
let foo: Foo<()> = Foo::new();
}
While <T> is not used in the definition of Foo, all references to Foo (such as impl Foo and the instantiation site in main) handle it and the compiler could theoretically accept this (with lots of grimacing and complaining), no?
For reference, the following error is currently returned:
error[E0392]: parameter `T` is never used
--> ./test.rs:1:12
|
1 | struct Foo<T> {
| ^ unused parameter
|
= help: consider removing `T`, referring to it in a field, or using a marker such as `std::marker::PhantomData`
error: aborting due to previous error
(Originally filed as https://github.com/rust-lang/rust/issues/70792)
One reason this is like this is that the compiler cannot infer the variance of T if it is unused, so requiring the user to mention it in a PhantomData makes clear what variance is desired. It could of course have a default, but which one is appropriate is probably not a trivial question.
My understanding is that while it is technically correct that an unused generic parameter cannot cause soundness issues on its own, there's also no reason you would ever want an unused generic parameter unless there is some sort of invisible or overly indirect reference to or ownership of a T which the compiler does not fully understand, in which case soundness almost certainly does depend on getting variance and drop check right, and PhantomData<T> is how Rust does that.
For prototyping / experimental coding, I understand how this looks excessive. But consider that the Vec<T> being commented out is being used to infer a variance. If we "just" allowed the code to compile anyway, we'd potentially be changing the variance we infer for the T type parameter. And that variance changing might cause other code to suddenly stop compiling, likely with an error that's very confusing no matter how well we word it because hardly anyone is consciously thinking about variance when they write their generic code (nor should they have to).
So AFAIK, there is no "ideological purity" going on, but rather a combination of the cold facts about soundness mentioned above, and these additional beliefs which seem highly plausible to me:
PhantomData<T> field is the most ergonomic way to get variance and drop check rightPhantomData<T>) is far, far easier to understand than a compile-time error about variance mismatches or sound generic drop requirementsIf we start with the premise (which I think we can all agree on) that code should never be committed/deployed into production with warnings, that really puts a temporal restriction on how far apart "obviating an unused parameter" and "getting obscure compilation error" can be. More importantly, I think that the points you bring up can actually be copy-and-pasted more or less unchanged for most warnings emitted by the rust compiler - I don't particularly see why they would necessarily be more pertinent to an unused generic parameter over some of the other code-smell issues that currently raise warnings and not errors. In fact, I would argue that what you describe is the definition of a compiler warning: you are doing something technically correct but it is emitting great code smell and is likely to cause you grief down the line if not corrected or addressed, and can only be ignored by specifically #[allow(..)]ing the code or practice in question.
Specifically with regards to default variance, the options as I see them would be to assume an implicit PhantomData<&'a T> (assuming both an unused generic lifetime 'a and unused generic parameter T), although I do wonder if there are cases where that might trigger an immediate compilation error rather than "at some point in the future".
We might consider const members of structs so
struct Foo<T> {
bar: String,
const baz: PhantomData<Vec<T>> = PhantomData,
}
More importantly, I think that the points you bring up can actually be copy-and-pasted more or less unchanged for most warnings emitted by the rust compiler
Some can, but I believe the "lead to hard errors elsewhere" and "obscure" points are fairly unique in this case. An unused variable could conceivably lead to other self-explanatory unused/dead warnings within the same function, but it can't cause a subtle type checking failure five modules away like this could.
Assuming a default variance is certainly an option, but I doubt it'd be a net win. Both covariance and invariance are quite common in practice (even if contravariance is pretty rare), and relying on covariance is also pretty common. But I have no idea how to make that quantifiable enough to presuade anyone who feels otherwise.
If we need to pick a "default variance" it should be the most conservative variance (ie. invariant) not covariant (as PhantomData<T> would give you).
error[E0392]: parameterTis never used implies the compiler is indeed capable of identifying the generic type parameter T as unused and could therefore ignore it and only warn. But of course this – and IMHO any warning – should not be allowed in production/final code.
Most helpful comment
One reason this is like this is that the compiler cannot infer the variance of
Tif it is unused, so requiring the user to mention it in aPhantomDatamakes clear what variance is desired. It could of course have a default, but which one is appropriate is probably not a trivial question.