Рассмотрим следующее
use std::sync::{Arc,Mutex};
fn arc_mutex_from_slice<T>(v: &[T]) -> Arc<Mutex<[T]>> {
todo!();
}
fn main() {
let v = vec![1,2,3];
println!("{:#?}", arc_mutex_from_slice(&v[..]));
}
Возможно ли реализовать arc_mutex_from_slice? Можно реализовать это преобразование для Arc<Mutex<Box<[T]>>> или Arc<Mutex<Vec<T>>>, но оба они добавляют еще одно ненужное выделение/косвенность.
Тип Arc<Mutex<[T]>> действителен и может быть инициализирован, когда известен размер среза.
let x = Arc::new(Mutex::new([1,2,3]));
Но я не могу найти способ добиться этого, когда размер среза неизвестен. Наивная попытка реализовать arc_mutex_from_slice следующим образом не компилируется.
fn arc_mutex_from_slice<T>(v: &[T]) -> Arc<Mutex<[T]>> {
return Arc::new(Mutex::new(*v));
}
error[E0277]: the size for values of type `[T]` cannot be known at compilation time
--> src/main.rs:4:21
|
4 | return Arc::new(Mutex::new(*v));
| -------- ^^^^^^^^^^^^^^ doesn't have a size known at compile-time
| |
| required by a bound introduced by this call
|
= help: within `Mutex<[T]>`, the trait `Sized` is not implemented for `[T]`
= note: required because it appears within the type `Mutex<[T]>`
note: required by a bound in `Arc::<T>::new`
Может ли какая-нибудь unsafe магия помочь с этим?
Я не думаю, что есть какой-либо способ построить Mutex неразмерного типа (безопасно или небезопасно), кроме как построить Mutex размерного типа и изменить его размер, потому что внутреннее расположение Mutex не указано. Таким образом, чтобы создать Mutex<[T]>, вы должны изменить размер Mutex<[T; N]>, потому что [T; N] — это единственные типы, которые уменьшаются до [T], а вы вообще не можете построить Mutex<[T]> из Vec<T>.
@prog-fh Я хотел, чтобы данные были клонированы из ввода &[T] или Vec<T>. Я подозреваю, что то, что вы предлагаете с потреблением Vec<T> и повторным использованием его буфера, в этом случае невозможно. Потому что Mutex<[T]> обязательно больше, чем просто [T], и может превышать размер внутреннего буфера Vec<T>. Несмотря на это, я до сих пор не могу найти способ сделать это преобразование даже с помощью клонирования.
срез дугового мьютекса совершенно бесполезен
@Stargateur, можете ли вы объяснить, почему это было бы бесполезно? Я не вижу другого способа совместного использования буфера изменяемых данных с другим потоком без блокировки каждого отдельного элемента или двойной косвенности.
он комментировал срез (ссылку), а не собственный массив. (трудно гарантировать что-либо о параллельном доступе)
@luctowers Почему бы не просто Arc<Mutex<Vec<T>>>? :)
@isaactfa Меня интересовало, можно ли избежать косвенного двойного указателя при наличии как Arc, так и Vec. Не похоже, что двойная косвенность строго необходима, просто ее трудно представить в ржавчине без нее.
Я советую вам искать конкретный тип, который одновременно является дугой и массивом, на lib.rs, я не нашел, что он вам идеально подходит lib.rs/crates/dyn_struct в основном то, что вы могли бы сделать, но вам нужно адаптируйте код, чтобы иметь атомарный счетчик. В любом случае, то, что вам нужно, это очень-очень сложный Rust, и я не могу не подчеркнуть, что, если у вас нет очень веской причины, вы должны просто жить с двойной косвенностью.
@Stargateur, это справедливо, спасибо. Двойная косвенность - это не конец света, просто хотел проверить, есть ли простой способ обойти это, чего мне не хватало.

