Confusion about implementing std::ops::Index

I tried to implement std::ops::Index for a custom struct to index it's fields.
Here's the compiler Error:

error: lifetime may not live long enough
   --> src/lib.rs:232:13
    |
232 |               if $index == $index_var {
    |               ^^ returning this value requires that `'a` must outlive `'static`
...
344 |           impl<'a> std::ops::Index<usize> for Parser<'a> {
    |                -- lifetime `'a` defined here
...
587 | /     revparse! {
588 | |         [some_option, 's', "help message"];
589 | |         [long_name, 'l', "help message", "value"];
590 | |         [another_opt, "help message", "value"];
...   |
595 | |         [pos_help "POSITIONAL ARGUMENT HELP MESSAGE THIS IS TEDIOUS"]
596 | |     };
    | |_____- in this macro invocation
    |
    = note: requirement occurs because of the type `RefCell<dyn ResVal<'_>>`, which makes the generic argument `dyn ResVal<'_>` invariant
    = note: the struct `RefCell<T>` is invariant over the parameter `T`
    = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
    = note: this error originates in the macro `$crate::revparse` which comes from the expansion of the macro `revparse` (in Nightly builds, run with -Z macro-backtrace for more info)

Playground code

It's at line 344 in the macro. Just ignore the macro code, it's needed for completion, else it won't make sense.

This problem isn’t about the Index trait, but about the dyn return type you have declared not matching what you are returning. You’re mixing up trait lifetime parameters (that you don't need) and trait object lifetime bounds (that you do need).

  • The trait ResVal has a lifetime parameter it does not use. This parameter should be removed (this is not necessary, but it will reduce confusion).

  • The type alias A (and perhaps other places, but definitely this one) needs to allow its dyn to have a lifetime bound shorter than 'static, so it should be:

    type A<'a> = std::cell::RefCell<dyn ResVal + 'a>;
    

All together, these changes will make your program compile:

--- a.rs	2025-09-20 08:48:40
+++ b.rs	2025-09-20 08:51:06
@@ -80,7 +80,7 @@
         self
     }
 }
-pub trait ResVal<'a> {
+pub trait ResVal {
     fn res_val(&self) -> bool;
     fn long_name(&self) -> &str;
     fn short_name(&self) -> Option<char>;
@@ -101,7 +101,7 @@
     pub user_val: &'a str,
     pub value: Option<String>,
 }
-impl<'a> ResVal<'a> for Box<dyn ResVal<'a>> {
+impl<'a> ResVal for Box<dyn ResVal + 'a> {
     fn help_msg(&self) -> &str {
         (**self).help_msg()
     }
@@ -121,7 +121,7 @@
         (**self).user_val()
     }
 }
-impl<'a> ResVal<'a> for ArgRes<'a> {
+impl<'a> ResVal for ArgRes<'a> {
     fn res_val(&self) -> bool {
         true
     }
@@ -141,7 +141,7 @@
         self.was_called = true;
     }
 }
-impl<'a> ResVal<'a> for ArgVal<'a> {
+impl<'a> ResVal for ArgVal<'a> {
     fn res_val(&self) -> bool {
         false
     }
@@ -218,7 +218,7 @@
         // Long name, short name and help message
         $crate::revparse!(@r $self_var, $parser, $index + 1usize, $index_var, {$($rest)*} -> {
             $($create_struct)*
-            $ln: std::cell::RefCell<Box<dyn $crate::ResVal<'a>>>,
+            $ln: std::cell::RefCell<Box<dyn $crate::ResVal + 'a>>,
         } -> {
             $($init_struct)*
             $ln: std::cell::RefCell::new(Box::new($sn_or_help.construct(
@@ -342,7 +342,7 @@
             $($create_struct)*
         }
         impl<'a> std::ops::Index<usize> for Parser<'a> {
-            type Output = std::cell::RefCell<dyn ResVal<'a>>;
+            type Output = std::cell::RefCell<dyn ResVal + 'a>;
             fn index(&$self_var, $index_var: usize) -> &Self::Output {
                 $($index_struct)*
                 { exit(37) }
@@ -475,7 +475,7 @@
         //$crate::parse(&mut $parser, $index, std::env::args());
     };
 }
-type A<'a> = std::cell::RefCell<dyn ResVal<'a>>;
+type A<'a> = std::cell::RefCell<dyn ResVal + 'a>;
 pub fn parse<'a, T>(p_args: &'a mut T, max_index: usize, args: impl Iterator<Item = String>)
 where
     T: Ptr + Index<usize, Output = A<'a>> + IndexMut<usize>,

I haven’t checked whether any other lifetime annotations are incorrect.

2 Likes

I didn't bother with the macro or wider design, but to explain the immediate cause of the error:

struct Parser<'a> {
    some_option: std::cell::RefCell<Box<dyn crate::ResVal<'a>>>,
    long_name: std::cell::RefCell<crate::ArgVal<'a>>,
    another_opt: std::cell::RefCell<Box<dyn crate::ResVal<'a>>>,
    short_hand_vers: std::cell::RefCell<crate::ArgRes<'a>>,
}
impl<'a> std::ops::Index<usize> for Parser<'a> {
    // Aka RefCell<dyn ResVal<'a> + 'static>
    type Output = std::cell::RefCell<dyn ResVal<'a>>;
    fn index(&self, index: usize) -> &Self::Output {
        if 0usize == index {
            &self.some_option
        } else if 0usize + 1usize == index {
            &self.long_name
        } else if 0usize + 1usize + 1usize == index {
            &self.another_opt
        } else if 0usize + 1usize + 1usize + 1usize == index {
            &self.short_hand_vers
        } else {
            exit(37)
        }
    }
}

You're trying to coerce each of these:

//      vvvvvvvvvvvvvvvvvvvvvvvvvvvvv
RefCell<Box<dyn ResVal<'a> + 'static>>
//      vvvvvvvvvv
RefCell<ArgVal<'a>>
//      vvvvvvvvvvvvvvvvv
RefCell<crate::ArgRes<'a>>

To RefCell<dyn ResVal<'a> + 'static>, which means the highlighted types above need to be able to coerce to dyn ResVal<'a> + 'static. Which requires the highlighted types meet a lifetime bound of : 'static.

But they do not, they only meet a bound of 'a. They can coerce to dyn ResVal<'a> + 'a but not to dyn RevVal<'a> + 'static.

Reduced example.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.