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?
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:
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.
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);
}
}