简体   繁体   中英

Why is the "type parameter never used" even though it is used in a where clause?

I am quite new to Rust, and I keep crossing with this issue, and I don't know how to go around it. I have a struct like,

pub struct Solver <'e, E: 'e, T, M, C>
    where
        E: Equation<T, M, C>,
        T: Term<C>,
        M: Mesh,
{
    equations: &'e [E],
}

that is always giving me this error,

error[E0392]: parameter `T` is never used
 --> src\solver.rs:5:31
  |
5 | pub struct Solver <'e, E: 'e, T, M, C>
  |                               ^ unused parameter
  |
  = help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData`

I understand that T is never used, but, for instance, I can't define Equation without T (or can I?). Is there a shorter version to write it? This PhantomData is a strange thing I would like to avoid, so I don't know how to solve this.

When bounding types for generics, bounds only make sense to the compiler where the features of the bounding types are used (calling their methods or accessing their fields, etc). It's not often necessary to constrain things otherwise. There are reasons to do it, but it's good to add those in only when the need is realized. And when declaring generic parameters, the same applies. Unless there's an explicit use of that parameter within the struct, it may not need to be added regardless whether the impl may need it.

In the code below, Solver isn't doing anything with E except holding it in an array, so that could be any type. It's flexible.

pub trait Equation
{
    type T;
    fn eq(&self) -> Self::T;
}

pub struct Solver<'e, E: 'e>
{
    equations: &'e [E],
}

impl<'e, E: 'e> Solver<'e, E>
{
    fn get_e(&self, i: usize) -> &'e E
    {
        &self.equations[i]
    }
}

Now suppose Solver will need to invoke the eq() method on the E 's in its array. Still, so far it's not necessary to put bounds on E yet in the struct definition. The bounds only have meaning in the implementation.

pub struct Solver<'e, E: 'e>
{
    equations: &'e [E],
}

impl<'e, E: 'e, F> Solver<'e, E>
where 
    E: Equation<T = F>,
{
    fn get_e(&self, i: usize) -> &'e E
    {
        &self.equations[i]
    }
    fn e_eq(&self, i: usize) -> F
    {
        self.equations[i].eq()
    }
}

The above will compile fine. The struct only cares that it has an E of any type and its lifetime adheres to 'e . And the impl became a little more specific by necessity.

The impl for structs can be somewhat independent in this regard. We didn't add any constraints until there was a need that arose from invoking .eq() and doing something with its return type. And a new generic parameter was added, because it had to be - there's an immediate use of it and the compiler will be satisfied.

Above, the specific type returned by .eq() was deferred to generic parameter F . So far, Solver can take any E that returns any type T as yet undefined - which is associated with Solver 's F - and return it from e_eq() .

To constrain things more and ensure that any E that Solver takes returns an i32 from its eq() method, F can be eliminated, and the type specified in the bounds of E :

impl<'e, E: 'e> Solver<'e, E>
where 
    E: Equation<T = i32>,
{
    fn get_e(&self, i: usize) -> &'e E
    {
        &self.equations[i]
    }
    fn e_eq(&self, i: usize) -> i32
    {
        self.equations[i].eq()
    }
}

All the changes so far have been by necessity and haven't affected the definition of Solver 's struct - just the impl .

I've personally found deferring things like this to be a good approach. Incrementally adding bounds as they're required, while continually ensuring that the code builds each step of the way can save time, as opposed to declaring several things ahead of time. If I think of several things that need to be added, I put them in TODO comments and work the changes in one at a time.

If you're working from a design doc of some sort while teammates are implementing things that will be dependent on the pieces you provide, you can add the needed methods to the impl and work in the bounds as they're needed and leave the method bodies empty with an unimplemented!() macro in them. Let the compiler give you hints on what bounds you need to add in as you go.

Strict constraints in the struct declaration may only be required when there are public fields, and other parts of the program make assumptions about what interfaces or properties those fields support.

Use PhantomData , from the documentation:

Zero-sized type used to mark things that “act like” they own a T.

Adding a PhantomData field to your type tells the compiler that your type acts as though it stores a value of type T, even though it doesn't really. This information is used when computing certain safety properties.

struct Solver <'e, E: 'e, T, M, C>
    where
        E: Equation<T, M, C>,
        T: Term<C>,
        M: Mesh,
{
    equations: &'e [E],
    _phantom_t: PhantomData<T>,
    _phantom_m: PhantomData<M>,
    _phantom_c: PhantomData<C>
}

Playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM