Rust での Singleton パターンの適用方法について。
Singleton パターンとは、GoF 本に掲載されるデザインパターンの一つ。クラス (構造体) のインスタンスをただ一つに制限する事で、一つの共有データにアプリのどこからでもアクセスできるようにする。
状況に応じた手法がいくつかある。
以下では、MyData
のインスタンスの作成に必要となるメンバが、sample
から公開されていない。そのため、MyData
のインスタンスは、sample
が作るただ一つに制限される。
use crate::sample::MyData;
fn main() {
let singleton = MyData::get();
assert_eq!(singleton.val(), 42);
}
mod sample {
use std::sync::LazyLock;
static SINGLETON: LazyLock<MyData> = LazyLock::new(||
MyData::new(42)
);
pub struct MyData {
val: i32
}
impl MyData {
pub fn get() -> &'static MyData {
LazyLock::force(&SINGLETON)
}
pub fn val(&self) -> i32 {
self.val
}
fn new(val: i32) -> Self {
Self { val }
}
}
}
以下では、前の例とは異なり、構造体へのアクセスを Mutex
で同期している。なお、同期を構造体単位でなくフィールド単位にしたり、Mutex
の代わりに RwLock
やアトミック型を使う方法も考えられる (コード量と並列性からどれも一長一短がある)。
use crate::sample::MyData;
fn main() {
let mut singleton = MyData::get();
singleton.set_val(43);
assert_eq!(singleton.val(), 43);
}
mod sample {
use std::sync::{LazyLock, Mutex, MutexGuard};
static SINGLETON: LazyLock<Mutex<MyData>> = LazyLock::new(||
Mutex::new(MyData::new(42))
);
pub struct MyData {
val: i32,
}
impl MyData {
pub fn get() -> MutexGuard<'static, MyData> {
LazyLock::force(&SINGLETON).lock().unwrap()
}
pub fn val(&self) -> i32 {
self.val
}
pub fn set_val(&mut self, value: i32) {
self.val = value
}
fn new(val: i32) -> Self {
Self { val }
}
}
}
以下では、遅延初期化のサンプルで使われていた Lazy
に代わって OnceCell
を使っている。これにより、アクセス時に自動的に既定値で初期化するのではなく、事前に引数を与えて初期化するようにしている。
use crate::sample::MyData;
fn main() {
MyData::init(42);
assert_eq!(MyData::get().val(), 42);
}
mod sample {
use std::sync::OnceLock;
static SINGLETON: OnceLock<MyData> = OnceLock::new();
#[derive(Debug)]
pub struct MyData {
val: i32,
}
impl MyData {
pub fn init(val: i32) {
SINGLETON
.set(MyData::new(val))
.expect("Should be called only one time.");
}
pub fn get() -> &'static MyData {
SINGLETON
.get()
.expect("Should be called after initialization.")
}
pub fn val(&self) -> i32 {
self.val
}
fn new(val: i32) -> Self {
Self { val }
}
}
}