Felk
Felk

Reputation: 8224

Lifetime issues: return struct containing reference to local closure

I am attempting to model some program state as Mutables from the futures-signals library, whose value I want to set generically from a serde_json Value identified by some string key.

For example, given I received some payload instructing me to update "my_int" with a Value, I want to be able to set the value of the Mutable that is known as "my_int".

My idea was to have a map from identifiers like "my_int" to a non-templated wrapper around a mutable's setter. It is important that said wrapper is non-templated, because otherwise I couldn't hold a collection of them in one map:

let my_int = Mutable::new(123);
let my_str = Mutable::new("asdf");
// ...

let setters = HashMap::from([
    ("my_int", /* magic wrapper around setter here somehow */),
    ("my_str", /* magic wrapper around setter here somehow */),
    // ...
]);

let property_name = "my_int"; // constant for demo purposes
let value = Value::from(234); // constant for demo purposes
let setter = setters.get(property_name).unwrap();
(setter.deser_and_set)(value);

Right now said magic wrapper looks like this:

struct SetterWrapper<'a> {
    name: &'static str,
    deser_and_set: &'a dyn Fn(Value) -> Result<(), Error>,
    // + some other unrelated fields
}

And I can create those inline, and it works:

let my_int_setter = SetterWrapper {
    name: "my_int",
    deser_and_set: &(|v: Value| {
        my_int.set(serde_json::from_value(v)?);
        Ok(())
    }),
    // + some other unrelated fields
};

But I have many mutables and don't want to repeat the above code for every one of them, so I attempted to put it into a function:

fn wrap_setter<'a, T>(name: &'static str, mutable: &'a Mutable<T>) -> SetterWrapper<'a>
    where T: for<'de> Deserialize<'de>
{
    let deser_and_set = |v: Value| {
        mutable.set(serde_json::from_value::<T>(v)?);
        Ok(())
    };
    SetterWrapper {
        name,
        deser_and_set: &deser_and_set,
    }
}

which I intend to use like let my_int_setter = wrap_setter("my_int", &my_int);, however I am encountering the following error:

error[E0515]: cannot return value referencing local variable `deser_and_set`
  --> src\main.rs:66:5
   |
66 | /     SetterWrapper {
67 | |         name,
68 | |         deser_and_set: &deser_and_set,
   | |                        -------------- `deser_and_set` is borrowed here
69 | |     }
   | |_____^ returns a value referencing data owned by the current function

The error itself makes sense to me: of course I can't return references to local variables, as those would dangle. But I believe conceptually I could solve the issue by somehow marking the closure in the function to have the same lifetime as the mutable, namely 'a, but you cannot give variables lifetime annotations.

How can I solve this issue? Or is my approach already clumsy?

Upvotes: 0

Views: 161

Answers (2)

Akida
Akida

Reputation: 1116

Probably the answer from @Joe_Jingyu is cleaner but I want to point out a second way you could take:

make SetterWrapper a trait and implement it for Mutable:

trait SetterWrapper {
    fn deser_and_set(&self, v: Value) -> Result<(), Error>;
}

impl<T> SetterWrapper for Mutable<T>
where
    T: for<'de> serde::de::Deserialize<'de>,
{
    fn deser_and_set(&self, v: Value) -> Result<(), Error> {
        self.set(serde_json::from_value::<T>(v)?);
        Ok(())
    }
}

Now you can create the HashMap with the trait objects and set the value:

let setters = HashMap::from([
    ("my_int", &my_int as &dyn SetterWrapper),
    ("my_str", &my_str),
]);

let property_name = "my_int"; // constant for demo purposes
let value = Value::from(234); // constant for demo purposes
let setter = setters.get(property_name).unwrap();
// now the call can be direct
setter.deser_and_set(value).unwrap();

Playground link (Note: I have build a simple Mutable myself, just to make the example work)

Upvotes: 1

Joe_Jingyu
Joe_Jingyu

Reputation: 1279

To work around the issue, one way I can think of is change the property deser_and_set to a Box from a reference. With that, the ownership of the Box can be moved out of the function. Give it a try.

struct SetterWrapper {
    name: &'static str,
    deser_and_set: Box<dyn Fn(Value) -> Result<(), Error>>,
    // + some other unrelated fields
}

fn wrap_setter<T>(name: &'static str, mutable: &Mutable<T>) -> SetterWrapper
    where T: for<'de> Deserialize<'de>
{
    SetterWrapper {
        name,
        deser_and_set: Box::new(|v: Value| {
            mutable.set(serde_json::from_value::<T>(v)?);
            Ok(())
        };),
    }
}

Upvotes: 1

Related Questions