我正在尝试通过实现log crate来实现一个简单的记录器。
记录器的行为应如下:
[1] First log message
[2] Second log message
[3] Third log message
要实现这一点,我有我的记录器结构
struct SeqLogger {
seq: i64,
}
并实现Log
特质
fn enabled(&self, metadata: &Metadata) -> bool
fn log(&self, record: &Record)
fn flush(&self)
在log(&self, record: &Record)
实现中,我会做
fn log(&self, record: &Record) {
println!("[{}] {}", self.seq, record.args());
self.seq = self.seq + 1;
}
但是,编译器抱怨self
不可更改。我是否以正确的方式来实现这一目标?没有&mut self
的情况下如何更新记录器的状态?
答案 0 :(得分:7)
似乎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!(); }
}
现在它可以编译了。
Playground with the last variant
编辑:根据评论,由于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--
此外,由于我们的状态只是一个整数,我们可以使用a standard atomic type而不是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--
}