Can I improve this code using lifetimes?


#1

Hello everybody! I’ve written my first few (more or less) real lines of code in Rust today and I have some doubts on them. I’ve posted this question on Code Review as well, feel free to answer wherever you want.

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:

  1. the warning suppression before the implementation of the Display trait for Json: I tried aggregating the operations in Vectors and then folding them together but the resulting code looked unnecessarily garbled, so I just ignored the Results; is there a better way to do this?

  2. In the Display implementation of JsonVal I use the format! 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 the write! 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 the to_string method go out of scope after the pattern matching. Is there a way out of this, perhaps using lifetimes?

  3. In the Display implementation of JsonVal I use the String::from associated function to return the value of JSON’s null. However, if I’m not mistaken, this would mean that I would be creating a new instance of the String every time I have to format a null. A str, being 'static (again, if I’m not mistaken), would solve the problem. Is the something like a static String or a way to be more efficient, perhaps using (again) lifetimes?

Also, I put all the refs 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. :wink:

You can find an updated version of this code on my Github repo.

Thank you in advance!


#2

I think with the try! macro the code would not be much longer.

Are you optimizing for readability or for performance?

For readability, look into join.

For performance, you can rearrange fmt for JsonVal roughly like this, to avoid extra allocations in every branch:

match *self {
    JsonVal::Str(ref s) => s.fmt(f),
    JsonVal::Null => "null".fmt(f),
    ....
}

#3

Hi @matklad, thanks for the tips!

I’ve put the try! macros you suggested.

Yes, delegation! So simple, thanks a lot!

OT: you work on the Rust plugin for IntelliJ, right? I hope I can be of some help as soon as I get more familiar with the language, I like Vim but I prefer IntelliJ. :smiley:


#4

The IntelliJ plugin for Rust is developed in Kotlin.
It’s a pretty darn nice programming language that to me somewhat resembles Rust(pattern matching, expression based… both &borrow a lot of features from functional programming languages) + just a very little bit of shell scripting.
I feel like if you know Rust it might be easier to get into Kotlin but you pretty much need to know Java…

Update: Btw, I found about Kotlin through this IntelliJ plugin. It was something like this:
This Week in Rust talks about the IDE page -> IDE page links an old plugin for IntelliJ -> that plugin’s page redirects in the README.md to this new and well maintained plugin -> “What language is that? Kotlin? Let me search for it” -> Played around with “In browser compiler” -> “Oh nice. Wait a second, this looks like an expression based language, let me try something like Rust’s let var = match something { arms…}. Awesome, I don’t like Java but I Love this!”


#5

Do try Idea Vim plugin (it even comes bundled with IDEA) :wink:


#6

I’m a Java/Scala developer by day. :wink: But I feel I need to get a good understanding of Rust in order to be of some help to the project (maybe). Thanks for the reply!


#7

I prefer IntelliJ for editing as well, actually. :smiley: I’m not a great fan of modal editing.