While I understand basically what str
and std::string::String
are and how they relate to each other, I find it a bit cumbersome to compose strings out of various parts without spending too much time and thought on it. So as usual I suspect I did not see the proper way to do it yet, which makes it intuitive and a breeze.
let mut s = std::string::String::with_capacity(200);
let precTimeToJSON = | pt : prectime::PrecTime, isLast : bool | {
s.push_str(
"{ \"sec\": "
+ &(pt.sec.to_string())
+ " \"usec\": "
+ &(pt.usec.to_string())
+ if isLast {"}"} else {"},"})
};
The code above is honored by the compiler with error messages like:
src\main.rs:25:20: 25:33 error: binary operation +
cannot be applied to type &'static str
[E0369]
And even after half an hours worth of fiddling and randomly adding &
, I could not make this compilable. So, here my questions:
- What do I have to write to achieve the obvious?
- What is the "standard" way to do this in Rust?
The Rust compiler is right (of course): there's no +
operator for string literals.
I believe the format!()
macro is the idiomatic way to do what you're trying to do. It uses the std::fmt
syntax, which essentially consists of a formatting string and the arguments to format (a la C's printf
). For your example, it would look something like this:
let mut s: String = String::new();
let precTimeToJSON = | pt : prectime::PrecTime, isLast : bool | {
s = format!("{{ \"sec\": {} \"usec\": {} }}{}",
pt.sec,
pt.usec,
if isLast { "" } else { "," }
)
};
Because it's a macro, you can intermix types in the argument list freely, so long as the type implements the std::fmt::Display
trait (which is true for all built-in types). Also, you must escape literal {
and }
as {{
and }}
, respectively. Last, note that the format string must be a string literal, because the macro parses it and the expanded code looks nothing like the original format!
expression.
Here's a playground link to the above example.
Two more points for you. First, if you're reading and writing JSON, have a look at a library such as rustc-serialize. It's much less painful!
Second, if you just want to concatenate &'static str
strings (that is, string literals), you can do that with zero run-time cost with the concat!()
macro. It won't help you in your case above, but it might with other similar ones.
Itertools::format
can help you write this as a single expression if you really want to.
let times: Vec<PrecTime>; // iterable of PrecTime
let s = format!("{}", times.iter().format(",", |pt, f|
f(&format_args!(r#"{{ "sec": {}, "usec": {} }}"#, pt.sec, pt.usec))
));
format()
uses a separator, so just specify ","
there (or ""
if you need no separator). It's a bit involved so that the formatting can be completely lazy and composable. You receive a callback f
that you call back with a &Display
value (anything that can be Display formatted).
Here we demonstrate this great trick of using &format_args!()
to construct a displayable value. This is something that comes in handy if you use the debug builder API as well.
Finally, use a raw string so that we don't need to escape the inner "
in the format: r#"{{ "sec": {} "usec": {} }}"#
. Raw strings are delimited by r#"
and "#
(free choice of number of #
).
Itertools::format()
uses no intermediate allocations, it is all directly passed on to the underlying formatter object.
You can also do this madness:
fn main() {
let mut s = std::string::String::with_capacity(200);
// Have to put this in a block so precTimeToJSON is dropped, see https://doc.rust-lang.org/book/closures.html
{
// I have no idea why this has to be mut...
let mut precTimeToJSON = |sec: u64, usec: u64, isLast: bool| {
s.push_str(&( // Coerce String to str. See https://doc.rust-lang.org/book/deref-coercions.html
"{ \"sec\": ".to_string() // String
+ &sec.to_string() // + &str (& coerces a String to a &str).
+ " \"usec\": " // + &str
+ &usec.to_string() // + &str
+ if isLast {"}"} else {"},"} // + &str
));
};
precTimeToJSON(30, 20, false);
}
println!("{}", &s);
}
Basically the operator String + &str -> String
is defined, so you can do String + &str + &str + &str + &str
. That gives you a String
which you have to coerce back to a &str
using &
. I think this way is probably quite inefficient though as it will (possibly) allocate loads of String
s.