Структура OnceLock<T>

#![allow(unused)]
fn main() {
pub struct OnceLock<T> { /* приватные поля */ }
}

1.70.0 · Источник

Описание

Примитив синхронизации, который номинально может быть записан только один раз.

Этот тип является потокобезопасным OnceCell и может использоваться в статических переменных. Во многих простых случаях вы можете использовать LazyLock<T, F> вместо этого, чтобы получить преимущества этого типа с меньшими усилиями: LazyLock<T, F> "выглядит как" &T, потому что он инициализируется с помощью F при разыменовании! OnceLock проявляет себя там, где LazyLock слишком прост для поддержки данного случая, так как LazyLock не позволяет передавать дополнительные входные данные в свою функцию после вызова LazyLock::new(|| ...).

OnceLock можно рассматривать как безопасную абстракцию над неинициализированными данными, которые становятся инициализированными после записи.

В отличие от Mutex, OnceLock никогда не отравляется при панике.

Примеры

Запись в OnceLock из отдельного потока:

#![allow(unused)]
fn main() {
use std::sync::OnceLock;

static CELL: OnceLock<usize> = OnceLock::new();

// `OnceLock` еще не был записан.
assert!(CELL.get().is_none());

// Создаем поток и записываем в `OnceLock`.
std::thread::spawn(|| {
    let value = CELL.get_or_init(|| 12345);
    assert_eq!(value, &12345);
})
.join()
.unwrap();

// `OnceLock` теперь содержит значение.
assert_eq!(
    CELL.get(),
    Some(&12345),
);
}

Вы можете использовать OnceLock для реализации типа, который требует логики "только для добавления":

#![allow(unused)]
fn main() {
use std::sync::{OnceLock, atomic::{AtomicU32, Ordering}};
use std::thread;

struct OnceList<T> {
    data: OnceLock<T>,
    next: OnceLock<Box<OnceList<T>>>,
}
impl<T> OnceList<T> {
    const fn new() -> OnceList<T> {
        OnceList { data: OnceLock::new(), next: OnceLock::new() }
    }
    fn push(&self, value: T) {
        // FIXME: эта реализация лаконична, но также медленна для длинных списков или многих потоков.
        // в качестве упражнения, подумайте, как вы могли бы улучшить ее, сохраняя поведение
        if let Err(value) = self.data.set(value) {
            let next = self.next.get_or_init(|| Box::new(OnceList::new()));
            next.push(value)
        };
    }
    fn contains(&self, example: &T) -> bool
    where
        T: PartialEq,
    {
        self.data.get().map(|item| item == example).filter(|v| *v).unwrap_or_else(|| {
            self.next.get().map(|next| next.contains(example)).unwrap_or(false)
        })
    }
}

// Давайте протестируем этот новый синхронизированный список только для добавления, выполнив небольшой подсчет
static LIST: OnceList<u32> = OnceList::new();
static COUNTER: AtomicU32 = AtomicU32::new(0);

const LEN: u32 = 1000;
thread::scope(|s| {
    for _ in 0..thread::available_parallelism().unwrap().get() {
        s.spawn(|| {
            while let i @ 0..LEN = COUNTER.fetch_add(1, Ordering::Relaxed) {
                LIST.push(i);
            }
        });
    }
});

for i in 0..LEN {
    assert!(LIST.contains(&i));
}
}

Методы

new

#![allow(unused)]
fn main() {
pub const fn new() -> OnceLock<T>
}

1.70.0 (const: 1.70.0)

Создает новую неинициализированную ячейку.

get

#![allow(unused)]
fn main() {
pub fn get(&self) -> Option<&T>
}

1.70.0

Получает ссылку на базовое значение.

Возвращает None, если ячейка не инициализирована или находится в процессе инициализации. Этот метод никогда не блокируется.

get_mut

#![allow(unused)]
fn main() {
pub fn get_mut(&mut self) -> Option<&mut T>
}

1.70.0

Получает изменяемую ссылку на базовое значение.

Возвращает None, если ячейка не инициализирована.

Этот метод никогда не блокируется. Поскольку он заимствует OnceLock изменяемо, статически гарантируется, что никаких активных заимствований OnceLock не существует, включая заимствования из других потоков.

wait

#![allow(unused)]
fn main() {
pub fn wait(&self) -> &T
}

1.86.0

Блокирует текущий поток до инициализации ячейки.

Пример

Ожидание завершения вычисления в другом потоке:

#![allow(unused)]
fn main() {
use std::thread;
use std::sync::OnceLock;

let value = OnceLock::new();

thread::scope(|s| {
    s.spawn(|| value.set(1 + 1));

    let result = value.wait();
    assert_eq!(result, &2);
})
}

set

#![allow(unused)]
fn main() {
pub fn set(&self, value: T) -> Result<(), T>
}

1.70.0

Инициализирует содержимое ячейки значением value.

Может блокироваться, если другой поток в настоящее время пытается инициализировать ячейку. Гарантируется, что ячейка будет содержать значение, когда set вернет управление, хотя не обязательно то, которое было предоставлено.

Возвращает Ok(()), если ячейка была неинициализирована, и Err(value), если ячейка уже была инициализирована.

Примеры

use std::sync::OnceLock;

static CELL: OnceLock<i32> = OnceLock::new();

fn main() {
    assert!(CELL.get().is_none());

    std::thread::spawn(|| {
        assert_eq!(CELL.set(92), Ok(()));
    }).join().unwrap();

    assert_eq!(CELL.set(62), Err(62));
    assert_eq!(CELL.get(), Some(&92));
}

try_insert

#![allow(unused)]
fn main() {
pub fn try_insert(&self, value: T) -> Result<&T, (&T, T)>
}