Я не знаю, каково ваше точное намерение. Я предполагаю, что вам нужен массив, разделяемый потоками.
Как указано в комментарии, массив должен иметь размер.
Если этот размер не задан во время компиляции, вам необходимо запомнить массив переменной длины (выделенный в куче) вместе с его размером.
Box<[T]> это удобно.
Пожалуйста, найдите ниже три примера. Первый использует ссылку+клон, второй перемещает исходные данные. В случае, если размер известен во время компиляции, третий пример позволяет избежать дополнительной косвенности, о которой вы заботитесь.
Обратите внимание, что если нам нужна только одна косвенность, то она должна использоваться различными контекстами (потоками), чтобы ссылаться на один и тот же мьютекс. Это означает, что блок памяти, содержащий этот мьютекс, должен непосредственно содержать все данные. Если этот размер неизвестен во время компиляции, это звучит как уродливый хак структуры с переменным размером, который мы иногда встречаем в C (довольно подверженный ошибкам, не совсем в духе Rust).
use std::sync::{Arc, Mutex};
fn arc_mutex_from_slice_clone<T: Clone>(v: &[T]) -> Arc<Mutex<Box<[T]>>> {
let owned = v.to_owned();
let boxed = owned.into_boxed_slice();
Arc::new(Mutex::new(boxed))
}
fn arc_mutex_from_vec<T>(v: Vec<T>) -> Arc<Mutex<Box<[T]>>> {
let boxed = v.into_boxed_slice();
Arc::new(Mutex::new(boxed))
}
fn arc_mutex_sized_from_vec<T: std::fmt::Debug, const N: usize>(
v: Vec<T>
) -> Arc<Mutex<[T; N]>> {
let array = v.try_into().unwrap();
Arc::new(Mutex::new(array))
}
fn main() {
let v1 = vec![1, 2, 3];
let m1 = arc_mutex_from_slice_clone(v1.as_slice());
println!("m1: {:?}", m1);
// v1 is still usable since m1 contains a clone
println!("v: {:?}", v1);
let m2 = arc_mutex_from_vec(v1);
println!("m2: {:?}", m2);
// v1 is not usable anymore since is it moved int m2
// println!("v: {:?}", v1);
//
let v2 = vec![6, 7, 8, 9];
let m3 = arc_mutex_sized_from_vec::<_, 4>(v2);
println!("m3: {:?}", m3);
}
/*
m1: Mutex { data: [1, 2, 3], poisoned: false, .. }
v: [1, 2, 3]
m2: Mutex { data: [1, 2, 3], poisoned: false, .. }
m3: Mutex { data: [6, 7, 8, 9], poisoned: false, .. }
*/
Да, я ищу массив изменяемых данных с общим потоком с блокировкой, которая является общей для всех элементов. Я хотел посмотреть, есть ли способ избежать двойной косвенности, когда есть и Arc, и Box. Но, похоже, это может быть невозможно.
@luctowers Я добавил третий пример без дополнительной косвенности. Это означает, что размер известен во время компиляции.
Спасибо :) Я не знаю размер моего vec во время компиляции, так что у меня это не сработает. Но я раньше не видел, чтобы эти константные дженерики использовались подобным образом, так что они могут пригодиться для чего-то другого!
На момент написания для безопасного Rust вы можете сделать это, но только если размер массива при распределении известен во время компиляции:
let v: Arc<Mutex<[u8]>> = Arc::new(Mutex::new([0;16]));
По сути, это приводит Arc<Mutex<[u8;16]>> к Arc<Mutex<[u8]>>.
Однако в настоящее время нет способа сделать это безопасно с массивом, размер которого известен только во время выполнения. Сложность заключается в том, что вам нужно будет создать неразмерный Mutex<[u8]> и переместить его в конструктор Arc, но перемещение значений в настоящее время требует, чтобы компилятор знал их размеры.
RFC 1909 может помочь ситуации, но он открыт с 2017 года и конца ему не видно.
Что касается unsafe... Вы могли бы выделить раздел памяти, который может содержать как Mutex<()>, так и массив, и обращаться к нему вручную через смещения байтов. Однако вы должны быть осторожны - вам может потребоваться дополнение между мьютексом и массивом, чтобы удовлетворить выравнивание элемента массива, и убедитесь, что вы не нарушаете никаких гарантий псевдонимов или провидения.
Да, я наткнулся на этот rfc в своем поиске, очень неудачно. Спасибо!
Срез ссылается на некоторые ранее существовавшие данные; мьютекс владеет своими данными. Вы не можете притворяться, что владеете чем-то, что принадлежит кому-то другому. В конечном итоге вы можете передать право собственности на мьютекс, но вам придется передать значение (
Vec), а не ссылку (срез). Вы также можете клонировать исходные данные, чтобы владеть этим клоном (не оригиналом), но, как правило, это не то, что вам нужно.