【问题标题】:Lifetime issues: return struct containing reference to local closure生命周期问题:返回包含对本地闭包的引用的结构
【发布时间】:2021-11-15 11:52:36
【问题描述】:

我正在尝试从futures-signals library 将某些程序状态建模为Mutables,我想从某个字符串键标识的serde_json Value 一般设置其值。

例如,假设我收到了一些负载,指示我用Value 更新"my_int",我希望能够设置Mutable 的值,即"my_int"

我的想法是创建一个从 "my_int" 等标识符到可变设置器周围的 非模板化 包装器的映射。重要的是,所述包装器是非模板化的,否则我无法在一个地图中保存它们的集合:

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);

现在说魔术包装看起来像这样:

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

我可以创建那些内联,而且它有效

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
};

但是我有很多可变变量,不想为每个变量重复上面的代码,所以我尝试将它放入一个函数中:

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,
    }
}

我打算像let my_int_setter = wrap_setter("my_int", &amp;my_int);一样使用它,但是我遇到了以下错误:

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

错误本身对我来说很有意义:当然我不能返回对局部变量的引用,因为它们会悬空。但我相信从概念上讲,我可以通过以某种方式将函数中的闭包标记为与可变的生命周期相同,即'a,来解决这个问题,但你不能给变量生命周期注释。

我该如何解决这个问题?还是我的方法已经笨拙了?

【问题讨论】:

  • 您可以在您的SetterWrapper 中添加Box&lt;dyn Fn(Value) -&gt; Result&lt;(), Error&gt;&gt;,而不是引用。或者将wrap_setter 定义为宏而不是函数。

标签: rust lifetime serde


【解决方案1】:

要解决此问题,我能想到的一种方法是将属性 deser_and_set 从引用更改为 Box。这样,可以将 Box 的所有权移出该功能。试一试。

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(())
        };),
    }
}

【讨论】:

  • 这当然看起来很有希望,但我收到了与生命周期相关的后续错误:the parameter type T`可能活得不够长`。
【解决方案2】:

@Joe_Jingyu 的回答可能更简洁,但我想指出您可以采取的第二种方式:

使SetterWrapper 成为一个特征并为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(())
    }
}

现在您可以使用 trait 对象创建 HashMap 并设置值:

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(注意:我自己构建了一个简单的 Mutable,只是为了让示例工作)

【讨论】:

  • 这似乎工作得很好,谢谢!你能详细说明为什么你认为这是一个不太干净的解决方案吗?当我将 trait 命名为 SetFromJsonValue
  • 在您的 SetterWrapper 中,您有另一个字段 (name)。特征对象不能做到这一点。请记住,Trait 对象有一些限制(这困扰了我几次)。而且动态调度也会增加一点运行时成本。但是,如果您对此感到满意,他们真的很好
猜你喜欢
  • 2021-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多