How to make rustc understand that a reference to a function pointer can have a longer lifetime than the parameter/return types of the function it points to

struct Criterion {
  // ...
}
// This trait definition will not compile unless I add a 'static bound to it,
// apparently because it doesn't want a &'static reference to a type that has
// a non-'static generic parameter (the argument to the function).
// This is an unacceptable limitation for my use case.
// Is there some workaround I can use?
trait SomeTrait {
   const CASES: &[(Criterion, fn(Self) -> ReturnType)];
}

fn transform<T: SomeTrait>(value: T, request: String) -> ReturnType {
    for (criterion, transform_func) in T::CASES {
        if criterion.matches(&request) {
            return (transform_func)(value);
        }
    }
    ReturnType::default()
}

There's no safe way to "disable" the outlives check as it would be unsound in the general case.

I think what you need here is a way for the reference in the const to be non-'static. generic_const_items can do it but that's an unstable, incomplete feature.[1]

Edit: Here's a way to define the const for all lifetimes 'a such that Self: 'a on stable.

(That's the same playground as my comment two posts later.)

Click for original failed attempts and suggestions

I attempted to mimic a GAT work around instead:

trait SomeTraitLifetime<'a, Guard = &'a Self>: 'a /* (1) */ {
    const CASES: &'a [(Criterion, fn(Self) -> ReturnType)]
        // (2)
        where Self: 'a,
    ;
}

trait SomeTrait: for<'a> SomeTraitLifetime<'a> {}
impl<T: ?Sized + for<'a> SomeTraitLifetime<'a>> SomeTrait for T {}

But this didn't work as one of (1) or (2) is required to compile, and

I may have missed a way to make those work.

But perhaps a better An alternative approach is to drop the requirement to use a const and instead use:

trait SomeTrait {
    fn cases<'a>() -> &'a [(Criterion, fn(Self) -> ReturnType)]
    where
        Self: 'a,
    ;
}

Although you do lose the compile time nature of the const this way.


  1. but here it is anyway â†Šī¸Ž

1 Like

Edit: The suggestion in my next post is better IMO, but I don't know if everyone would share that opinion.


Maybe I got too caught up on the "for any" in my GAT mimicking attempts. You can also just put a lifetime on the trait and use it directly:

trait SomeTrait<'a>: 'a {
    const CASES: &'a [(Criterion, fn(Self) -> ReturnType)];
}

fn transform<'t, T: SomeTrait<'t>>(value: T, request: String) -> ReturnType {
    for (criterion, transform_func) in T::CASES {
        if criterion.matches(&request) {
            return (transform_func)(value);
        }
    }
    ReturnType::default()
}


impl<'a, 'i: 'a> SomeTrait<'a> for &'i () {
    const CASES: &'a [(Criterion, fn(Self) -> ReturnType)] = &[
        (Criterion {}, |_| {})
    ];
}

Though it does make the lifetime infect everything.

1 Like

Sorry for the spam, I keep realizing I'm not at a dead end. Here's a for<'a> version that works. (I've edited my first reply to contain link to this solution too.)

trait SomeTraitLifetime<Cases> {
    const CASES: Cases;
}

trait SomeTrait: for<'a>
    SomeTraitLifetime<&'a [(Criterion, fn(Self) -> ReturnType )]>
{}
impl<T> SomeTrait for T where ... {}

// Non-`'static` implementor.  It's important to not put any bounds
// involving `'a` on the implementation or `for<'a> SomeTraitLifetime<'a>`
// may not hold.
impl<'a, 'i> SomeTraitLifetime<&'a [(Criterion, fn(Self) -> ReturnType)]> for &'i () {
    const CASES: &'a [(Criterion, fn(Self) -> ReturnType)] = &[];
}
1 Like