如何为自定义记录器设置内部状态?

kkp*_*oon 6 rust

我正在尝试通过实现日志包来实现一个简单的记录器.

记录器的行为应如下所示:

[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

Cer*_*rus 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!(); }

}
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是不稳定的,因此您可能希望使用AtomicIsizeAtomicUsize替代:

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)

操场