🔬 Это экспериментальное API только для ночных сборок. (once_cell_try_insert #116693)

Инициализирует содержимое ячейки значением value, если ячейка была неинициализирована, затем возвращает ссылку на него.

Может блокироваться, если другой поток в настоящее время пытается инициализировать ячейку. Гарантируется, что ячейка будет содержать значение, когда try_insert вернет управление, хотя не обязательно то, которое было предоставлено.

Возвращает Ok(&value), если ячейка была неинициализирована, и Err((&current_value, value)), если она уже была инициализирована.

Примеры

#![feature(once_cell_try_insert)]

use std::sync::OnceLock;

static CELL: OnceLock<i32> = OnceLock::new();

fn main() {
    assert!(CELL.get().is_none());

    std::thread::spawn(|| {
        assert_eq!(CELL.try_insert(92), Ok(&92));
    }).join().unwrap();

    assert_eq!(CELL.try_insert(62), Err((&92, 62)));
    assert_eq!(CELL.get(), Some(&92));
}

get_or_init

#![allow(unused)]
fn main() {
pub fn get_or_init<F>(&self, f: F) -> &T
where
    F: FnOnce() -> T,
}

1.70.0

Получает содержимое ячейки, инициализируя его с помощью f(), если ячейка была неинициализирована.

Многие потоки могут вызывать get_or_init конкурентно с разными инициализирующими функциями, но гарантируется, что только одна функция будет выполнена, если функция не паникует.

Паника

Если f() паникует, паника распространяется на вызывающую сторону, и ячейка остается неинициализированной.

Ошибкой является реентерабельная инициализация ячейки из f. Точный результат не указан. Текущая реализация приводит к взаимной блокировке, но это может быть изменено на панику в будущем.

Примеры

#![allow(unused)]
fn main() {
use std::sync::OnceLock;

let cell = OnceLock::new();
let value = cell.get_or_init(|| 92);
assert_eq!(value, &92);
let value = cell.get_or_init(|| unreachable!());
assert_eq!(value, &92);
}

get_mut_or_init

#![allow(unused)]
fn main() {
pub fn get_mut_or_init<F>(&mut self, f: F) -> &mut T
where
    F: FnOnce() -> T,
}

🔬 Это экспериментальное API только для ночных сборок. (once_cell_get_mut #121641)

Получает изменяемую ссылку на содержимое ячейки, инициализируя его с помощью f(), если ячейка была неинициализирована.

Этот метод никогда не блокируется. Поскольку он заимствует OnceLock изменяемо, статически гарантируется, что никаких активных заимствований OnceLock не существует, включая заимствования из других потоков.

Паника

Если f() паникует, паника распространяется на вызывающую сторону, и ячейка остается неинициализированной.

Примеры

#![allow(unused)]
#![feature(once_cell_get_mut)]

fn main() {
use std::sync::OnceLock;

let mut cell = OnceLock::new();
let value = cell.get_mut_or_init(|| 92);
assert_eq!(*value, 92);

*value += 2;
assert_eq!(*value, 94);

let value = cell.get_mut_or_init(|| unreachable!());
assert_eq!(*value, 94);
}

get_or_try_init

#![allow(unused)]
fn main() {
pub fn get_or_try_init<F, E>(&self, f: F) -> Result<&T, E>
where
    F: FnOnce() -> Result<T, E>,
}

🔬 Это экспериментальное API только для ночных сборок. (once_cell_try #109737)

Получает содержимое ячейки, инициализируя его с помощью f(), если ячейка была неинициализирована. Если ячейка была неинициализирована и f() завершилась неудачей, возвращается ошибка.

Паника

Если f() паникует, паника распространяется на вызывающую сторону, и ячейка остается неинициализированной.

Ошибкой является реентерабельная инициализация ячейки из f. Точный результат не указан. Текущая реализация приводит к взаимной блокировке, но это может быть изменено на панику в будущем.

Примеры

#![allow(unused)]
#![feature(once_cell_try)]

fn main() {
use std::sync::OnceLock;

let cell = OnceLock::new();
assert_eq!(cell.get_or_try_init(|| Err(())), Err(()));
assert!(cell.get().is_none());
let value = cell.get_or_try_init(|| -> Result<i32, ()> {
    Ok(92)
});
assert_eq!(value, Ok(&92));
assert_eq!(cell.get(), Some(&92))
}

get_mut_or_try_init

#![allow(unused)]
fn main() {
pub fn get_mut_or_try_init<F, E>(&mut self, f: F) -> Result<&mut T, E>
where
    F: FnOnce() -> Result<T, E>,
}

🔬 Это экспериментальное API только для ночных сборок. (once_cell_get_mut #121641)

Получает изменяемую ссылку на содержимое ячейки, инициализируя его с помощью f(), если ячейка была неинициализирована. Если ячейка была неинициализирована и f() завершилась неудачей, возвращается ошибка.

Этот метод никогда не блокируется. Поскольку он заимствует OnceLock изменяемо, статически гарантируется, что никаких активных заимствований OnceLock не существует, включая заимствования из других потоков.

Паника

Если f() паникует, паника распространяется на вызывающую сторону, и ячейка остается неинициализированной.

Примеры

#![allow(unused)]
#![feature(once_cell_get_mut)]

fn main() {
use std::sync::OnceLock;

let mut cell: OnceLock<u32> = OnceLock::new();

// Неудачные попытки инициализировать ячейку не изменяют ее содержимое
assert!(cell.get_mut_or_try_init(|| "not a number!".parse()).is_err());
assert!(cell.get().is_none());

let value = cell.get_mut_or_try_init(|| "1234".parse());
assert_eq!(value, Ok(&mut 1234));
*value.unwrap() += 2;
assert_eq!(cell.get(), Some(&1236))
}

into_inner

#![allow(unused)]
fn main() {
pub fn into_inner(self) -> Option<T>
}

1.70.0

Потребляет OnceLock, возвращая обернутое значение. Возвращает None, если ячейка была неинициализирована.

Примеры

#![allow(unused)]
fn main() {
use std::sync::OnceLock;

let cell: OnceLock<String> = OnceLock::new();
assert_eq!(cell.into_inner(), None);

let cell = OnceLock::new();
cell.set("hello".to_string()).unwrap();
assert_eq!(cell.into_inner(), Some("hello".to_string()));
}

take

#![allow(unused)]
fn main() {
pub fn take(&mut self) -> Option<T>
}

1.70.0

Извлекает значение из этого OnceLock, возвращая его в неинициализированное состояние.

Не оказывает эффекта и возвращает None, если OnceLock был неинициализирован.

Поскольку этот метод заимствует OnceLock изменяемо, статически гарантируется, что никаких активных заимствований OnceLock не существует, включая заимствования из других потоков.

Примеры

#![allow(unused)]
fn main() {
use std::sync::OnceLock;

let mut cell: OnceLock<String> = OnceLock::new();
assert_eq!(cell.take(), None);

let mut cell = OnceLock::new();
cell.set("hello".to_string()).unwrap();
assert_eq!(cell.take(), Some("hello".to_string()));
assert_eq!(cell.get(), None);
}

Реализации трейтов

Clone

1.70.0

#![allow(unused)]
fn main() {
impl<T: Clone> Clone for OnceLock<T>
}

clone

#![allow(unused)]
fn main() {
fn clone(&self) -> OnceLock<T>
}

Возвращает дубликат значения.

clone_from

#![allow(unused)]
fn main() {
fn clone_from(&mut self, source: &Self)
}

1.0.0

Выполняет копирующее присваивание из source.

Debug

1.70.0

#![allow(unused)]
fn main() {
impl<T: Debug> Debug for OnceLock<T>
}

fmt

#![allow(unused)]
fn main() {
fn fmt(&self, f: &mut Formatter<'_>) -> Result
}

Форматирует значение с помощью заданного форматировщика.

Default

1.70.0

#![allow(unused)]
fn main() {
impl<T> Default for OnceLock<T>
}

default

#![allow(unused)]
fn main() {
fn default() -> OnceLock<T>
}

Создает новую неинициализированную ячейку.

Пример

use std::sync::OnceLock;

fn main() {
    assert_eq!(OnceLock::<()>::new(), OnceLock::default());
}

Drop

1.70.0

#![allow(unused)]
fn main() {
impl<T> Drop for OnceLock<T>
}

drop

#![allow(unused)]
fn main() {
fn drop(&mut self)
}

Выполняет деструктор для этого типа.

From<T>

1.70.0

#![allow(unused)]
fn main() {
impl<T> From<T> for OnceLock<T>
}

from

#![allow(unused)]
fn main() {
fn from(value: T) -> Self
}

Создает новую ячейку с содержимым, установленным в value.

Пример

#![allow(unused)]
fn main() {
use std::sync::OnceLock;

let a = OnceLock::from(3);
let b = OnceLock::new();
b.set(3)?;
assert_eq!(a, b);
Ok(())
}

PartialEq

1.70.0

#![allow(unused)]
fn main() {
impl<T: PartialEq> PartialEq for OnceLock<T>
}

eq

#![allow(unused)]
fn main() {
fn eq(&self, other: &OnceLock<T>) -> bool
}

Равенство для двух OnceLock.

Два OnceLock равны, если они либо оба содержат значения и их значения равны, либо если ни один не содержит значения.

Примеры

#![allow(unused)]
fn main() {
use std::sync::OnceLock;

let five = OnceLock::new();
five.set(5).unwrap();

let also_five = OnceLock::new();
also_five.set(5).unwrap();

assert!(five == also_five);

assert!(OnceLock::<u32>::new() == OnceLock::<u32>::new());
}

ne

#![allow(unused)]
fn main() {
fn ne(&self, other: &Rhs) -> bool
}

1.0.0

Тестирует !=. Реализация по умолчанию почти всегда достаточна и не должна переопределяться без очень веской причины.

Eq

1.70.0

#![allow(unused)]
fn main() {
impl<T: Eq> Eq for OnceLock<T>
}

RefUnwindSafe

1.70.0

#![allow(unused)]
fn main() {
impl<T: RefUnwindSafe + UnwindSafe> RefUnwindSafe for OnceLock<T>
}

Send

1.70.0

#![allow(unused)]
fn main() {
impl<T: Send> Send for OnceLock<T>
}

Sync

1.70.0

#![allow(unused)]
fn main() {
impl<T: Sync + Send> Sync for OnceLock<T>
}

UnwindSafe

1.70.0

#![allow(unused)]
fn main() {
impl<T: UnwindSafe> UnwindSafe for OnceLock<T>
}

Автоматические реализации трейтов

!Freeze

#![allow(unused)]
fn main() {
impl<T> !Freeze for OnceLock<T>
}

Unpin

#![allow(unused)]
fn main() {
impl<T> Unpin for OnceLock<T>
where
    T: Unpin,
}

Стандартные реализации

(Реализации стандартных трейтов для всех типов остаются без изменений)