Impl. a trait for an existing struct/impl with lifetimes & generics from Gimli


#1

I’m playing with Gimli and I’m having some troubles to add some convenience methods on DebuggingInformationEntry.

Here’s the definition from Gimli:

pub struct DebuggingInformationEntry<'abbrev, 'unit, R, Offset = usize>
where
    R: Reader<Offset = Offset> + 'unit,
    Offset: ReaderOffset + 'unit,
{ //... }

and its implementation:

impl<'abbrev, 'unit, R, Offset> DebuggingInformationEntry<'abbrev, 'unit, R, Offset>
where
    R: Reader<Offset = Offset>,
    Offset: ReaderOffset,
{ //... }

I’d like to add a trait looking like this:

trait UnitConvenienceMethods {
    fn foo() -> bool;
    fn bar() -> i32;
}

impl UnitConvenienceMethods for DebuggingInformationEntry<'abbrev, 'unit, R, R::Offset> 
where R: gimli::Reader,
{ //... }

I cannot get my head around how I should declare lifetimes and generics for my custom trait and impl block.

From what I understand, the trait declaration should not be burdened with lifetime/generic hint but its impl on DebuggingInformationEntry should, correct?


#2

If your custom trait doesn’t need lifetime or generic parameters itself, then you don’t need to add any to it. For the trait implementation, you basically have to redeclare DebuggingInformationEntry's types and bounds, e.g.:

impl<'abbrev, 'unit, R, Offset> UnitConvenienceMethods
    for DebuggingInformationEntry<'abbrev, 'unit, R, Offset>
where
    R: Reader<Offset = Offset>,
    Offset: ReaderOffset,

I think you can omit the lifetime bounds on R and Offset that are present in the struct declaration (e.g. the “+ 'unit” in the R: Reader<...> + 'unit) if you’re not going to make use of them in the implementation. But otherwise, you could redeclare DebuggingInformationEntry just like the struct, including the lifetime bounds, e.g.:

impl<'abbrev, 'unit, R, Offset> UnitConvenienceMethods
    for DebuggingInformationEntry<'abbrev, 'unit, R, Offset>
where
    R: Reader<Offset = Offset> + 'unit,
    Offset: ReaderOffset + 'unit,

#3

Thanks! It clarifies the situation a bit. Unfortunately I’m still stuck. The following:

trait UnitConvenienceMethods { //... }

impl UnitConvenienceMethods<'abbrev, 'unit, R, Offset> 
    for DebuggingInformationEntry<'abbrev, 'unit, R, Offset> 
where  
    R: gimli::Reader,
    Offset: gimli::ReaderOffset,
{ // ... }

yields some errors. The compiler tells that R and Offset are not found in this scope. That abbrev and unit are undeclared lifetimes and finally the last errors make me think that the trait must somehow declare the lifetimes and generic types:

error[E0107]: wrong number of lifetime parameters: expected 0, found 2
  --> src/main.rs:19:6
   |
19 | impl UnitConvenienceMethods<'abbrev, 'unit, R, Offset> 
   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 unexpected lifetime parameters

error[E0244]: wrong number of type arguments: expected 0, found 2
  --> src/main.rs:19:6
   |
19 | impl UnitConvenienceMethods<'abbrev, 'unit, R, Offset> 
   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected no type arguments

I guess this is pushing my limited knowledge to the limit :stuck_out_tongue:


#4

The generic type and lifetime parameters go into the impl part, ie impl<...they go here...> UnitConvenienceMethods for .... You have them in the UnitConvenienceMethods. Look at the code snippet I pasted in my first post.


#5

Stupid me, thanks!

I had another issue with associated type vs generic:

error[E0271]: type mismatch resolving `<R as gimli::Reader>::Offset == Offset`
  --> src/main.rs:19:33
   |
19 | impl<'abbrev, 'unit, R, Offset> UnitConvenienceMethods 
   |                                 ^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found type parameter
   |
   = note: expected type `<R as gimli::Reader>::Offset`
              found type `Offset`
   = note: required by `gimli::DebuggingInformationEntry`

For the context, the gimili Reader trait has an Offset associated type:

pub trait Reader: Debug + Clone {
    /// The endianity of bytes that are read.
    type Endian: Endianity;

    /// The type used for offsets and lengths.
    type Offset: ReaderOffset; 

I had to specify the Offsetassociated type of the Reader to the Offset specified in the generics declaration:

impl<'abbrev, 'unit, R, O> UnitConvenienceMethods 
    for DebuggingInformationEntry<'abbrev, 'unit, R, O> 
where  
    R: gimli::Reader<Offset=O>,
    O: gimli::ReaderOffset,

I’ve renamed the name of the generic type to have a better distinction between the generic type and the associated type. I don’t know if it is a good practice or not.

This issue really pushed me into generics, associated types and lifetime declarations. Thanks for your help @vitalyd!


#6

I think I had that in my original post as well :slight_smile:

This is fine - I don’t know if there’s an established convention. I’ve personally seen more of the style where the generic name matches the associated type name (i.e. Reader<Offset = Offset>), and one can argue that this better reflects the purpose of the generic type. However, you can certainly rename if it makes the code easier for you to read/understand.


#7

Yep you had :slight_smile:

Having different names helps me to make the distinction but it’s probably because I’m not (yet) used to the notation and pattern.