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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With