Структура Once

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

Описание

Низкоуровневый примитив синхронизации для однократного глобального выполнения.

Ранее это была единственная синхронизация "выполнить один раз" в std. Другие библиотеки реализовывали новые синхронизирующие типы с помощью Once, такие как OnceLock<T> или LazyLock<T, F>, до того как они были добавлены в std. В частности, OnceLock<T> заменяет Once по функциональности и должен быть предпочтительнее для общего случая, когда Once ассоциирован с данными.

Этот тип может быть создан только с помощью Once::new().

Примеры

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

static START: Once = Once::new();

START.call_once(|| {
    // выполнить инициализацию здесь
});
}

Методы

new

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

1.2.0 (const: 1.32.0)

Создает новое значение Once.

call_once

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

1.0.0

Выполняет процедуру инициализации один и только один раз. Данное замыкание будет выполнено, если это первый вызов call_once, в противном случае процедура не будет вызвана.

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

Когда эта функция возвращает управление, гарантируется, что некоторая инициализация была выполнена и завершена (это может быть не указанное замыкание). Также гарантируется, что любые записи в память, выполненные выполненным замыканием, могут быть надежно наблюдаемы другими потоками в этот момент (существует отношение happens-between между замыканием и кодом, выполняющимся после возврата).

Если данное замыкание рекурсивно вызывает call_once на том же экземпляре Once, точное поведение не указано: допустимыми исходами являются паника или взаимная блокировка.

Примеры

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

static mut VAL: usize = 0;
static INIT: Once = Once::new();

// Доступ к `static mut` небезопасен большую часть времени, но если мы делаем это
// синхронизированным образом (например, запись один раз или чтение всех), то все в порядке!
//
// Эта функция вызовет `expensive_computation` только один раз и будет
// всегда возвращать значение, возвращенное при первом вызове.
fn get_cached_val() -> usize {
    unsafe {
        INIT.call_once(|| {
            VAL = expensive_computation();
        });
        VAL
    }
}

fn expensive_computation() -> usize {
    // ...
}
}

Паника

Замыкание f будет выполнено только один раз, даже если этот метод вызывается конкурентно из многих потоков. Однако, если это замыкание паникует, то оно отравит этот экземпляр Once, вызывая панику всех будущих вызовов call_once.

Это похоже на отравление мьютексами, но этот механизм гарантированно никогда не пропускает паники внутри f.

call_once_force

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

1.51.0

Выполняет ту же функцию, что и call_once(), но игнорирует отравление.

В отличие от call_once(), если этот Once был отравлен (т.е. предыдущий вызов call_once() или call_once_force() вызвал панику), вызов call_once_force() все равно вызовет замыкание f и не приведет к немедленной панике. Если f паникует, Once останется в отравленном состоянии. Если f не паникует, Once больше не будет в отравленном состоянии, и все будущие вызовы call_once() или call_once_force() будут no-ops.

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

Примеры

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

static INIT: Once = Once::new();

// отравить once
let handle = thread::spawn(|| {
    INIT.call_once(|| panic!());
});
assert!(handle.join().is_err());

// отравление распространяется
let handle = thread::spawn(|| {
    INIT.call_once(|| {});
});
assert!(handle.join().is_err());

// call_once_force все равно выполнится и сбросит отравленное состояние
INIT.call_once_force(|state| {
    assert!(state.is_poisoned());
});

// как только происходит любой успех, мы перестаем распространять отравление
INIT.call_once(|| {});
}

is_completed

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

1.43.0

Возвращает true, если некоторый вызов call_once() завершился успешно. В частности, is_completed вернет false в следующих ситуациях:

  • call_once() не вызывался вообще,
  • call_once() был вызван, но еще не завершился,
  • экземпляр Once отравлен

Возврат этой функцией false не означает, что Once не был выполнен. Например, он мог быть выполнен во время между началом выполнения is_completed и ее возвратом, в этом случае возвращаемое значение false было бы устаревшим (но все еще допустимым).

Примеры

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

static INIT: Once = Once::new();

assert_eq!(INIT.is_completed(), false);
INIT.call_once(|| {
    assert_eq!(INIT.is_completed(), false);
});
assert_eq!(INIT.is_completed(), true);
}
#![allow(unused)]
fn main() {
use std::sync::Once;
use std::thread;

static INIT: Once = Once::new();

assert_eq!(INIT.is_completed(), false);
let handle = thread::spawn(|| {
    INIT.call_once(|| panic!());
});
assert!(handle.join().is_err());
assert_eq!(INIT.is_completed(), false);
}

wait

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

1.86.0

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

Пример

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

static READY: Once = Once::new();

let thread = thread::spawn(|| {
    READY.wait();
    println!("everything is ready");
});

READY.call_once(|| println!("performing setup"));
}

Паника

Если этот Once был отравлен из-за паники замыкания инициализации, этот метод также вызовет панику. Используйте wait_force, если такое поведение нежелательно.

wait_force

#![allow(unused)]
fn main() {
pub fn wait_force(&self)
}

1.86.0

Блокирует текущий поток до завершения инициализации, игнорируя отравление.

Если этот Once был отравлен, эта функция блокируется до тех пор, пока он не станет завершенным, в отличие от Once::wait(), которая паникует в этом случае.

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

Debug

1.16.0

#![allow(unused)]
fn main() {
impl Debug for Once
}

fmt

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

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

RefUnwindSafe

1.59.0

#![allow(unused)]
fn main() {
impl RefUnwindSafe for Once
}

UnwindSafe

1.59.0

#![allow(unused)]
fn main() {
impl UnwindSafe for Once
}

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

!Freeze

#![allow(unused)]
fn main() {
impl !Freeze for Once
}

Send

#![allow(unused)]
fn main() {
impl Send for Once
}

Sync

#![allow(unused)]
fn main() {
impl Sync for Once
}

Unpin

#![allow(unused)]
fn main() {
impl Unpin for Once
}

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

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