Кажется, что logger
ящик не предназначен для логгеров, чтобы иметь какое-либо внутреннее состояние, поэтому он заставляет их делиться как неизменяемые. Фактически это значительно упрощает задачу, поскольку регистратор обычно должен быть разделен между потоками и использоваться одновременно, а это невозможно с & mut self
.
Однако есть обычный обходной путь: изменчивость интерьера. Существует тип std::cell::Cell
, разработанный специально для этого варианта использования: иметь неизменную ссылку на что-то, что должно быть изменяемым. Ваше внутреннее состояние - просто целое число, поэтому оно Copy
, и мы можем просто попытаться использовать Cell
как есть:
extern crate log; // 0.4.5
use log::*;
use std::cell::Cell;
struct SeqLogger {
seq: Cell<i64>,
}
impl Log for SeqLogger {
fn log(&self, record: &Record) {
println!("[{}] {}", self.seq.get(), record.args());
self.seq.set(self.seq.get() + 1);
}
fn enabled(&self, metadata: &Metadata) -> bool { if false {true} else {unimplemented!()} }
fn flush(&self) { unimplemented!(); }
}
Тем не менее, компилятор снова сразу рассердился:
error[E0277]: `std::cell::Cell<i64>` cannot be shared between threads safely
--> src/lib.rs:9:6
|
9 | impl Log for SeqLogger {
| ^^^ `std::cell::Cell<i64>` cannot be shared between threads safely
|
= help: within `SeqLogger`, the trait `std::marker::Sync` is not implemented for `std::cell::Cell<i64>`
= note: required because it appears within the type `SeqLogger`
Это имеет смысл, поскольку, как я уже говорил, сам регистратор должен быть Sync
, поэтому мы должны гарантировать, что его содержимое также безопасно делиться. В то же время, Cell
не является Sync
- именно из-за внутренней изменчивости, которую мы здесь используем. Опять же, есть обычный способ исправить это - Mutex
:
extern crate log; // 0.4.5
use log::*;
use std::cell::Cell;
use std::sync::Mutex;
struct SeqLogger {
seq: Mutex<Cell<i64>>,
}
impl Log for SeqLogger {
fn log(&self, record: &Record) {
let seq = self.seq.lock().unwrap(); // perhaps replace this with match in production
println!("[{}] {}", seq.get(), record.args());
seq.set(seq.get() + 1);
}
fn enabled(&self, metadata: &Metadata) -> bool { if false {true} else {unimplemented!()} }
fn flush(&self) { unimplemented!(); }
}
Теперь он прекрасно компилируется.
Детская площадка с последним вариантом
РЕДАКТИРОВАТЬ: Согласно комментариям, мы можем удалить один слой косвенности, так как Mutex
предоставляет нам как внутреннюю изменчивость (своего рода), так и способность Sync
. Таким образом, мы можем удалить Cell
и напрямую обратиться к MutexGuard
:
// --snip--
fn log(&self, record: &Record) {
let mut seq = self.seq.lock().unwrap(); // perhaps replace this with match in production
println!("[{}] {}", *seq, record.args());
*seq = *seq + 1;
}
// --snip--
И, кроме того, поскольку наше состояние является целым числом, мы можем использовать стандартный атомарный тип вместо Mutex
. Обратите внимание, что AtomicI64
нестабилен, поэтому вы можете использовать AtomicIsize
или AtomicUsize
вместо:
use std::sync::atomic::{AtomicIsize, Ordering};
struct SeqLogger {
seq: AtomicIsize,
}
impl Log for SeqLogger {
fn log(&self, record: &Record) {
let id = self.seq.fetch_add(1, Ordering::SeqCst);
println!("[{}] {}", id, record.args());
}
// --snip--
}
Детская площадка