简体   繁体   中英

Read Json in Rust

I am trying to read the contents of a Json file in Rust. This file contains an array of points in the plane, like [[1, 2], [3.5, 2.7], [0, -2.1]] .

My first attempt at an implementation is

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]>>()
}

Now I have two problems with this.

First, I get a compile error

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

which seems to imply that my map operation is not safe. Being a complete newbie to Rust, it is not obvious to me where &v resides in memory. Ideally, I would like to access the underlying array backing the Vec<f64> , or even better, avoid to allocate the inner vectors an read directly the json to a Vec<[f64, ..2]> .

Second - but less important - there are those two ugly unwrap calls. Now, I understand that both reading a file and parsing json may fail. Is there a way to easily combine Result instances, such as flatmap in Scala or bind in Haskell? Even better, something like the do notation?

For your second question, yes, there is and_then() method on Result , but I'm afraid that it won't work here because error types are different: read_to_string() returns Result<String, IoError> while json::decode() returns Result<T, DecoderError> , and you can't just combine them - there is no generic way to describe a union of types in Rust, so you can't express a combined error type.

There are plans to ease working with errors, they are covered by this and this RFCs, so maybe this situation will improve in future.

Now the answer to the main question.

You're getting the compile error about moving out of dereference because you're using iter() and dereference pattern in closure argument at the same time. This method returns an iterator which yields references into the vector - something which satisfies Iterator<&Vec<f64>> bound. This means that you can't move the values out from the vector through this iterator because it is impossible to move values out of a reference.

However, &v pattern means that v should be moved out from a reference, that is, this closure:

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

is equivalent to this one:

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

This pattern is only useful for types which are implicitly copyable, like int , or for destructuring enums/tuples/etc, but Vec<T> is not implicitly copyable because it has a destructor and no destructuring happens here.

First, you can leave & out in &v completely (you also don't need specifying the type parameter to collect() because it will be inferred from function return type):

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

The reason why you can do this is that index operator is translated to trait method call which automatically dereferences its target.

However, you would express the intention better if you use into_iter() instead of iter() :

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

into_iter() on Vec<T> returns an iterator which yields T , not &T like iter() , so v here will be of type Vec<f64> . into_iter() consumes its target, but since points is not used after this call, this is safe to do so and it better expresses the fact that points are transformed to the result.

But there is even better way. JSON decoder does not support deserializing statically sized arrays like [f64, ..2] because it requires supporting numbers in generic parameters, and Rust does not have them yet. But you can always write your own type and implement Decodable for it:

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

(try it here )

Now if you need [f64, ..2] you can always add a method to Point struct which will construct it for you.

Unfortunately, Decodable and Decoder are really underdocumented now so you have to rely on common sense and inspect rustc --pretty=expanded output when you try to implement them.

Edit updated with the latest Rust version

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM