我正在尝试通过实现日志包来实现一个简单的记录器.
记录器的行为应如下所示:
[1] First log message
[2] Second log message
[3] Third log message
Run Code Online (Sandbox Code Playgroud)
为了实现这一点,我有我的记录器结构
struct SeqLogger {
seq: i64,
}
Run Code Online (Sandbox Code Playgroud)
并实施Log特质
fn enabled(&self, metadata: &Metadata) -> bool
fn log(&self, record: &Record)
fn flush(&self)
Run Code Online (Sandbox Code Playgroud)
在log(&self, record: &Record)实施中,我会这样做
fn log(&self, record: &Record) {
println!("[{}] {}", self.seq, record.args());
self.seq = self.seq + 1;
}
Run Code Online (Sandbox Code Playgroud)
但是,编译器抱怨这self是不可变的.我是以正确的方式实施这个吗?如何在不更新记录器状态的情况下&mut self?
似乎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!(); }
}
Run Code Online (Sandbox Code Playgroud)
但是,编译器立即再次生气:
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`
Run Code Online (Sandbox Code Playgroud)
因为,正如我之前所说,记录器本身必须如此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!(); }
}
Run Code Online (Sandbox Code Playgroud)
现在它编译得很好.
编辑:根据评论,我们可以剥离一层间接,因为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--
Run Code Online (Sandbox Code Playgroud)
而且,由于我们的状态只是一个整数,我们可以使用标准的原子类型而不是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--
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
120 次 |
| 最近记录: |