Как у меня есть внутреннее состояние для пользовательского регистратора, который принимает только сам? - PullRequest
0 голосов
/ 31 октября 2018

Я пытаюсь реализовать простой регистратор путем реализации ящика для журналов .

Регистратор должен вести себя так:

[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?

1 Ответ

0 голосов
/ 31 октября 2018

Кажется, что 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--
}

Детская площадка

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...