Trait that accepts (optionally) referencial fn parameter

For reasons that are not particularly important in this context, I want to be able to do something very similar to the following for various different types:

pub trait MapValue<'a, Out> {
    fn map_value<C>(&'a self, map_func: fn(C, Out), context: C);
}

impl MapValue<'_, i32> for i32 {
    fn map_value<C>(&self, map_func: fn(C, i32), context: C) {
        map_func(context, *self);
    }
}

// This one works
// impl<'a> MapValue<'a, &'a i32> for i32 {
//     fn map_value<C>(&'a self, map_func: fn(C, &'a i32), context: C) {
//         map_func(context, self)
//     }
// }

// But for some types I actually need to do this
impl<'a, 'b: 'a> MapValue<'b, &'a i32> for i32 {
    fn map_value<C>(&'b self, map_func: fn(C, &'a i32), context: C) {
        let cpy = *self; // Something more useful happens here in the real code
        map_func(context, &cpy);
    }
}

Unfortunately that fails to compile with the following error:

22 |         map_func(context, &cpy);
   |         ------------------^^^^-
   |         |                 |
   |         |                 borrowed value does not live long enough
   |         argument requires that `cpy` is borrowed for `'a`
23 |     }
   |     - `cpy` dropped here while still borrowed

Obviously I must be doing my lifetime annotations wrong as the call to map_func returns before cpy goes out of scope so it should be impossible that cpy does not live long enough to be passed into map_func. Any ideas?

It isn't going to work if you copy the value. What's wrong with this?

impl<'a, 'b: 'a> MapValue<'b, &'a i32> for i32 {
    fn map_value<C>(&'b self, map_func: fn(C, &'a i32), context: C) {
        map_func(context, self);
    }
}
1 Like

Because 'a is a parameter of the trait, its region necessarily extends beyond the end of the map_value function. With the declaration you've provided, I should be able to call it like this:

fn f(x: &'static i32) {
    MapValue::<'static, &'static i32>::map_value(x, |_,_| (), ());
}

Thanks, but as I tried to indicate in the comments inside the code, that is a separate case that I need to support and that works, but for some types I need to do a conversion of the type before calling map_func, I just used a copy to emulate that situation.

Ah I see, thanks. Any idea how I could express my intentions correctly in the declaration?

Not without more context of what your actual application is. In general, it's extremely hard to abstract over owned value vs. shared borrow vs. exclusive borrow in a single definition.


Edit: One option is to always pass a reference into the mapping function:

pub trait MapValue<Out> {
    fn map_value<C>(&self, map_func: fn(C, &Out), context: C);
}

impl MapValue<i32> for i32 {
    fn map_value<C>(&self, map_func: fn(C, &i32), context: C) {
        map_func(context, self);
    }
}

impl MapValue<i64> for i32 {
    fn map_value<C>(&self, map_func: fn(C, &i64), context: C) {
        let cpy = *self as i64; // Something more useful happens here in the real code
        map_func(context, &cpy);
    }
}

Basically I am writing a proc macro that needs to take a Rust object, iterate through all its fields and call corresponding setter functions on a C++ object. The thing is that for some types I need to do a conversion first before I can call the setter and some of these conversion require that I allocate a C++ object on the Rust stack. Because I can't move those objects I can't return them so I can't have a set of "conversion functions". My plan is thus to use this trait instead that allocates on the stack and calls a passed in fn to call the setters. This should allow me to handle all possible cases without introducing special case handling into the macro itself.

I have previously tried what you suggest by always passing a reference, the issue is then that it cannot handle setter functions that do not expect references, i.e. those for simple types like i32.

Since you're doing unsafe things anyway (FFI), your best option might be to use raw pointers instead of references for this: The compiler won't try to enforce any lifetime rules, which puts the burden on you to ensure there are never any uses-after-free.

Yeah I could force it to work with raw pointers and unsafe, but it feels like this should be possible in safe Rust and that there is a gap in my understanding of the language that I want to fix. Ideally I want map_func to accept a reference whenever I call .map_value on something that is a reference and a value otherwise. The thing is that the lifetime of what goes into the map_func isn't necessarily related to the lifetime of what .map_value was called on, it just needs to live a long as the call to map_func.

The thing to realize here is that there are an infinite number of different reference types for each target type: one for every lifetime. There are tools to relate these to each other, like HRTB's (for<'a> ...), but I don't think there's any way to use those tools in a context where you might not have a reference at all.

Oh I see, thanks. Well if what I want to do isn't possible to express in safe Rust then I guess using unsafe should be fine as its pretty easy to explain why what I want to do is sound.

Edit: I will probably have to mark the trait as unsafe though, because I guess external users could then pass in dangerous map_value fns if I do this

I haven't played around with GATs much, but you could do something like this (nightly-only). The associated type might cause you headaches when you try to actually use it, though.

#![feature(generic_associated_types)]

pub trait MapValue<Out> {
    type MapFn<C>;
    fn map_value<C>(&self, map_func: Self::MapFn<C>, context: C);
}

impl MapValue<i32> for i32 {
    type MapFn<C> = fn(C, i32);
    fn map_value<C>(&self, map_func: fn(C, i32), context: C) {
        map_func(context, *self);
    }
}

impl<'a> MapValue<&'a i32> for i32 {
    type MapFn<C> = for<'b> fn(C, &'b i32);
    fn map_value<C>(&self, map_func: fn(C, &i32), context: C) {
        let cpy = *self; // Something more useful happens here in the real code
        map_func(context, &cpy);
    }
}

Ah cool thanks, haven't heard about GATs. I'll have a look, but I probably won't use it for now anyway as I don't want to be nightly-only.

Actually, you can get rid of the associated type if you are willing to lift C to be a parameter of the trait:

pub trait MapValue<C, F> {
    fn map_value(&self, map_func: F, context: C);
}

impl<C> MapValue<C, fn(C, i32)> for i32 {
    fn map_value(&self, map_func: fn(C, i32), context: C) {
        map_func(context, *self);
    }
}

impl<C> MapValue<C, fn(C, &i32)> for i32 {
    fn map_value(&self, map_func: fn(C, &i32), context: C) {
        let cpy = *self; // Something more useful happens here in the real code
        map_func(context, &cpy);
    }
}

Oh, okay that is pretty interesting and seems to work. Awesome, thanks!

GATs are pretty close to stabilization, BTW.

1 Like

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.