Basic JSON Representation in Rust
There are no tests. That means that all of the changes I show compile, but may not do what I say. Rust makes is very easy to write basic tests, so I'd encourage you to write them. Especially as you are learning a new language and especially when you ask for feedback or refactor the code.
You should never ignore Results. If you don't believe it's possible for an error to occur, use unwrap to cause the program to abort on error. Even better would be to use try! to return an error in case of error.
Result::Ok(()) is duplicated at the end of each method; move it after the match.
Result::Ok is imported via the prelude, you can just say Ok.
Multiple consecutive fmt calls can be combined with the write! macro and a format string.
Instead of using to_string, embed the write! call inside the match arms. Then there are no additional heap allocations.
Consider dealing with the middle commas in a different way. Pop off the first element, then unconditionally add the comma for all subsequent ones. Theres a possibility of performance difference here, so you should test.
Consider using an Option instead of creating a Null type. Option is a pervasive type from the standard library and people innately know how to use it.
I think you will have a bug if a string contains embedded quotes, as there's no escaping happening.




use std::fmt;

pub enum Json {
    Obj(Vec&lt;(String, JsonVal)&gt;),
    Arr(Vec&lt;JsonVal&gt;),
}

pub enum JsonVal {
    Str(String),
    Num(f64),
    Composite(Json),
    Bool(bool),
    Null,
}

impl fmt::Display for Json {
    fn fmt(&amp;self, f: &amp;mut fmt::Formatter) -&gt; fmt::Result {
        match *self {
            Json::Obj(ref obj) =&gt; {
                try!("{".fmt(f));
                let mut props = obj.iter();
                if let Some(prop) = props.next() {
                    try!(write!(f, r#""{}":{}"#, prop.0, prop.1));
                }
                for prop in props {
                    try!(write!(f, r#","{}":{}"#, prop.0, prop.1));
                }
                try!("}".fmt(f));
            }
            Json::Arr(ref arr) =&gt; {
                try!("[".fmt(f));
                let mut items = arr.iter();
                if let Some(item) = items.next() {
                    try!(item.fmt(f));
                }
                for item in items {
                    try!(write!(f, ",{}", item));
                }
                try!("]".fmt(f));
            }
        }

        Ok(())
    }
}

impl fmt::Display for JsonVal {
    fn fmt(&amp;self, f: &amp;mut fmt::Formatter) -&gt; fmt::Result {
        match *self {
            JsonVal::Str(ref string) =&gt; write!(f, r#""{}""#, string),
            JsonVal::Num(number) =&gt; number.fmt(f),
            JsonVal::Composite(ref json) =&gt; json.fmt(f),
            JsonVal::Bool(boolean) =&gt; boolean.fmt(f),
            JsonVal::Null =&gt; "null".fmt(f),
        }
    }
}

fn main() {

}





  the warning suppression before the implementation of the Display trait


We mentioned this one already; good call on thinking that something was wonky here.


  In the Display implementation of JsonVal I use the format! macro to basically make a copy of the string
  In the Display implementation of JsonVal I use the String::from associated function to return the value of JSON's null. 


Again, good call on both of these points. Creating an allocated object is sometimes the right thing, but in this case we can just delegate to the existing Display implementations of various types. In case you weren't aware, the {} corresponds to items that implement Display. That's what makes it possible to just call out to those.


  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


Yes, this is the right intuition. When formatting something, you don't have ownership of the item, only a reference. That means you can't take ownership of a struct member, only further references. The ref keyword in a pattern match is conceptually equivalent to taking another reference on the right side:

struct Foo(String);
let f = Foo("hello".to_string());

let String(ref s) = f;
let s = &amp;f.0; // equivalent


The problem is that you can't always write code like the second case, like in a multi-way pattern match or in one arm of a match, so the ref keyword is needed.