Подтвердить что ты не робот

Каков "стандартный" способ конкатенации строк?

В то время как я понимаю, что такое 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?
4b9b3361

Ответ 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() не использует промежуточных распределений, все они непосредственно передаются базовому объекту форматирования.