【问题标题】:Read Json in Rust在 Rust 中读取 Json
【发布时间】:2014-12-07 19:10:41
【问题描述】:

我正在尝试用 Rust 读取 Json 文件的内容。此文件包含平面中的点数组,例如 [[1, 2], [3.5, 2.7], [0, -2.1]]

我第一次尝试实现是

extern crate serialize;
use serialize::json;
use std::io::File;

fn read_points() -> Vec<[f64, ..2]> {
  let contents = File::open(&Path::new("../points.json")).read_to_string().unwrap();
  let points: Vec<Vec<f64>> = json::decode(contents.as_slice()).unwrap();

  points.iter().map(|&v| [v[0], v[1]]).collect::<Vec<[f64, ..2]>>()
}

现在我有两个问题。

首先,我得到一个编译错误

error: cannot move out of dereference of `&`-pointer

这似乎暗示我的地图操作不安全。作为 Rust 的新手,&amp;v 驻留在内存中的位置对我来说并不明显。理想情况下,我想访问支持Vec&lt;f64&gt; 的底层数组,或者更好的是,避免将内部向量分配给直接读取json 到Vec&lt;[f64, ..2]&gt;

第二个 - 但不太重要 - 有两个丑陋的 unwrap 电话。现在,我知道读取文件和解析 json 都可能失败。有没有办法轻松组合Result 实例,例如Scala 中的flatmap 或Haskell 中的bind?更好的是,类似于 do 符号?

【问题讨论】:

    标签: json rust


    【解决方案1】:

    对于第二个问题,是的,Result 上有 and_then() 方法,但我担心它在这里不起作用,因为错误类型不同:read_to_string() 返回Result&lt;String, IoError&gt;json::decode()返回Result&lt;T, DecoderError&gt;,你不能只是组合它们——在Rust中没有描述类型联合的通用方法,所以你不能表达组合的错误类型。

    有计划减轻错误处理,thisthis RFC 涵盖了这些问题,因此这种情况可能会在未来得到改善。

    现在是主要问题的答案。

    由于您同时在闭包参数中使用iter() 和取消引用模式,因此您会收到关于退出取消引用的编译错误。此方法返回一个迭代器,该迭代器将 references 生成到向量中 - 满足Iterator&lt;&amp;Vec&lt;f64&gt;&gt; bound。这意味着您无法通过此迭代器将值从向量中移出,因为无法将值移出引用。

    但是,&amp;v 模式意味着v 应该从引用中移出,也就是这个闭包:

    |&v| [v[0], v[1]]  // v is Vec<f64>
    

    相当于这个:

    |r| {              // r is &Vec<f64>
        let v = *r;    // v is Vec<f64>
        [v[0], v[1]]
    }
    

    这种模式只适用于隐式可复制的类型,如int,或解构枚举/元组/等,但Vec&lt;T&gt; 不可隐式复制,因为它有一个析构函数,并且此处不会发生解构。

    首先,您可以将&amp; 完全保留在&amp;v 中(您也不需要为collect() 指定类型参数,因为它会从函数返回类型中推断出来):

    points.iter().map(|v| [v[0], v[1]]).collect()
    

    您可以这样做的原因是索引运算符被转换为自动取消引用其目标的特征方法调用。

    但是,如果您使用into_iter() 而不是iter(),您会更好地表达意图:

    points.into_iter().map(|v| [v[0], v[1]]).collect()
    

    into_iter() on Vec&lt;T&gt; 返回一个迭代器,它产生T,而不是像iter() 那样的&amp;T,所以这里的v 将是Vec&lt;f64&gt; 类型。 into_iter() 消耗它的目标,但是由于在此调用之后没有使用 points,因此这样做是安全的,并且它更好地表达了 points 被转换为结果的事实。

    但还有更好的方法。 JSON 解码器不支持反序列化像 [f64, ..2] 这样的静态大小的数组,因为它需要支持泛型参数中的数字,而 Rust 还没有它们。但是您始终可以编写自己的类型并为其实现Decodable

    extern crate serialize;
    
    use serialize::{Decoder, Decodable};
    use serialize::json;
    
    #[deriving(Show)]
    struct Point(f64, f64);
    
    impl Decodable for Point {
        fn decode<D: Decoder>(d: &mut D) -> Result<Point, D::Error> {
            d.read_tuple(2, |d| {
                d.read_tuple_arg(0, |d| d.read_f64()).and_then(|e1|
                    d.read_tuple_arg(1, |d| d.read_f64()).map(|e2|
                        Point(e1, e2)
                    )
                )
            })
        }
    }
    
    fn main() {
        let s = "[[1, 2], [3.5, 2.7], [0, -2.1]]";
        let v: Vec<Point> = json::decode(s).unwrap();
        println!("{}", v);
    }
    

    (试试看here

    现在,如果您需要 [f64, ..2],您可以随时向 Point 结构添加一个方法,该结构将为您构建它。

    不幸的是,DecodableDecoder 现在的文档确实不足,因此当您尝试实现它们时,您必须依靠常识并检查 rustc --pretty=expanded 输出。

    编辑使用最新的 Rust 版本更新

    【讨论】:

    • Decoder/Decodable 实现自 10 月以来发生了一些变化。 IRC 上的 Yurume 为我提供了这个 is.gd/rCRh9e,它已更新为在 Alpha 版本上运行。
    • @VladimirMatveev 感谢您花时间这样做。由于 Rust 是一个不断变化的目标,因此更新此类问题的答案真的很有帮助!
    猜你喜欢
    • 2022-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-26
    • 1970-01-01
    • 2021-11-10
    • 1970-01-01
    相关资源
    最近更新 更多