Структура Arc

#![allow(unused)]
fn main() {
pub struct Arc<T, A = Global> where
    A: Allocator,
    T: ?Sized, { /* приватные поля */ }
}

Потокобезопасный (Send + Sync) счетчик ссылок с атомарными операциями.

Тип Arc<T> предоставляет совместное владение значением типа T, выделенным в куче. Вызов clone на Arc создает новый экземпляр Arc, который указывает на то же самое выделение в куче, увеличивая при этом счетчик ссылок. Когда последний указатель Arc на выделение выходит из области видимости, значение удаляется (деструктор запускается), и память освобождается.

Совместное владение в Rust по умолчанию осуществляется через проверку заимствований. Rc<T> и Arc<T> позволяют обойти это ограничение: Rc использует проверки во время выполнения для обеспечения целостности, Arc использует атомарные операции.

Совместное владение также означает неизменность: невозможно получить изменяемую ссылку (&mut T) на значение внутри Arc, если только не используется другой механизм для обеспечения мутабельности, например Mutex или RwLock.

Умные указатели

Значения типа Arc<T> являются умными указателями: они указывают на значение типа T, расположенное в куче, и имеют метаданные счетчика ссылок. При создании Arc с помощью Arc::new метаданные счетчика ссылок также сохраняются в куче рядом с T. При клонировании Arc создается новый умный указатель, который указывает на то же самое размещение в куче.

Когда Arc выходит из области видимости, его реализация Drop уменьшает счетчик ссылок. Если счетчик ссылок достигает нуля, значение внутри T удаляется, а память освобождается.

Примеры

Потокобезопастельное совместное использование изменяемых данных:

#![allow(unused)]
fn main() {
use std::sync::{Arc, Mutex};
use std::thread;

let five = Arc::new(Mutex::new(5));

for _ in 0..10 {
    let five = Arc::clone(&five);

    thread::spawn(move || {
        let mut value = five.lock().unwrap();
        *value += 1;
    });
}
}

Clone

Создание ссылки на тот же самый allocation:

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

let foo = Arc::new(vec![1.0, 2.0, 3.0]);
// Две ссылки на одни и те же данные
let a = foo.clone();
let b = Arc::clone(&foo);
// a, b и foo - все Arc, указывающие на одни и те же данные
}

Функция Arc::clone(&from) семантически эквивалентна from.clone(), но более идиоматична.

Deref поведение

Arc<T> автоматически разыменовывается в T (благодаря реализации Deref), поэтому вы можете вызывать методы T непосредственно на Arc<T>. Чтобы избежать конфликта имен с методами T, методы Arc<T>которые являются ассоциированными функциями (вызываются как Arc::get_mut(&mut value) вместо value.get_mut()).

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

let my_arc = Arc::new(());
let my_arc_clone = Arc::clone(&my_arc);
}

Также следует учитывать автоматическое разыменование при сравнении:

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

let five = Arc::new(5);
assert!(five == 5); // Автоматическое разыменование в i32
}

Примеры совместного использования между потоками

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

let five = Arc::new(5);

for _ in 0..10 {
    let five = Arc::clone(&five);

    thread::spawn(move || {
        println!("{five:?}");
    });
}
}

Примеры Weak

Слабые ссылки (Weak) не предотвращают удаление allocation:

#![allow(unused)]
fn main() {
use std::sync::{Arc, Weak};

let strong = Arc::new("hello".to_owned());
let weak = Arc::downgrade(&strong);

// Сильная ссылка все еще существует, значение доступно
assert_eq!(1, Arc::weak_count(&strong));
assert_eq!(Some("hello"), weak.upgrade().as_deref());

drop(strong);
// Теперь значение удалено
assert!(weak.upgrade().is_none());
}

Отличия от std::rc::Rc

Arc использует атомарные операции для подсчета ссылок, что означает его безопасность для совместного использования между потоками. Однако это влечет накладные расходы по производительности, которых нет у Rc. Rc не является потокобезопасным и не реализует Send или Sync.

Атомарность

Arc использует глобально атомарные операции для подсчета ссылок. На современных процессорах это часто достигается с помощью инструкций с блокировкой кэша. Однако системы без атомарных операций могут использовать спин-блокировки или другие механизмы.

Безопасность для циклических ссылок

Arc по умолчанию не предотвращает циклические ссылки. Это может привести к утечкам памяти. Для предотвращения циклических ссылок используйте Weak.

Трейт-объекты

Когда T имеет типаж Dyn (например, dyn SomeTrait), Arc автоматически реализует соответствующие типажи, такие как DynSomeTrait.

Реализации

  • Arc<str>: Arc<str> не имеет нуль-терминатора и может использоваться для строковых срезов
  • Arc<[T]>: Arc<[T]> может использоваться для срезов массива
  • Arc<CStr>: Arc<CStr> может использоваться для строк в стиле C

Методы

Создание

  • new(value: T) -> Arc<T> - Создает новый Arc<T>
  • new_cyclic(data_fn: F) -> Arc<T> - Создает Arc с циклической ссылкой
  • new_uninit() -> Arc<MaybeUninit<T>> - Создает неинициализированный Arc
  • pin(value: T) -> Pin<Arc<T>> - Закрепляет значение в памяти

Получение внутренних данных

  • into_raw(this: Arc<T>) -> *const T - Преобразует в сырой указатель
  • from_raw(ptr: *const T) -> Arc<T> - Восстанавливает из сырого указателя
  • as_ptr(this: &Arc<T>) -> *const T - Получает сырой указатель

Счетчики ссылок

  • strong_count(this: &Arc<T>) -> usize - Количество сильных ссылок
  • weak_count(this: &Arc<T>) -> usize - Количество слабых ссылок

Слабые ссылки

  • downgrade(this: &Arc<T>) -> Weak<T> - Создает слабую ссылку
  • try_unwrap(this: Arc<T>) -> Result<T, Arc<T>> - Извлекает значение, если это последняя сильная ссылка

Сравнения

  • ptr_eq(this: &Arc<T>, other: &Arc<T>) -> bool - Проверяет, указывают ли на одно allocation

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

  • Clone, Debug, Default, Deref, Drop
  • From<T>, From<Box<T>>, From<String>, From<&str>
  • PartialEq, Eq, PartialOrd, Ord, Hash
  • Send, Sync (когда T: Send + Sync)
  • Pointer, Display

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

  • !Unpin (когда T: ?Unpin)
  • UnwindSafe (когда T: UnwindSafe)
  • RefUnwindSafe (когда T: RefUnwindSafe)