12
use std::iter::Iterator;

trait ListTerm<'a> {
    type Iter: Iterator<Item = &'a u32>;
    fn iter(&'a self) -> Self::Iter;
}

enum TermValue<'a, LT>
where
    LT: ListTerm<'a> + Sized + 'a,
{
    Str(LT),
}
error[E0392]: parameter `'a` is never used
 --> src/main.rs:8:16
  |
8 | enum TermValue<'a, LT>
  |                ^^ unused type parameter
  |
  = help: consider removing `'a` or using a marker such as `std::marker::PhantomData`

'a clearly is being used. Is this a bug, or are parametric enums just not really finished? rustc --explain E0392 recommends the use of PhantomData<&'a _>, but I don't think there's any opportunity to do that in my use case.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
mako
  • 1,201
  • 14
  • 30
  • 1
    You error says struct, your code says enum? – Neikos Nov 08 '16 at 10:22
  • @Neikos ah, the error occurs in both cases, but with a struct it's much less annoying to add a phantomdata cause the user isn't necessarily interacting with it directly. I wanted to make it clear my use case is an enum so it'd be really bad ergonomics to require users to match a phantomdata whenever they unwrap one – mako Nov 08 '16 at 18:57

3 Answers3

19

'a clearly is being used.

Not as far as the compiler is concerned. All it cares about is that all of your generic parameters are used somewhere in the body of the struct or enum. Constraints do not count.

What you might want is to use a higher-ranked lifetime bound:

enum TermValue<LT>
where
    for<'a> LT: 'a + ListTerm<'a> + Sized,
{
    Str(LT),
}

In other situations, you might want to use PhantomData to indicate that you want a type to act as though it uses the parameter:

use std::marker::PhantomData;

struct Thing<'a> {
    // Causes the type to function *as though* it has a `&'a ()` field,
    // despite not *actually* having one.
    _marker: PhantomData<&'a ()>,
}

And just to be clear: you can use PhantomData in an enum; put it in one of the variants:

enum TermValue<'a, LT>
where
    LT: 'a + ListTerm<'a> + Sized,
{
    Str(LT, PhantomData<&'a ()>),
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
DK.
  • 55,277
  • 5
  • 189
  • 162
  • 1
    N...No. The lifetime bound is used in the body of the enum. The fact that it needs to look at the trait bounds to figure that out doesn't explain why it isn't getting it. This is a helpful answer though. It still looks like neither of these techniques will do it for my use case. IRL, TermValue takes another type that needs a lifetime equivalent to the other, and for<'a> bounding can only apply to one bound at a time, right? – mako Nov 09 '16 at 00:20
6

DK. answered how to circumvent the issue (by using PhantomData as suggested), and hinted that the issue was that 'a was unused in the definition, but why would the compiler care about that?

'a is a lifetime marker. It is used by the borrow-checker to identify the relationship between the lifetime of different objects, as well as their borrow status.

When borrowing an object, you may borrow it either mutably (&mut T) or immutably (&T), and in accordance with the Mutability XOR Aliasing principle underpinning Rust's memory safety it changes everything:

  • You can have multiple concurrent &T
  • You can only have a single &mut T, and it excludes concurrent &T

When you parameterize your struct or enum with 'a, you announce your intention to borrow something whose lifetime will be some 'a. You do not, however, announce whether you will be borrowing mutably or immutably, and this detail is critical.

The compiler, therefore, will peer at the internals of your data type and check whether you use a mutable or immutable reference to deduce, by itself, which kind of borrow will occur when you use the data type.

And here, because 'a is unused, it cannot find any such use and therefore cannot compile your code.


It is arguable whether the compiler peering inside the data type is a good thing or not, since changing the internals of this data type (from &T to &mut T) could lead to compilation failures without changing the type interface.

It is important, thus, to remember that how you use the generic parameters (owning, borrowing mutably or borrowing immutably) is NOT an implementation detail.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
4

If you aren't using an associated type (like <LT as ListTerm<'a>>::Iter) in the enum definition, you probably don't need to make 'a a parameter at all.

I assume you want the LT: ListTerm<'a> bound so that you can write one or more fns or an impl that uses LT as a ListTerm. In which case, you can easily parameterize the type with just <LT>, and put the 'a generic and trait bound only on the items that require it:

trait ListTerm<'a> {
    type Iter: Iterator<Item = &'a u32>;
    fn iter(&'a self) -> Self::Iter;
}

enum TermValue<LT> {  // no 'a parameter here...
    Str(LT),
}

impl<'a, LT> TermValue<LT>  // ... just here
where
    LT: ListTerm<'a>,
{
    fn iter(&'a self) -> LT::Iter {
        match *self {
            TermValue::Str(ref term) => term.iter(),
        }
    }
}

Some standard library types like std::collections::HashMap<K, V> do this: the K: Hash + Eq bound isn't on the type itself. Alternatively, you could have a where clause on each method where the bound is needed. The difference between a where clause on an impl and one on a fn is not significant unless you're implementing a trait (see this question).

The main reason for using PhantomData is that you want to express some constraint that the compiler can't figure out by itself. You don't need PhantomData to express "Any TermData<LT> is only valid as long as its contained LT is valid", because the compiler already enforces that (by "peering inside" the type, as in Matthieu's answer).

Community
  • 1
  • 1
trent
  • 25,033
  • 7
  • 51
  • 90