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

Rust complains about unused type parameter which is only used to parameterize another #23246

Closed
timbertson opened this issue Mar 10, 2015 · 16 comments

Comments

@timbertson
Copy link
Contributor

Code:

$ cat typetest.rs 
struct Foo<Elem, List:AsSlice<Elem>> {
    elems: List,
}

fn main() {
}

Compile:

$ rustc typetest.rs 
typetest.rs:1:12: 1:16 error: parameter `Elem` is never used
typetest.rs:1:16: 1:16 help: consider removing `Elem` or using a marker such as `core::marker::PhantomData`
error: aborting due to previous error

I'm pretty new to rust, so forgive me if I've missed something. But I can't see a way to say "my struct contains a type T2, which implements SomeGenericTrait<T>" without my struct also being parameterized over T, as I'm doing here. But rust thinks that I'm not using T.

I tried adding:

dummy: Elem,

to my struct def, which makes it compile. So it doesn't look like this is just one error message obscuring another, rust does really think this is the only thing wrong with my code.


$ rustc --version --verbose
rustc 1.0.0-dev (2fc8b1e7c 2015-03-07) (built 2015-03-08)
binary: rustc
commit-hash: 2fc8b1e7c4ca741e59b144c331d69bf189759452
commit-date: 2015-03-07
build-date: 2015-03-08
host: x86_64-unknown-linux-gnu
release: 1.0.0-dev
@alexcrichton
Copy link
Member

This is actually intended behavior due to a recently merged RFC. The idea here is that the compiler needs to know what constraints it can put on the type parameter Elem, and usage of the PhantomData type will instruct the compiler how it can do so.

You can check out the examples for PhantomData to see some usage, and in this case you'll probably want to do something along the lines of:

  1. Refactor the struct so unused type parameters aren't needed. This can often be done with associated types, something along the lines of:

    trait A { type B; }
    struct C<T: A> {
        inner: A::B,
    }
  2. Use PhantomData<Elem> to indicate that the compiler should just consider the type parameter used, considering Foo<A, B> as containing something of type A as well as B.

You probably don't want to store dummy: Elem as it will require you to store an extra element and could have other memory implications as well.

@timbertson
Copy link
Contributor Author

Thanks for the helpful explanation, @alexcrichton. You also conveniently educated me on associated types, which I was wondering about but didn't know what to search for (I was wondering "why is Iterator not generic"). It turns out this is just what I needed.

@neithernut
Copy link

neithernut commented Aug 28, 2017

I just stumbled upon this limitation in a context where PhantomData is of no help: generic enums. Consider the following piece of code:

enum Foo<A, B>
    where A: Bar<B> + Sized
{
    X(A),
    Y,
    Z
}

trait Bar<B> {
   fn bar(&self, &B);
}

Edit: I'm aware of several workarounds. These include adding PhantomData to X, but this pollutes the enum (in the sense that construction of a value by the user becomes iffy).

@Piping
Copy link

Piping commented Nov 7, 2017

Hi, I had a similar problem regarding to unused type parameter.
Here is the simplified code, I need to control the visibility for quicksort's sort function.

struct Quicksort<T>;  // <---- unused parameter warning
trait Sorter<T> {
  fn sort(&self,ar<T>){...}
  fn join(...);
  fn split(...);
}
impl<T> Quicksort<T> {
  pub fn sort(&self,ar<T>){
    Sorter::sort(self, ar<T>) ;
  }
}
impl<T> Sorter<T> for Quicksort<T>{
 fn join(...) {...}
 fn split(...) {...}
}

Any help to resolve the problem? because struct Quicksort does not have any fields. The type parameter is solely for the sort function from trait.
Otherwise if I removed impl<T> Quicksort<T>, then I don't need the T for Quicksort at all.

@zakarumych
Copy link
Contributor

zakarumych commented Jun 25, 2018

For those who will look here for answers:
"In most cases you do not need additional type parameters at all".
That's it. Just remove them.

Taking @Piping example for illustration.
Let's get rid of type parameter for Quicksort.

struct Quicksort;

Now rustc obviously won't complain about unused parameters since there are none.
But at some point we actually need that type-parameter.

Continuing. Type parameter can be introduced at function level.

impl Quicksort {
  pub fn sort<T>(&self,ar<T>){
    Sorter::sort(self, ar<T>) ;
  }
}

Now when it comes to implementing a trait things goes smoothly again since type parameter is already exists in Sorter trait.

impl<T> Sorter<T> for Quicksort {
 fn join(...) {...}
 fn split(...) {...}
}

There are few places where you actually need artificial type parameter.

  1. When type conceptually contains some generic type.
  2. Type must implement a trait with associated type.
trait Trait {
  type X;
}

Now either the type in question will be able to implement the Trait with some predefined X.

struct Foo;

impl Trait for Foo {
  type X = u32;
}

Or it has to have this artificial type parameter after all.

struct Foo<T>(PhantomData<T>);

impl<T> Trait for Foo<T> {
  type X = T;
}

@AdrienChampion
Copy link

AdrienChampion commented Apr 5, 2020

@neithernut

In case you're still interested, I had a similar problem and I solved it by creating an additional variant that stores the PhantomData and a never type Empty:

pub enum Empty {}
pub enum Foo<A, B> {
    X(A),
    // ...
    Void(Empty, PhantomData<B>),
}

playground example

The good news is that it's zero-cost and does not pollute the variants you do use.

The bad news is that, until never type exhaustive matching comes to Rust, this approach will pollute the code when you match on your enum (although you could macro your way around it):

match foo {
    Foo::A(_) => (),
    // ...
    Foo::Void(empty, _) => match empty {},
    //               ^
    // phantom data
}

@mankinskin
Copy link

@alexcrichton
I also had some issues with this and generic enums. It would be very useful if the compiler could recognize that the type parameter is only used in a trait bound for another type parameter. I don't understand the reason why this would not work now.. Can't the compiler derive the constraints from the trait the type parameter is passed to?

@raphaelcohn
Copy link

@mankinskin I agree. PhantomData in this instance just pollutes the code. It is an implementation detail that should never be exposed in a public API (which is what happens with both @AdrienChampion 's and @neithernut 's code. It's also exceedingly irritiating, adding code complexity, complicating construction (so making it harder to just pass generic enum members as closures w/o writing extra code, etc. It's a code smell in Rust itself.

@dlubarov
Copy link

dlubarov commented Nov 24, 2020

Looking back to @alexcrichton's response and the RFC, I think the question is not so much why unused type parameters cause an error, but why the compiler considers type parameters unused in examples like the OP's.

To me, it seems like Elem has one usage (in List: AsSlice<Elem>), albeit not in the body of the struct. It seems like that should constitute a usage for the purpose of variance inference, since it implies a variance bound: Elem must be invariant since AsSlice's parameter is invariant.

In Scala for example, the equivalent would be class Foo[E, S <: Seq[E]](val seq: S). Scala doesn't infer variance, but it "knows" that E must be covariant or invariant. If we try to make it contravariant (with -E), we get error: contravariant type E occurs in covariant position.

If getting the compiler to recognize these variance bounds would be complicated, perhaps lifetime and non-lifetime parameters could be handled differently? As the RFC notes, only lifetimes have subtype relationships, so it seems like non-lifetime parameters might as well be "inherently" invariant, and not subject to inference. Code like struct Thing<'a, 'b: 'a>(&'b u32); would continue to be rejected, but I don't think there's any "legitimate" reason to write lifetime parameters like that.

@shlevy
Copy link
Contributor

shlevy commented Feb 21, 2021

I think perhaps we should reopen this one. The associated RFC doesn't seem to justify a compiler error in code like:

pub struct Foo<A> {
    a: A,
}

pub struct Bar<A, F>
where
    F: AsRef<Foo<A>>,
{
    foo: F,
}

@steveklabnik
Copy link
Member

Changes of this magnitude would require an RFC these days, and I would encourage folks to either open an RFC or a thread on users or internals rather than continuing to comment on an issue that was closed almost six years ago.

@mankinskin
Copy link

But can't this issue be reopened? This is kind of the first place you end up at when googling about this, and the discussion is basically already here. I don't think there is a lot of designing left to do, it just needs to be implemented in the right way.

I can try to write up an RFC some time, but it is not high on my priority list right now and I am completely unexperienced with rustc. Opening up this issue for now would at least raise attention to it and helps resolving this faster.

@steveklabnik
Copy link
Member

Large scale changes to the language, of which this is one, require an RFC. I am not on the lang team, but this isn't actually a bug: this is a request for a change to how the language works. A bug being open for it is not the procedure by which this would be changed.

@raphaelcohn
Copy link

@mankinskin I agree completely, and I feel your frustration; I share it, and it's got worse as rust has become more widely used. Rust's core team also have a very narrow view of what a bug is - one that with my software craftsmanship hat on I couldn't disagree with more strongly.

Rust's development has been captured by the bureaucratically minded; the same mindset that has infested wikipedia, and parodied beautifully in Brazil.

Personally, I find it rude; it shows disinterest in the views of those outside of the main development, and the idea that anyone can have a good idea.

@mankinskin
Copy link

@raphaelcohn I know what you mean and we should always be wary of overdoing that, it might stagnate innovation, but I also think there is value in organization and strict review. Rust's promise is to be a secure language, that means it must takes steps carefully, which makes it more difficult to innovate. I agree that there eventually needs to be an RFC for this but I don't really see the point of restricting discussions about feature changes in these github issues. I suppose they use this as a bug tracker, and new features are not supposed to be tracked here.

In general, I think there are a lot of improvements that could be made to the entire open source workflow nowadays. The tools don't really integrate well, basically just using hyperlinks. For open source work there ought to be issue dependencies, resource budgets, integrated documentations and tests, voting on forks, funding, user permissions, all of that. That would make open source a lot more "open" and transparent. It has to come eventually, but there still needs some work to be done.

@BurntSushi
Copy link
Member

Moderation note: what @steveklabnik said is correct. The kind of unconstructive broad criticism that has taken place in the past few comments is not appropriate here. Since this discussion doesn't seem to be headed anywhere good, and the next steps if one were to make progress have been clarified, I'm going to lock this thread.

@rust-lang rust-lang locked as too heated and limited conversation to collaborators Feb 25, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests