|
📌 Новый раздел
- 👾 Best practice
- Варианты применения
|
|
|
Интеллектуальный указатель на данные в куче c не более isize::MAX байт
Box<T> представляет собой тип smart-pointer (поскольку он реализует Deref<Target=T>), который выделяет память на куче (heap) и хранит в ней значение типа T.
Когда Box<T> выходит за пределы области видимости, данные кучи, на которые указывает ящик, также очищаются из-за Drop реализации признака.
|
|
Boxing может быть полезным в следующих случаях
|
1. Решение проблемы владения (Ownership Issues):
Если вам нужно передать владение значением другой части кода, но вы не можете передать его по значению (например, из-за того, что размер значения неизвестен во время компиляции), вы можете упаковать его в Box и передать указатель на кучу.
Более идиоматично использовать ссылки ( &T/&mut T) для указания на данные, однако они часто имеют сложность на время жизни.
Box позволяет избежать этой сложности за счет выделения кучи.
Указатель на данные в куче не может быть NULL
Для любителей C++: std::unique_ptr
2. Работа с типами неизвестного размера (Unknown Sized Types):
Некоторые типы данных, такие как trait объекты или срезы (slice), имеют неизвестный размер во время компиляции. Boxing позволяет работать с такими типами, так как Box имеет фиксированный размер.
3. Уменьшение накладных расходов на стек (Stack Overhead):
Значения, хранящиеся на стеке, имеют ограничение на размер, поскольку размер стека ограничен. Упаковка значений в Box позволяет избежать этого ограничения и уменьшить накладные расходы на стек.
fn main(){
// Из стека в кучу:
let val: u8 = 5;
let boxed: Box = Box::new(val);
// Переместите значение из Box обратно в стек путем разыменования dereferencing Deref:
// (либо для сопоставления в match)
let boxed: Box = Box::new(5);
let val: u8 = *boxed;
}
|
|
Boxing может быть излишним или нежелательным
|
1. Избыточное использование кучи (Excessive Heap Allocation):
Использование слишком многих Box может привести к избыточной фрагментации памяти и снижению производительности из-за частых запросов на выделение и освобождение памяти на куче.
2. Ненужная абстракция (Unnecessary Abstraction):
В некоторых случаях использование Box может усложнить код и внести лишнюю абстракцию, когда передача значения по значению или ссылке была бы более простым и понятным.
3. Избыточное копирование (Excessive Copying):
Поскольку Box копирует только указатель, а не само значение, иногда могут возникнуть ситуации, когда происходит неожиданное копирование данных, что может быть неэффективно.
В целом, boxing является мощным инструментом в Rust, который предоставляет гибкость в работе с памятью и типами данных, но его следует использовать разумно, учитывая потенциальные недостатки и избегая излишнего использования, когда это возможно.
|
|
|
- new(x: T) -> Box
<T> - Выделяет память в куче, а затем помещает в неё данные. Это фактически не выделяет память, если T имеет нулевой размер.
- new_uninit() -> Box
<MaybeUninit<T>> - Создает новый Box<[MaybeUninit<T>]> с неинициализированным содержимым.
- new_uninit_slice(len: usize) -> Box
<[MaybeUninit<T>]> - для создания Box<[MaybeUninit<T>]> с неинициализированной памятью. Для оптимизации, чтобы избежать лишней инициализации памяти.
- assume_init - Преобразуется в Box
<[T], A> или Box<T, A>
- downcast - Попытки привести Box в соответствие с конкретным типом.
- **from_raw(raw: *mut T) -> Box
<T> - Создает Box<T> из необработанного указателя.
- into_pin(boxed: Box
<T, A>) -> Pin<Box<T, A>> - Преобразует Box<T> в Pin<Box<T>>. Если T не реализует Unpin, то *boxed будет закреплён в памяти и не сможет быть перемещен.
- into_raw(b: Box
<T>) -> *mut T - Потребляет Box, возвращая упакованный необработанный указатель.
- leak
<'a>(b: Box<T, A>) -> &'a mut T - это функция, которая преобразует Box<T> в &'static T, effectively "утекающая" память (не освобождая её до конца работы программы).
- pin(x: T) -> Pin
<Box<T>> - создает Pin<Box<T>>, фиксируя данные в куче и гарантируя, что они не будут перемещены в памяти.
- write(boxed: Box
<MaybeUninit<T>, A>, value: T) -> Box<T, A> - метод для безопасной записи значения в уже выделенную память Box, без дополнительного выделения памяти.
|
|
|
1. Когда у вас есть тип, размер которого не может быть известен во время компиляции (рекурсивный тип), и вы хотите использовать значение этого типа в контексте, который требует точного размера.
Example:
Рекурсивный тип т.е. неизвестный размер он содержит в полях сам себя
enum List {
Empty,
Node(i32, Box<List>)
}
и
struct Cacher<N,M>{
calculation: Box<Fn(N)->M> // У Fn типажа нет размера
}
2. Когда у вас большой объем данных, и вы хотите передать право собственности, но убедитесь, что данные не будут скопированы, когда вы это сделаете. Так как в случае передача права собственности на большой объем данных может занять много времени, поскольку данные копируются в стек.
Чтобы улучшить производительность в этой ситуации, мы можем хранить большой объем данных в куче в Box.
Затем в стеке копируется только небольшое количество данных указателя, а данные, на которые он ссылается, остаются в одном месте в куче.
3. Когда вы хотите получить значение, и вы заботитесь только о том, что это тип, который реализует конкретный признак, а не определенный тип
Example:
let v:Vec<Box<BaseTrait>> = vec![Box::new(obj_1),...]; // obj_1 и другие типы реализующие один типаж
|
|
|
use std::pin::Pin;
use futures::{future, Future};
fn test() -> Pin<Box<dyn Future<Output = Result<bool, ()>>>> {
Box::pin(future::ok(true))
}
async fn async_fn() -> bool {
test().await.unwrap()
}
fn main(){}
|
|
|
Поскольку Box<T> является указателем, Rust всегда знает, сколько места требуется для Box<T> размер указателя не изменяется в зависимости от количества данных, на которые он указывает.
Во время компиляции Rust должен знать, сколько места занимает тип.
Один тип, размер которого не может быть известен во время компиляции, является рекурсивным типом, где значение может иметь как часть самого себя другое значение того же типа.
Поскольку такое вложение значений теоретически может продолжаться бесконечно, Rust не знает, сколько места требуется для значения рекурсивного типа.
Однако блоки имеют известный размер, поэтому вставив блок в определение рекурсивного типа, вы можете получить рекурсивные типы.
|
|
|
Компилятор не может определить размер выделяемой памяти для этих данных так как они рекурсивны
// recursive type has infinite size
enum List {
Cons(i32, List),
Nil,
}
use List::{Cons, Nil};
fn main() {
let list = Cons(1, Cons(2, Cons(3, Nil))); // ❌
}
Указатель Box<T> имеет фиксированный размер не зависимо от данных
#[derive(Debug)]
enum List {
Cons(i32, Box),
Nil,
}
use List::{Cons, Nil};
fn main() {// ✅
let list = Cons(1,
Box::new(Cons(2,
Box::new(Cons(3,
Box::new(Nil))))));
println!("{:?}", list);
}
Generic версия
#[derive(Debug)]
enum List { // ✅
Cons(T, Box>),
Nil,
}
fn main(){
let list: List = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
println!("{:?}", list);
}
|
|
Box::leak
Недостатком Box::leak является то, что утекает память.
Мы что-то выделяем, но никогда не удаляем и не освобождаем это.
Это может быть нормально, если это происходит только ограниченное количество раз.
Но если мы продолжим это делать, программа постепенно исчерпает память.
method.leak
|
Box::leak в Rust используется для того, чтобы утечь (leak) память, превращая объект в ссылку с 'static временем жизни.
Это означает, что объект, на который указывает Box, больше никогда не будет освобождён автоматически, и его память будет оставаться выделенной до конца работы программы.
Зачем использовать Box::leak
-
Продление времени жизни объекта: Иногда требуется, чтобы объект жил дольше, чем текущая область видимости или даже до конца программы. С помощью Box::leak объект может быть "утечён", чтобы можно было создать ссылку на него с 'static временем жизни.
-
Интерфейс с FFI (Foreign Function Interface): При взаимодействии с кодом на других языках программирования (например, C), часто требуется, чтобы Rust-объекты имели 'static время жизни, так как C-код не понимает времени жизни Rust и не может корректно обрабатывать автоматическое управление памятью.
-
Глобальные переменные: Box::leak часто используется при создании глобальных переменных, когда вам нужно, чтобы данные жили в течение всего времени выполнения программы. Это позволяет создать статическую переменную, хранящую ссылку на данные.
fn main() {
let x: &'static [i32; 3] = Box::leak(Box::new([1, 2, 3]));
thread::spawn(move || dbg!(x));
thread::spawn(move || dbg!(x));
// --------------------------------------------------------------------
let boxed = Box::new(42);
let leaked: &'static i32 = Box::leak(boxed);
println!("Leaked value: {}", leaked);
}
|
|
Box::leak<'a>(b: Box<T>) -> &'a mut T - Потребляет и пропускает Box, возвращая изменяемую ссылку &'a mut T
Отбрасывание возвращаемой ссылки приведет к утечке памяти.
Если это неприемлемо, ссылка сначала должна быть обернута функцией Box::from_raw, создающей Box.
Затем этот Box можно отбросить, что приведет к правильному уничтожению T и освобождению выделенной памяти.
method.leak
|
Simple usage:
fn main() {
let x:Box<usize> = Box::new(41);
let static_ref: &'static mut usize = Box::leak(x);
*static_ref += 1;
assert_eq!(*static_ref, 42);
}
Unsized data:
fn main() {
let x:Box<[i32]> = vec![1, 2, 3].into_boxed_slice();
let static_ref:&mut [i32] = Box::leak(x);
static_ref[0] = 4;
assert_eq!(*static_ref, [4, 2, 3]);
}
fn main(){
#[wasm_bindgen(skip)]
#[derive(Debug)]
pub struct Hand ( &'static str);
#[wasm_bindgen]
impl Hand {
pub fn new( key:&str)->Self{
Hand(
Box::leak(String::from(key).into_boxed_str())
)
}
}
}
fn get_static(s: String) -> (&'static str,*mut String){
let my_speed: Box<String> = Box::new(s);
let my_speed_ptr: *mut String = Box::into_raw(my_speed);
unsafe {
let mut my_speed_two: Box<String> = Box::from_raw(my_speed_ptr);
let static_ref: &'static mut String = Box::leak(my_speed_two);
(*static_ref).push_str(" World!");
(static_ref,my_speed_ptr)
}
}
fn main(){
let s = String::from("Hello ");
let (static_str,my_speed_ptr):(&'static str,*mut String) = get_static(s);
println!("{}",static_str);
unsafe {
drop(Box::from_raw(my_speed_ptr));
}
}
|
|
downcast<T>(self) -> Result<Box<T>, Box<Any + 'static>>
Попытайтесь свести Box<T> к конкретному типу.
|
use std::any::Any;
fn print_if_string(value: Box) {
if let Ok(string) = value.downcast::() {
println!("String ({}): {}", string.len(), string);
}
}
fn main() {
let my_string = "Hello World".to_string();
print_if_string(Box::new(my_string));
print_if_string(Box::new(0i8));
}
|
|
|
Упаковка — умный указатель на значение типа T в куче. Когда упаковка оказывается за пределами области видимости, вызывается деструктор, содержащийся в ней объект уничтожается, а память в куче освобождается.
|
|
|
Разыменовывает один уровень косвенности
fn main(){
let box = Box:new(Some(123));
if let Some(n) = *box{
assert_eq!(n,123);
}
}
|
|
Два уровня косвенной адресации
Двойная упаковка (Box<Box<T>>) увеличивает уровень косвенной адресации, но сохраняет минимальное использование стека (только указатели)
|
Вызов boxed_origin() возвращает Box<Point> (указатель на объект Point в куче).
Затем этот Box<Point> снова упаковывается в ещё один Box, размещённый в куче.
Таким образом, box_in_a_box — это указатель на указатель.
fn origin() -> Point {
Point { x: 0.0, y: 0.0 }
}
let boxed_point: Box = Box::new(origin());
fn boxed_origin() -> Box {
// Разместить эту точку в куче и вернуть указатель на неё
Box::new(Point { x: 0.0, y: 0.0 })
}
fn main(){
// Два уровня косвенной адресации
let box_in_a_box: Box> = Box::new(boxed_origin());
println!("Boxed box занимает {} байт в стеке", mem::size_of_val(&box_in_a_box));// 8 (размер одного указателя на 64-битной системе)
// Копировать данные, что находятся в `boxed_point`, в `unboxed_point`
let unboxed_point: Point = *boxed_point; // копирует данные из кучи на стек
println!("Unboxed point занимает {} байт в стеке", mem::size_of_val(&unboxed_point));// 16 (по 8 байт на каждое поле x и y типа f64)
}
|
|
То что имеет неизвестный размер упаковываем в Box
|
use std::collections::HashMap;
use std::thread;
use std::time::Duration;
struct Cacher<N,M>{
calculation:Box<Fn(N)->M>,
map:HashMap<u32, u32>
}
impl Cacher<u32,u32>{
fn new<T:'static + Fn(u32) -> u32>(calculation: T) -> Cacher<u32,u32> {
Cacher {
calculation:Box::new(calculation),
map:HashMap::new()
}
}
fn value(&mut self, arg: u32) -> u32 {
if self.map.contains_key(&arg){
*self.map.get(&arg).unwrap()
}else{
let v = (self.calculation)(arg);
self.map.insert(arg,v);
v
}
}
}
fn generate_workout(intensity: u32, random_number: u32) {
let mut lazy_evaluation_result = Cacher::new(|num| {
println!("calculating slowly...");
thread::sleep(Duration::from_secs(2));
num
});
fn main(){}
|
|
Box::into_raw
Box::from_raw
|
fn main(){
let my_speed: Box = Box::new(88);
let my_speed_ptr: *mut i32 = Box::into_raw(my_speed);
unsafe {
let mut my_speed_two: Box = Box::from_raw(my_speed_ptr);
*my_speed_two+=1;
println!("{:?}",my_speed_two);// 89
}
let my_speed_addr = my_speed_ptr as usize;// преобразовать необработанный указатель в целое число
println!("address = {:X}", my_speed_addr);// 556BE0B489D0
// Взяв на себя ответственность за оригинальный `Box `, мы обязаны собрать его позже, чтобы он был уничтожен.
unsafe {
drop(Box::from_raw(my_speed_ptr));
}
}
fn main(){
let my_num: Box = Box::new(10);
let my_num_ptr: *const i32 = &*my_num;
let mut my_speed: Box = Box::new(88);
let my_speed_ptr: *mut i32 = &mut *my_speed;
}
|
|
|
фиксация указателя на память на месте, без возможности перемещаться с адреса
Эта концепция pinned «закрепления» необходима для реализации безопасных интерфейсов поверх таких вещей, как само референтные типы и интрузивные структуры данных (ссылаются сами на себя), которые в настоящее время невозможно смоделировать в полностью безопасном Rust с использованием только проверенных заимствований ссылок.
Pin - это тип, который используется для обозначения "закрепленного" (pinned) значения, которое гарантированно не будет перемещено в памяти.
Это важный инструмент для обеспечения безопасности в асинхронном программировании и работы с некоторыми типами данных, такими как Future и Generator.
Закрепление объекта в Rust гарантирует, что объект останется в фиксированном месте в памяти (важно для асинхронного программирования).
Закрепление может помочь предотвратить гонку данных и другие проблемы параллелизма, когда несколько задач обращаются к одним и тем же данным.
Закрепление также может помочь повысить производительность за счет сокращения копирования и перемещения кода при работе с асинхронными данными.
Закрепление гарантирует, что определенные типы данных всегда будут доступны в памяти, даже если компьютер заменит другие части программы.
Обратите внимание, что закрепление и Unpin влияет только на указываемый тип P::Target, а не на сам P тип указателя, который был обернут Pin<P>.
Например, то, является ли Box<T> Unpin или нет, не влияет на поведение Pin<Box<T>> (здесь T - это указываемый тип)
|
|
std::marker::PhantomPinned
|
std::marker::PhantomPinned — это маркерный тип в Rust, который используется для обозначения того, что тип не может быть перемещён в памяти после создания. Он играет важную роль в системе безопасности Rust, когда требуется гарантировать, что объект остаётся по тому же адресу в памяти на протяжении всего своего жизненного цикла.
Когда нужен PhantomPinned?
- Тип зависит от своего местоположения в памяти (например, при работе с FFI, указателями, привязкой к адресам памяти).
- Перемещение объекта в памяти может привести к некорректному поведению программы.
В Rust структуры по умолчанию являются перемещаемыми. Если структура содержит ссылки на себя (или указатели, зависящие от её адреса), перемещение объекта может нарушить их корректность. PhantomPinned помогает избежать этого, делая тип неперемещаемым.
|
|
Мутация данных находящихся за Box::pin
pinning-in-rust
|
use std::pin::Pin;
struct MyStruct {
value: u32,
_pin: PhantomPinned,
}
fn main() {
let mut my_struct: Pin> = Box::pin(MyStruct {
value: 10,
_pin: PhantomPinned,
});
println!("{}", my_struct.value);
unsafe {
let mut_ref: Pin<&mut MyStruct> = Pin::as_mut(&mut my_struct);
let mut_pinned: &mut MyStruct = Pin::get_unchecked_mut(mut_ref);
mut_pinned.value = 32;
}
println!("{}", my_struct.value);
}
|
|
Какие гарантии это дает? Как оно их реализует?
|
Безопасность при использовании асинхронных операций
В асинхронном коде, где множество задач может быть запущено параллельно, важно, чтобы определенные значения (например, переменные состояния) не перемещались в памяти. Это предотвращает ситуации, когда асинхронная операция завершается до того, как переменная, на которую она ссылается, будет готова.
Безопасность совместного использования: Закрепленное значение можно безопасно передавать между потоками и корутинами, так как его адрес в памяти остается постоянным.
Pin реализуется через специальную маркировку в типе, которая указывает, что это значение не должно перемещаться.
|
|
Как Unpin влияет на Pin? Что это значит?
|
Unpin — это маркерный трейт (marker trait), который указывает, что тип безопасно перемещать в памяти, даже если он был "закреплен".
По умолчанию, большинство типов в Rust автоматически реализуют Unpin.
Это означает, что такие типы могут быть перемещены, даже если они находятся внутри Pin.
|
|
Разрешено ли перемещать закрепленные данные после того, как Pin умрет? Почему?
Перемещение закрепленных данных становится разрешенным после того, как Pin умирает, потому что контракт Pin действует только до тех пор, пока Pin существует.
|
В Rust, когда объект обернут в Pin, он не может быть перемещен в памяти, пока он "закреплен".
Однако, когда Pin умирает (то есть выходит из области видимости и больше не существует), ограничения Pin больше не применяются.
use std::pin::Pin;
use std::marker::PhantomPinned;
struct MyStruct {
value: i32,
_marker: PhantomPinned, // Указывает, что MyStruct не реализует Unpin
}
impl MyStruct {
fn new(value: i32) -> Pin> {
Box::pin(MyStruct { value, _marker: PhantomPinned })
}
}
fn main() {
let mut pinned_value: Pin> = MyStruct::new(10);
// pinned_value не может быть перемещен в памяти
let value_ref: &i32 = &pinned_value.as_ref().get_ref().value;
println!("Pinned value: {}", value_ref);
// pinned_value выходит из области видимости и уничтожается
{
let mut unpinned_value = Box::new(MyStruct { value: 20, _marker: PhantomPinned });
// unpinned_value может быть перемещен, так как он не обернут в Pin
let value_ref: &i32 = &unpinned_value.value;
println!("Unpinned value: {}", value_ref);
}
// После этого pinned_value больше не существует, и его ограничения не применяются
}
|
|
Что такое structural pinning? Когда его следует использовать и почему?
|
Важно понимать, что в Rust два вида закрепления (pinning): structural pinning и proprietary pinning.
Structural pinning: Подразумевает, что сам объект и все его поля не могут быть перемещены после того, как объект был закреплен. Это достигается тем, что структура не реализует трейт Unpin, и, соответственно, гарантирует, что все её поля также не реализуют Unpin.
Proprietary pinning: Используется для более гибкого управления, когда только некоторые части объекта не могут быть перемещены, а другие могут. Обычно достигается через использование безопасных абстракций и методов.
Когда использовать Structural Pinning
Structural pinning следует использовать, когда:
1. Ссылка на внутренние поля: Когда структура хранит ссылки на свои собственные поля, перемещение всей структуры нарушит эти ссылки. В этом случае необходимо гарантировать, что вся структура остается на месте после закрепления.
2. Асинхронные операции: Когда структура используется в асинхронных задачах и её перемещение может привести к небезопасным ситуациям. Например, futures, которые хранят ссылки на свои внутренние данные.
3. Низкоуровневое программирование: В некоторых системных программах, например, при работе с аппаратными ресурсами или взаимодействии с C-кодом, требуется гарантировать, что определенные данные остаются на одном месте в памяти.
use std::pin::Pin;
use std::marker::PhantomPinned;
use std::ptr::NonNull;
struct MyStruct {
value: i32,
self_ref: Option>,
_marker: PhantomPinned,
}
impl MyStruct {
fn new(value: i32) -> Pin> {
let mut boxed = Box::pin(MyStruct {
value,
self_ref: None,
_marker: PhantomPinned,
});
let self_ref = NonNull::from(&*boxed);
// Safety: this is safe because the boxed value will never move.
unsafe {
let mut_ref = Pin::as_mut(&mut boxed);
Pin::get_unchecked_mut(mut_ref).self_ref = Some(self_ref);
}
boxed
}
fn get_self_ref(&self) -> &MyStruct {
unsafe { self.self_ref.unwrap().as_ref() }
}
}
fn main() {
let pinned = MyStruct::new(10);
println!("Value: {}", pinned.as_ref().get_ref().value);
println!("Self-ref Value: {}", pinned.as_ref().get_ref().get_self_ref().value);
}
|
|
Что такое Pin projection? Почему оно существует? Как он используется?
|
Pin projection — это процесс доступа к полям структуры, которая была закреплена (pinned) с помощью Pin, таким образом, чтобы гарантировать, что эти поля тоже остаются закрепленными и не могут быть перемещены.
Почему существует Pin Projection?
Pin projection существует для обеспечения безопасного доступа к внутренним полям структуры, которая была закреплена. Без этой возможности использование Pin было бы ограничено и сложно реализуемо, так как не было бы безопасного способа работать с внутренними полями закрепленных структур.
Как использовать Pin Projection?
Pin projection позволяет безопасно работать с полями структур, которые были обернуты в Pin, используя специальные методы и макросы. Рассмотрим пример на Rust, чтобы продемонстрировать, как это работает.
use std::pin::Pin;
use std::marker::PhantomPinned;
use std::ptr::NonNull;
struct Inner {
value: i32,
}
struct Outer {
inner: Inner,
_marker: PhantomPinned, // Указывает на то, что структура не реализует Unpin
}
impl Outer {
// Создает Pin>, который гарантирует, что объект не будет перемещен в памяти.
fn new(value: i32) -> Pin> {
Box::pin(Outer {
inner: Inner { value },
_marker: PhantomPinned,
})
}
// Пример проекции поля `inner` через безопасный метод
fn project_inner(self: Pin<&mut Self>) -> Pin<&mut Inner> {
// map_unchecked_mut Этот метод используется для проекции закрепленного (pinned) объекта на одно из его полей.
// Он позволяет безопасно получить доступ к внутренним полям, гарантируя, что они остаются закрепленными.
// Безопасно проецируем внутреннее поле `inner`, чтобы работать с ним, сохраняя гарантию неизменного местоположения в памяти.
unsafe {
self.map_unchecked_mut(|outer| &mut outer.inner)
}
}
}
fn main() {
let mut outer = Outer::new(42);
// Закрепляем структуру и проецируем поле
let mut inner: Pin<&mut Inner> = outer.as_mut().project_inner();
// Теперь можем безопасно использовать поле `inner`
println!("Inner value: {}", inner.value);
// Изменяем значение
inner.value = 50;
println!("Updated inner value: {}", outer.as_ref().inner.value);
}
|
|
самореферентные структуры
Когда это нужно?
- Кэширование (храним парс строки и ссылку на неё).
- Структуры с индексами или ссылками на свои же данные.
- Парсеры, библиотеки для JSON, XML, где удобно хранить ссылки на части входной строки.
|
Самореферентная структура — это структура, которая содержит ссылку на данные внутри самой себя. То есть у неё есть поле, которое указывает на другое её поле.
struct SelfRef<'a> {
text: String,
slice: Option<&'a str>, // ссылка на text внутри этой же структуры
}
impl<'_> SelfRef<'_> {
fn new(text: String) -> Self {
SelfRef { text, slice: None }
}
fn init(&mut self) {
let slice = &self.text[..];
self.slice = Some(slice); // ❌ ошибка!
// Потому что, чтобы сохранить ссылку на self.text, структура должна жить дольше самой ссылки. Но они живут одинаково, и borrow checker не может гарантировать безопасность.
}
}
Как решают проблему?
-
- Не использовать ссылки, а хранить индексы
-
- Использовать Pin + unsafe
-
- Использование умных указателей Arc или Rc
1. Не использовать ссылки, а хранить индексы:
struct SafeRef {
text: String,
slice_range: Option<(usize, usize)>,
}
impl SafeRef {
fn init(&mut self) {
self.slice_range = Some((0, 5));
}
fn get_slice(&self) -> &str {
let (start, end) = self.slice_range.unwrap();
&self.text[start..end]
}
}
2. Использовать Pin + unsafe
Самореферентные типы возможны через Pin, но это продвинутый путь:
Pin гарантирует, что объект не будет перемещён в памяти после инициализации.
Тогда ссылка внутри остаётся валидной.
use std::pin::Pin;
use std::marker::PhantomPinned;
struct SelfRef {
data: String,
reference: *const String,
_pin: PhantomPinned,
}
impl SelfRef {
fn new(text: &str) -> Pin<Box<Self>> {
let mut this = Box::pin(Self {
data: text.to_string(),
reference: std::ptr::null(),
_pin: PhantomPinned,
});
unsafe {
let mut_ref = Pin::as_mut(&mut this);
Pin::get_unchecked_mut(mut_ref).reference = &this.data;
}
this
}
}
fn main(){}
Использование crate ouroboros. Специализированный крейт для самореферентных структур:
use ouroboros::self_referencing;
#[self_referencing]
struct MyStruct {
text: String,
#[borrows(text)]
slice: &'this str,
}
fn main() {
let my = MyStruct::new("Hello World".to_string(), |text| &text[..5]);
println!("{}", my.borrow_slice()); // Hello
}
3. Использование умных указателей Arc или Rc
use std::rc::Rc;
struct SharedData {
data: Rc<String>,
reference: Rc<String>, // та же самая ссылка
}
impl SharedData {
fn new(text: &str) -> Self {
let data = Rc::new(text.to_string());
Self {
reference: Rc::clone(&data),
data,
}
}
}
fn main(){}
|
|
|
Создание Pin перекладывает на вас обязательство не делать перемещение (mem::replace) данных переданных в Pin по ссылке.
Но сам Pin уже можно перемещать так как он подписан на Unpin
Pin<P> гарантирует, что указатель любого типа указателя P имеет стабильное место в памяти, что означает, что он не может быть перемещен в другое место, а его память не может быть освобождена до тех пор, пока не будет удалена.
Мы говорим, что указатель «приколот»
Однако многие типы всегда можно свободно перемещать, даже если они закреплены, поскольку для них не требуется стабильный адрес. Сюда входят все основные типы (например bool, i32 ссылки), а также типы, состоящие исключительно из этих типов. Типы, которые не заботятся о закреплении, реализуют Unpin признак маркера, который отменяет эффект Pin. Для T: Unpin, Pin<Box<T>> и Box<T> функционируют одинаково, как Pin<&mut T> и &mut T.
Обратите внимание, что закрепление и Unpin влияет только на тип, на который указывает P::Target, а не на сам тип указателя, Pзаключенный в него Pin<P>. Например, то, является ли Box<T> это, Unpin не влияет на поведение Pin<Box<T>> (здесь T— тип, на который указывает).
Для низкоуровневого асинхронного кода или двусвязного антрузивного списка
Когда мы завернули данные в Pin мы можем его перемещать std::mem::replace так как Pin подписан на трейт std::marker::Unpin нельзя сами данные data перемещать
Если data реализует автотрейт std::marker::Unpin тогда мы получаем реализацию DerefMut для Pin<&mut data>
fn main(){
let mut data:u32 = 42;// u32 impl Unpin
let mut value_pin = Pin::new(&mut data);// или = unsafe{Pin::new_unchecked(&mut data);}
let old = std::mem::replace(&mut *value_pin,3);// мы можем это делать
println!("{} {}",old,value_pin);
assert_eq!(42,old);
assert_eq!(3,*value_pin);
}
Pin::new_unchecked - делает Pin для любого объекта даже если он не Unpin и мы обязуемся не двигать то что за ссылкой т.е. T
|
|
Trait std::marker::Unpin
struct.PhantomPinned
|
Однако многие типы всегда могут свободно перемещаться, даже когда они закреплены, потому что они не зависят от стабильного адреса.
Сюда входят все основные типы (например bool, i32 и ссылки), а также типы, состоящие только из этих типов.
Типы, которые не заботятся о закреплении, реализуют трейт- Unpin маркер, который отменяет эффект Pin<P>.
Ибо T: Unpin, Pin<Box<T>> и Box<T> функционируют одинаково, как Pin<&mut T> и &mut T
pub struct PhantomPinned;
Тип маркера, который не реализуется Unpin.
Если тип содержит PhantomPinned, он не будет реализован Unpin по умолчанию.
|
|
|
Учитывая следующие trait:
trait MutMeSomehow {
fn mut_me_somehow(self: Pin<&mut Self>);
}
trait SayHi: fmt::Debug {
fn say_hi(self: Pin<&Self>) {
println!("Hi from {:?}", self)
}
}
Реализовать их для следующих типов: Box<T>, Rc<T>, Vec<T>, String, &[u8], T.
use core::fmt;
use std::fmt::Debug;
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::rc::Rc;
struct Unmovable {
non_structural_field: String,
_pin: PhantomPinned,
}
impl Unmovable {
fn new(data: String) -> Pin<Box<Self>> {
let res = Unmovable {
non_structural_field: data,
_pin: PhantomPinned,
};
Box::pin(res)
}
fn get_non_pinned_field(self: Pin<&mut Self>) -> &mut String {
unsafe { &mut self.get_unchecked_mut().non_structural_field }
}
}
mod specific {
use super::*;
pub trait MutMeSomehow {
fn mut_me_somehow(self: Pin<&mut Self>);
}
impl MutMeSomehow for Unmovable {
fn mut_me_somehow(self: Pin<&mut Self>) {
*self.get_non_pinned_field() = "Mutating".into();
}
}
impl<T> MutMeSomehow for Box<T> where T: Unpin + Default {
fn mut_me_somehow(self: Pin<&mut Self>) {
let p = Pin::into_inner(self);
*p.as_mut() = T::default();
}
}
impl<T: Default> MutMeSomehow for Vec<T> {
fn mut_me_somehow(self: Pin<&mut Self>) {
let p = unsafe { Pin::get_unchecked_mut(self) };
if let Some(first_element) = p.first_mut() {
*first_element = T::default();
}
}
}
impl MutMeSomehow for String {
fn mut_me_somehow(self: Pin<&mut Self>) {
let s = Pin::into_inner(self);
s.clear();
}
}
impl MutMeSomehow for &[u8] {
fn mut_me_somehow(self: Pin<&mut Self>) {
let data = Pin::into_inner(self);
*data = [0; 3].as_ref();
}
}
pub trait SayHi: fmt::Debug {
fn say_hi(self: Pin<&Self>) {
println!("Hi from {:?}", self)
}
}
impl<T: Debug> SayHi for Box<T> {}
impl<T: Debug> SayHi for Rc<T> {}
impl<T: Debug> SayHi for Vec<T> {}
impl SayHi for String {}
impl SayHi for &[u8] {}
}
mod generic {
use super::*;
pub trait MutMeSomehow {
fn mut_me_somehow(self: Pin<&mut Self>);
}
impl<T> MutMeSomehow for T where T: Unpin + Default{
fn mut_me_somehow(self: Pin<&mut Self>) {
let p = Pin::into_inner(self);
*p = T::default();
}
}
pub trait SayHi: fmt::Debug {
fn say_hi(self: Pin<&Self>) {
println!("Hi from {:?}", self)
}
}
impl<T: Debug> SayHi for T {}
}
fn main() {
let mut v: Vec<u8> = vec![1, 2, 3];
let p = Pin::new(&mut v);
generic::SayHi::say_hi(p.as_ref());
specific::MutMeSomehow::mut_me_somehow(p);
println!("{:?}", v);
let mut s = &v[..];
let p = Pin::new(&mut s);
specific::MutMeSomehow::mut_me_somehow(p);
println!("{:?}", s);
}
|
|
|
pub mod second{
// Обратите внимание, что этот инвариант реализуется просто за счет невозможности вызова кода,
// который бы выполнял перемещение закрепленного значения. Это так, поскольку единственный способ получить
// доступ к этому закрепленному значению — через закрепление Pin<&mut T>> , что, в свою очередь, ограничивает наш доступ.
use std::marker::PhantomPinned;
use std::pin::Pin;
use std::pin::pin;
#[derive(Default)]
pub struct AddrTracker {
pub prev_addr: Option<usize>,
// удаляем автоматически реализуемую `Unpin`, которая помечает этот тип как имеющий некоторые
// адресно-зависимое состояние. Это важно для ожидаемого закрепления
// гарантирует работу и более подробно обсуждается ниже.
_pin: PhantomPinned,
}
impl AddrTracker {
fn check_for_move(self: Pin<&mut Self>) {
let current_addr = &*self as *const Self as usize;
println!("current_addr={}",current_addr);
match self.prev_addr {
None => {
// SAFETY: we do not move out of self
let self_data_mut = unsafe { self.get_unchecked_mut() };
self_data_mut.prev_addr = Some(current_addr);
},
Some(prev_addr) => assert_eq!(prev_addr, current_addr),
}
}
}
pub fn test(){
// 1. Создайте значение, еще не в адресно-зависимом состоянии.
let tracker = AddrTracker::default();
// 2. Закрепите Pin значение, поместив его за указателем закрепления pinning, таким образом поместив
// его в адресно-зависимое состояние
let mut ptr_to_pinned_tracker: Pin<&mut AddrTracker> = pin!(tracker);
assert_eq!(None,ptr_to_pinned_tracker.as_ref().prev_addr);
ptr_to_pinned_tracker.as_mut().check_for_move();
// Попытка получить доступ к трекеру или передать ptr_to_pinned_tracker всему, что требует
// изменяемый доступ к незакрепленной версии больше не будет компилироваться
// 3.Теперь мы можем предположить, что значение трекера никогда не будет перемещено, поэтому
// здесь никогда не будет паники!
ptr_to_pinned_tracker.as_mut().check_for_move();
}
}
|
|
Pin особенно полезен в ситуациях, где тип должен оставаться на одном месте в памяти для безопасного использования.
Один из таких случаев — это асинхронное программирование с использованием self-referential структур.
Например, давайте рассмотрим случай, когда нам нужно создать асинхронный генератор, который использует буфер.
|
use std::pin::Pin;
use std::future::Future;
use std::task::{Context, Poll};
use std::marker::PhantomPinned;
use tokio::time::{Duration, sleep};
use std::ptr::NonNull;
// Когда у вас есть структура, которая сама на себя ссылается,
// такой код небезопасен и его невозможно скомпилировать без использования Pin.
pub mod error{
use super::*;
// Содержит буфер и ссылку на этот буфер (некорректный код)
struct SelfReferential {
buffer: String,
// self_ref: Option<&str>, // Это вызовет ошибку, так как ссылаться на поле в той же структуре небезопасно
}
// Функция, которая возвращает future, асинхронно работающий с буфером
pub async fn example_async_function() {
let mut sr = SelfReferential {
buffer: "Hello".to_string(),
// self_ref: None, // Это вызовет ошибку
};
// sr.self_ref = Some(&sr.buffer); // Это вызовет ошибку
}
}
pub mod success{
use super::*;
// Структура, содержащая строковый буфер и ссылку на этот буфер, что делает её self-referential
struct SelfReferential {
buffer: String,
self_ref: Option>,// NonNull Небезопасный указатель на строку
_marker: PhantomPinned, // Маркер, чтобы гарантировать, что структура не будет перемещена
}
impl SelfReferential {
fn new(txt: &str) -> Pin> {
let sr = SelfReferential {
buffer: txt.to_string(),
self_ref: None,
_marker: PhantomPinned,
};
// Box::pin Функция, которая закрепляет значение в памяти, гарантируя, что оно не будет перемещено.
let mut boxed = Box::pin(sr);
let self_ref = NonNull::from(&boxed.buffer);
// Безопасно обновляем поле self_ref
unsafe {
let mut_ref = Pin::as_mut(&mut boxed);
Pin::get_unchecked_mut(mut_ref).self_ref = Some(self_ref);
}
boxed
}
}
// Пример асинхронной функции, использующей SelfReferential
pub async fn example_async_function() {
let sr = SelfReferential::new("Hello");
println!("Buffer: {}", sr.as_ref().get_ref().buffer);
// Используем `sleep` для симуляции асинхронной работы
sleep(Duration::from_secs(2)).await;
println!("Buffer after sleep: {}", unsafe { sr.as_ref().get_ref().self_ref.unwrap().as_ref() });
}
}
#[tokio::main]
async fn main() {
error::example_async_function().await;
// success::example_async_function().await;
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|