#rust
#Ржавчина
Вопрос:
Предположим, у меня есть простая функция ведения журнала, подобная этой:
pub trait Log {
fn log(amp;mut self, _message: amp;str) {}
}
Я мог бы написать функцию, которая использует его следующим образом:
pub fn output_int(x: i32, log: amp;mut impl Log) {
log.log(amp;format!("Writing value: {}", x));
// Do something with x
}
Однако в этом есть недостаток: несмотря на то, что я использовал статическую отправку и сборку с --release
, вызов format!()
не оптимизирован (godbolt).
Это можно исправить, используя метод в should_log()
стиле, и тогда он компилируется в ничто:
pub trait Log {
fn log(amp;mut self, _message: amp;str) {}
fn should_log(amp;self) -> bool { false }
}
pub fn output_int(x: i32, log: amp;mut impl Log) {
if log.should_log() {
log.log(amp;format!("Writing value: {}", x));
}
// Do something with x
}
struct NopLog {}
impl Log for NopLog {}
pub fn main() {
let mut log = NopLog{};
output_int(3, amp;mut log);
}
Однако это выглядит не очень эргономично. Лучшее решение, которое я могу придумать, — создать макрос, подобный этому:
macro_rules! log {
($log:ident, $($arg:tt)*) => {
if $log.should_log() {
$log.log(amp;format!($($arg)*));
}
};
}
pub fn output_int(x: i32, log: amp;mut impl Log) {
log!(log, "Writing value: {}", x);
// Do something with x
}
Это работает, но все еще кажется немного взломанным. Я просто хотел проверить — я пропустил какое-то очевидное более простое решение для вызова format!()
, в то же время позволяя оптимизировать его до nop?
Комментарии:
1. Как насчет принятия закрытия вместо строки в качестве сообщения, чтобы вы могли писать
log.log(|| amp;format!("Writing value: {}", x))
?2. @SvenMarnach Обратите внимание, что этот конкретный пример не будет работать, поскольку ссылка станет зависшей, как только вернется закрытие. Я бы предложил что-то вроде
impl FnOnce() -> impl Into<Cow<'static, str>>
, чтобы можно было возвращать только литерал и выделенныйString
.3. Причина, по которой это не оптимизировано, заключается в том, что вызов
format
выполняется вoutput_int
, прежде чем быть переданным в качестве аргумента, и его поведение (думаю, паника) наблюдается там, независимо от того, используется ли возвращаемое значение на самом деле.4. @SvenMarnach он также должен был бы понимать, что
format!
является чистым, что требует некоторого мощного глобального анализа, тем более что он вызывает признаки форматирования для произвольных значений.5.
format!()
никогда не оптимизируется AFAIK: godbolt.org/z/KnvzKn . Это потому, чтоalloc::fmt::format()
не оптимизирован полностью. Может быть, стоит открыть проблему по этому поводу? Это не ошибка, но я думаю, что ее можно улучшить.
Ответ №1:
Обычно решением здесь является использование impl Display
or amp;dyn Display
(если вам нужна безопасность объекта) вместо amp;str
. Таким образом, типу присуща лень, и вы можете передать тип, который только лениво выполняет форматирование по требованию. Затем вы можете использовать что-то вроде format_args
или lazy_format
, чтобы фактически создать свою логику отложенного форматирования; оба они создадут непрозрачный объект, который запускает логику форматирования только по требованию.
То есть ваша особенность ведения журнала становится:
pub trait Log {
fn log(amp;mut self, message: impl Display) {}
}
И ваш сайт вызова становится:
pub fn output_int(x: i32, log: amp;mut impl Log) {
log.log(format_args!("Writing value: {x}"));
// Do something with x
}