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

3

我遇到了一个看似简单的问题。我知道为什么会出现错误,但似乎无法解决。显然我缺少一些基本的东西。

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

这对我来说很有道理,因为我在函数中创建结构并返回一个借用引用,当然,当函数结束时它就会消失。

但是,当我将返回类型更改为std::result::Result<MyAppDeployInfo, Error>(即不返回引用)时,我似乎无法让Ok(x)正常工作...我收到了一个错误:

expected struct `MyAppDeployInfo`, found reference

再次说明,这是有道理的,因为serde_json创建一个结构,然后我通过引用进行迭代,所以当我索引集合时,我在查看一个引用。

因此,我尝试了各种方法来获取结构值,如解除引用、Box::newclone()to_owned()等,但仍无法使其正常工作。

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

2个回答

3

不了解你的项目(下次请提供一个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"),
}

太棒了,谢谢!使用into_iter()而不是使用collect()是我之前遗漏的。现在它可以正常工作了。 - Chris

2
观察代码片段的类型。
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),
    }
}

现在如果你将返回类型更改为Result<MyAppDeployInfo,Error>,就会出现类型不匹配的问题,因为x是一个引用。如果你对x进行解引用,就会出现错误“无法移动借用的内容”,因为MyAppDeployInfo不是Copy,而且你正在尝试移动它。如果你写x.clone(),应该可以工作,除非你改变了其他东西?
另外,你可以从一开始就处理移动内容。如果你写data.deploy_info.value.into_iter().filter(|x| x.app == "myapp"),就会移动出初始结构而不是复制它。然后,生成的Vec将具有MyAppDeployInfo作为其项目类型。然后你可以使它成为mut并使用pop()来获取唯一元素,以便你可以移动出它。
或者你可以像@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"),
    }
}

非常好,感谢您详细的解释!我现在完全明白了。 - Chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接