This week I started reading "The Rust Programming Language". As I reached the chapters on enumerations and pattern matching I felt I had enough material to put together a simple representation of JSON in Rust, just to play around and get a better feeling of the language.
This is what I came up with:
use std::fmt;
pub enum Json {
Obj(Vec<(String, JsonVal)>),
Arr(Vec<JsonVal>),
}
pub enum JsonVal {
Str(String),
Num(f64),
Composite(Json),
Bool(bool),
Null,
}
#[allow(unused_must_use)]
impl fmt::Display for Json {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Json::Obj(ref obj) => {
"{".fmt(f);
for (n, prop) in obj.iter().enumerate() {
if n != 0 {
",".fmt(f);
}
"\"".fmt(f);
prop.0.fmt(f);
"\":".fmt(f);
prop.1.fmt(f);
}
"}".fmt(f);
Result::Ok(())
}
Json::Arr(ref arr) => {
"[".fmt(f);
for (n, item) in arr.iter().enumerate() {
if n != 0 {
",".fmt(f);
}
item.fmt(f);
}
"]".fmt(f);
Result::Ok(())
}
}
}
}
impl fmt::Display for JsonVal {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,
"{}",
match *self {
JsonVal::Str(ref string) => format!("\"{}\"", string),
JsonVal::Num(number) => number.to_string(),
JsonVal::Composite(ref json) => json.to_string(),
JsonVal::Bool(boolean) => boolean.to_string(),
JsonVal::Null => String::from("null"),
})
}
}
The things that "smell" a little bit, in my humble opinion, are three:
The warning suppression before the implementation of the
Display
trait forJson
: I tried aggregating the operations inVector
s and then folding them together but the resulting code looked unnecessarily garbled, so I just ignored theResult
s. Is there a better way to do this?In the
Display
implementation ofJsonVal
I use theformat!
macro to basically make a copy of the string (a "technique" which smells by itself); however, as I'm not touching the string in any way, it would be great to just hand out the string itself to thewrite!
macro; returning*string
wouldn't make sense as I would be trying to perform a move on a borrowed reference, but just returning the reference would mean that in other branches I'd have to return a reference as well, which gets rejected because the result of the calls to theto_string
method go out of scope after the pattern matching. Is there a way out of this, perhaps using lifetimes?In the
Display
implementation ofJsonVal
I use theString::from
associated function to return the value of JSON'snull
. However, if I'm not mistaken, this would mean that I would be creating a new instance of theString
every time I have to format anull
. Astr
, being'static
(again, if I'm not mistaken), would solve the problem. Is the something like a staticString
or a way to be more efficient, perhaps using (again) lifetimes?
Also, I put all the ref
s because the compiler hinted me to do so and I understand more or less that it has to do with the fact that I'm just borrowing what's inside the struct to use it, but it's not completely clear to me. However, I still have to encounter the ref
keyword on the book, so don't bother explaining if you feel like I'm better off RTFM.
You can find an updated version of this code on my Github repo.