在函数中返回由 serde_json 创建的结构

Returning a struct created by serde_json in a function

我被一个看似简单的问题困住了。我得到 为什么 我看到错误但似乎无法解决它。显然我遗漏了一些基本的东西。

fn terraform_deploy_info<'a>(app: &'a MyApp) -> std::result::Result<&MyAppDeployInfo, Error> {
    let terraform = process::Command::new("terraform")
          // We are querying output values.
          .arg("output")
          // We want it in json format for easy processing.
          .arg("-json")
          .output()
          .expect("failed to execute terraform");

    let output = String::from_utf8_lossy(&terraform.stdout);
    let data: TerraformOutputs = serde_json::from_str(&output).unwrap();

    let m = data.deploy_info.value.iter().filter(|&x| x.app == "myapp").collect::<Vec<_>>();

    if m.len() > 1 {
        return Err(Error::MultipleDeployInfo);
    }

    match m.get(0) {
        Some(&x) => Ok(x),
        None => Err(Error::NoDeployInfo),
    }
}

我得到的错误是:

borrowed value must be valid for the lifetime 'a as defined on the body at

这对我来说很有意义,因为我在函数中创建结构并return借用引用,当函数完成时它当然会消失。

但是,当我将 return 类型更改为 std::result::Result<MyAppDeployInfo, Error>(也就是说,不是 return 引用)我可以'似乎 Ok(x) 无法正常工作...我收到错误消息:

expected struct `MyAppDeployInfo`, found reference

同样,这是有道理的,因为 serde_json 创建一个结构,然后我遍历引用,所以当我索引到集合中时,我正在查看一个引用。

所以我尝试了各种方法来获取结构值,例如取消引用、Box::newclone()to_owned() 等,但仍然无法让它工作。

我已经在这里搜索了所有问题,阅读了这本书等,但我仍然不清楚如何解决这个问题...任何指点将不胜感激。

在不了解更多关于您的项目的情况下(请下次制作一个 MCVE),我会说您可以将 .iter() 调用更改为 .into_iter()。我不会收集到 Vec 然后使用 get,而是直接使用迭代器:

let m = data.deploy_info.value.into_iter().filter(|&x| x.app == "myapp").fuse();

match (m.next(), m.next()) {
    (None, None) => Err(Error::NoDeployInfo),
    (Some(x), None) => Ok(x),
    (Some(_), Some(_)) => Err(Error::MultipleDeployInfo),
    (None, Some(_)) => panic!("Iterator::fuse broken"),
}

观察您的代码段的类型。

let m = data.deploy_info.value // value is a Vec<MyAppDeployInfo>
    .iter() // returns a Iterator<Item=&MyAppDeployInfo>
    .filter(|&x| x.app == "myapp")
    .collect::<Vec<_>>(); // collects into a Vec<&MyAppDeployInfo>

if m.len() > 1 {
    return Err(Error::MultipleDeployInfo);
}

match m.get(0) { // get() returns a reference to an element
                 // i.e. a &&MyAppDeployInfo
        Some(&x) // pattern match says x : &MyAppDeployInfo
            => Ok(x), // which matches the return type
                      // but you get a borrowing error.
        None => Err(Error::NoDeployInfo),
    }
}

现在,如果您将 return 类型更改为 Result<MyAppDeployInfo, Error>,您会遇到类型不匹配的问题,因为 x 是引用。如果取消引用 x,则会收到错误 "cannot move out of borrowed content",因为 MyAppDeployInfo 不是 Copy,并且您正在尝试移动。如果你写 x.clone(),它应该可以工作,除非你更改了其他内容?

或者,您可以从一开始就移动内容。如果你写 data.deploy_info.value.into_iter().filter(|x| x.app == "myapp"),你将移出初始结构而不是复制它。然后生成的 Vec 将具有 MyAppDeployInfo 作为其项目类型。然后你可以使它成为 mut 并使用 pop() 而不是 get(0) 以一种你可以移出的方式获得唯一的元素。

或者你可以按照@ker 的建议去做,而不是首先使用 collect()。不过,我仍然会切换到 into_iter(),将其设为最终代码:

fn terraform_deploy_info(app: &MyApp) // no explicit lifetime needed
        -> std::result::Result<MyAppDeployInfo, Error> {
    let data = // ...

    let mut m = data.deploy_info.value.into_iter()
        .filter(|x| x.app == "myapp").fuse();

    match (m.next(), m.next()) {
        (None, None) => Err(Error::NoDeployInfo),
        (Some(x), None) => Ok(x),
        (Some(_), Some(_)) => Err(Error::MultipleDeployInfo),
        (None, Some(_)) => panic!("Iterator::fuse broken"),
    }
}