1

Hopefully this is enough context for the question...

Using Handlebars with Rust, I'm trying to implement a handler to handle this input:

 {{toJSON JSON_OBJ_OR_NONE}}

where JSON_OBJ_OR_NONE is either a valid fragment of JSON like

{
   "abc": 123,
   "def": ["g", "h", "i"],
   "jkl": {
     "m": 1,
     "n": "op"
   }
}

or nothing (an empty string).

What it should return is either a pretty-printed version of the supplied JSON, or "{}" if JSON_OBJ_OR_NONE is empty.

The supplied JSON fragment is completely arbitrary; it could contain any valid JSON, or an empty string. Output should be pretty-printed.

I've tried to implement this in a bunch of different ways, and I'm currently at

handlebars_helper!(toJSON: |json_obj_or_none: str|
if json_obj_or_none.is_empty() {
    "{}"
} else {
    let s = serde_json::to_string_pretty(&json_obj_or_none).is_ok().to_string();
    &s[..]
});

This looks close, but I'm seeing

error[E0597]: `s` does not live long enough
   --> src/main.rs:145:10
    |
145 |         &s[..]
    |         -^----
    |         ||
    |         |borrowed value does not live long enough
    |         borrow later used here
146 |     });
    |     - `s` dropped here while still borrowed

when I compile it

While this seems to be close to a working solution, I suspect there's a much more elegant way of coding it.

monch1962
  • 5,151
  • 5
  • 31
  • 38
  • Possible duplicate of [Proper way to return a new string in Rust](https://stackoverflow.com/questions/43079077/proper-way-to-return-a-new-string-in-rust) – justinas Oct 25 '19 at 07:31
  • Thanks @justinas for helping out this newby... From what I can see in your link, I need to return a (long lived) String. That makes sense, but I'm using a macro function call (handlebars_helper!) so I don't think I can allocate a String::new() inside the macro – monch1962 Oct 25 '19 at 07:51
  • Having looked at the [source of handlebars_helper](https://docs.rs/handlebars/1.1.0/src/handlebars/macros.rs.html#24-73) all it does with your `if .. else .. {}` is bind it to variable and then return it. All of this happens inside the body of a function generated by the macro. So you should absolutely be able to allocate. – justinas Oct 25 '19 at 08:43
  • 1
    The only point is you need to allocate in both branches of the `if`, so you also need to change `"{}"` into `"{}".into()` – Jmb Oct 25 '19 at 12:07

2 Answers2

2

Just to wrap this up, I eventually got it working as follows:

handlebars_helper!(toJSON: |json_obj_or_none: object|
if json_obj_or_none.is_empty() {
    "{}".into()
} else {
    let s = serde_json::to_string_pretty(&json_obj_or_none).unwrap_or_else(|_| "{}".to_string());
    s
});
monch1962
  • 5,151
  • 5
  • 31
  • 38
1

Code like:

{
   let s = something;
   &s
}

is almost always a mistake in Rust. References always borrow from something, and references can't exist after that thing is destroyed. Local variables are destroyed at the end of their scope.

So what this means is:

  1. Make s
  2. Borrow from s
  3. Destroy s and its content
  4. Return a reference to that thing, which has just been destroyed

So you need to pass ownership:

{
   let s = something;
   s // not borrowed!
}

or ensure that s isn't destroyed to soon by moving let to a scope level up:

let s;
{
   s = something;
   &s
   // `s` isn't destroyed here, because `let s` was outside `{}`
}
// `s` and `&s` are destroyed now

If you mix it with string literals in if/else, Rust will complain that String and &str are different. Use String::from("") for the literal, or see Cow type that holds both. Rust needs to know which strings to free (String) and which it must never free (&str), that's why there are two types.

Kornel
  • 97,764
  • 37
  • 219
  • 309