rusthigher-rank-types

Using HRTBs to automate getter method unit tests


I have a "view" data structure in Rust that contains a reference to another data structure, and I want to automate testing of the getter methods that I implemented on the view data structure. Here's a MRE with a single getter:

/// View data structure.
struct View<'a> {
    value: &'a u32,
}
impl<'a> View<'a> {
    /// Getter method.
    pub fn value(&self) -> u32 {
        *self.value
    }
}

/// Attempt to automate unit testing of the getter: create an underlying value,
/// then create a `View`, then apply the getter method to the `View` and ensure
/// we get the expected value out.
fn check_getter(getter: fn(&View) -> u32) {
    let value: u32 = 7;
    let view = View { value: &value };
    assert!(getter(&view) == 7);
}
check_getter(View::value);

This fails to compile, with the following error:

mismatched types
expected fn pointer `for<'r, 's> fn(&'r tests::test_adcs::View<'s>) -> _`
   found fn pointer `for<'r> fn(&'r tests::test_adcs::View<'_>) -> _`

I think I need to use HRTBs, and have tried various permutations, but haven't been able to get anything to compile.


Solution

  • TL;DR: Use a closure (you can use macros if you prefer):

    check_getter(|v| v.value());
    

    You fell into the trap of early-bound vs. late-bound lifetimes.

    View::value does not have the signature for<'a> fn <View<'a>>::value(). Rather, it has the signature fn <View<'some_concrete_lifetime>>::value(). That is, 'a is not any lifetime, but some inferred lifetime. Therefore HRTB doesn't work - only one lifetime matches the given function. You can read more about early-bound and late-bound lifetimes in this recent answer I wrote, but the crux of the issue is that because the lifetime 'a appears on the impl, not the function itself, it becomes early-bound and has only one concrete lifetime for each instance of the function instead of being late-bound and allowed to use with any lifetime.

    The fix is just to wrap it with a closure, making it late-bound.