Каков "стандартный" способ конкатенации строк?
В то время как я понимаю, что такое str
и std::string::String
, и как они соотносятся друг с другом, я считаю немного сложным составить строки из разных частей, не тратя слишком много времени и не подумав об этом. Так что, как обычно, я подозреваю, что пока не вижу правильного способа сделать это, что делает его интуитивным и легким.
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 {"},"})
};
Приведенный выше код удостоен компилятора сообщениями об ошибках, например:
src\main.rs: 25: 20: 25:33 ошибка: двоичная операция +
не может применяться к типу &'static str
[E0369]
И даже после того, как полчаса играли и случайно добавляли &
, я не мог сделать это компилируемым. Итак, вот мои вопросы:
- Что я должен написать для достижения очевидного?
- Что такое "стандартный" способ сделать это в Rust?
Ответы
Ответ 1
Компилятор Rust прав (конечно): нет оператора +
для строковых литералов.
Я считаю, что format!()
macro - это идиоматический способ сделать то, что вы пытаетесь сделать. Он использует std::fmt
синтаксис, который по существу состоит из строки форматирования и аргументов для форматирования (a la C printf
). Для вашего примера это выглядит примерно так:
let mut s: String = String::new();
let precTimeToJSON = | pt : prectime::PrecTime, isLast : bool | {
s = format!("{{ \"sec\": {} \"usec\": {} }}{}",
pt.sec,
pt.usec,
if isLast { "" } else { "," }
)
};
Поскольку это макрос, вы можете свободно перемешать типы в списке аргументов, если тип реализует std::fmt::Display
trait ( что верно для всех встроенных типов). Кроме того, вы должны удалить литерал {
и }
как {{
и }}
соответственно. Наконец, обратите внимание, что строка формата должна быть строковым литералом, потому что макрос ее анализирует, а расширенный код не похож на исходное выражение format!
.
Здесь ссылка на игровые площадки в приведенный выше пример.
Еще два момента для вас. Во-первых, если вы читаете и записываете JSON, посмотрите на библиотеку, такую как rustc-serialize. Это гораздо менее болезненно!
Во-вторых, если вы просто хотите объединить строки &'static str
(то есть строковые литералы), вы можете сделать это с нулевой стоимостью во время выполнения с concat!()
. Это не поможет вам в вашем случае выше, но может быть и с другими подобными.
Ответ 2
Вы также можете сделать это безумие:
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);
}
В основном определяется оператор String + &str -> String
, поэтому вы можете сделать String + &str + &str + &str + &str
. Это дает вам String
, с которым вы должны принудительно вернуться к &str
с помощью &
. Я думаю, что этот путь, вероятно, довольно неэффективен, хотя он (возможно) распределяет нагрузки String
s.
Ответ 3
Itertools::format
может помочь вам написать это как единое выражение, если вы действительно этого хотите.
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()
использует разделитель, поэтому просто укажите ","
там (или ""
, если вам не нужен разделитель). Это немного связано с тем, что форматирование может быть полностью ленивым и сложным. Вы получаете обратный вызов f
, который вы вызываете с помощью значения &Display
(все, что может быть отображено на дисплее).
Здесь мы демонстрируем этот большой трюк использования &format_args!()
для построения отображаемого значения. Это полезно, если вы также используете API-интерфейс отладочного отладки.
Наконец, используйте необработанную строку, так что нам не нужно скрывать внутренний "
в формате: r#"{{ "sec": {} "usec": {} }}"#
. Необработанные строки ограничены r#"
и "#
(свободный выбор числа #
).
Itertools::format()
не использует промежуточных распределений, все они непосредственно передаются базовому объекту форматирования.