Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Тема
Описание
Доп.

Miri

Проверка заимствований в небезопасной среде

Экспериментальный интерпретатор промежуточного представления среднего уровня Rust (MIR). Он может запускать двоичные файлы и наборы тестов грузовых проектов и обнаруживать определенные классы неопределенного поведения, например:

Доступ к памяти за пределами памяти и использование после освобождения Недопустимое использование неинициализированных данных Нарушение внутренних предварительных условий (достижение unreachable_unchecked, вызов copy_nonoverlapping с перекрывающимися диапазонами, ...)

Недостаточно выровненные обращения к памяти и ссылки Нарушение некоторых инвариантов базового типа (например, логическое значение, отличное от 0 или 1, или недопустимый дискриминант перечисления) Экспериментальный вариант: нарушения правил Stacked Borrows, регулирующих использование псевдонимов для ссылочных типов.

Экспериментально: гонки данных (но без слабых эффектов памяти) Кроме того, Miri также сообщит вам об утечках памяти: когда в конце выполнения все еще выделена память, и эта память недоступна из глобальной статики, Miri выдаст ошибку.

...

Однако имейте в виду, что Miri не будет обнаруживать все случаи неопределенного поведения в вашей программе и не может запускать все программы.

rustup +nightly component add miri

cargo +nightly miri test

Unsafe Небезопасное ведет к ненадежному (unsound).

fn main(){
// `x` всегда должен указывать на свободную от гонки, действительную, выровненную, инициализированную память u8.
    unsafe fn unsafe_f(x: *mut u8) {
        my_native_lib(x);
    }
}

Unsound Ненадежный приводит к неопределенному (undefined). UB

Неопределенное поведение (UB)

Как уже упоминалось, unsafe код подразумевает особые обещания компилятору (unsafe иначе и не было бы необходимости). Неспособность выполнить какое-либо обещание заставляет компилятор создавать ошибочный код, выполнение которого приводит к UB. После запуска неопределенного поведения может случиться что угодно.

Коварно, эффекты могут быть

  1. незаметными,
  2. проявляться далеко от места нарушения или
  3. быть видимыми только при определенных условиях.

На первый взгляд работающая программа (включая любое количество модульных тестов) не является доказательством того, что код UB не может выйти из строя по прихоти. Код с UB объективно опасен, недействителен и никогда не должен существовать.

fn main(){
    if should_be_true() {
       let r: &u8 = unsafe { &*ptr::null() };    // После запуска ВСЕ приложение не определено. 
    } else {                                  
        println!("the spanish inquisition");      
    }
}

Undefined Неопределенный ведет к темной стороне силы. Unsound Code

Любой безопасный Rust, который может (даже теоретически) создавать UB для любого пользовательского ввода, всегда ненадежен. Как и unsafe код, который может вызывать UB по собственной инициативе, нарушая вышеупомянутые обещания. Необоснованный код представляет собой угрозу стабильности и безопасности и нарушает базовые предположения многих пользователей Rust.

#![allow(unused)]
fn main() {
fn unsound_ref<T>(x: &T) -> &u128 {      // Подпись выглядит безопасной для пользователей. 
    unsafe { mem::transmute(x) }         //  Бывает нормально, если запускается с & u128, UB практически для всего остального.
} 
}

Небезопасные сверхспособности

  • Выразить необработанный указатель *const T и *mut T (raw-указатели)
  • Вызовите небезопасную функцию или метод
  • Доступ или изменение изменчивой статической переменной static mut COUNTER: u32 = 0;
  • Внедрение небезопасного Trait

Вы также встретите ключевое слово unsafe, когда будете реализовывать интерфейс к чужому коду не на Rust. Идиоматичным считается написание безопасных обёрток вокруг небезопасных библиотек.

unsafe Ключевое слово только предоставляет доступ к этим четырем особенностей, которые затем не проверяются компилятором для безопасности памяти.

Вы все равно получите некоторую степень безопасности внутри небезопасного блока.

Неправильное использование unsafe кода ведет к Undefined Behavior непределенному поведению

Использование unsafe:

  • Разыменование необработанных указателей
  • Вызов unsafeфункций (включая функции C, встроенные функции компилятора и необработанный распределитель)
  • Реализуйте unsafe черты характера
  • Мутировать статику
  • Поля доступа unions

Найти ошибки в unsafe коде

habr/561012

cargo miri test

unsafe нужен когда мы разыменовываем сырой указатель, и когда хотим из сырого указателя получить ссылку и в блоке unsafe мы утверждаем что &mut T:

  • не null
  • T валидный живой объект
  • нет алиасинга

fn main(){
    // Ссылка приводится к указателю, это безопасно:
    let p: &mut T = ...;
    let p: *mut T = p;

    // Разыменовывание указателя требует unsafe :
    let p: *mut T = ...
    let p: T = unsafe { *p };

    // Каст указателя к ссылке требует unsafe :
    let p: *mut T = ...
    let p: &mut T = unsafe { &mut *p }; // важно тут получаем любой lifetime
}

Вызывая небезопасную функцию внутри unsafe блока, мы говорим, что мы прочитали документацию этой функции и берем на себя ответственность за поддержание контрактов функции, мы понимаем, как правильно ее использовать, и мы проверили, что мы выполняем контракт функции

unsafe fn dangerous() {}

unsafe Ключевое слово в этом контексте показывает , что функция имеет требование , мы должны соблюдать, когда мы вызываем эту функцию, потому что ржавчина не может гарантировать , что мы встречались эти требования. В отличии от безопасных абстракций небезопасного кода когда мы гарантируем его безопасность то unsafe блок ставится только внутри ф-ции.

unsafe {
    dangerous();
}


fn main(){
    // это объявление того, что функция небезопасна
    unsafe fn dangerous() {
        // страшные вещи
    }

    // это отметка небезопасного блока
    unsafe {
        // страшные вещи
    }

    // небезопасные типажи
    unsafe trait Scary { }

    // реализация (impl) таких типажей
    unsafe impl Scary for i32 {}

}

📌 Двоичная heap::sift_up

Создание объекта с деструктором который корректно освободит два ресурса на одну память

binaryheapsift_up

Зачем нужны raw ponters:

  • взаимодействие с кодом C;
  • создание безопасных абстракций, которые средство проверки займа не понимает;

Могут:

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

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

Разница между ними в том, что мутации не разрешены напрямую для const указателей, но мы можем легко преобразовать их в mut указатели, а затем изменить значение.

В отличие от ссылок и интеллектуальных указателей, необработанные указатели *const T и *mut T:

  • Разрешено игнорировать правила заимствования, имея как неизменяемые, так и изменяемые указатели или несколько изменяемых указателей в одно и то же место. Лишены сроков жизни в какой-либо форме, в отличие от &, и поэтому компилятор не может делать выводы о висячих указателях;
  • Не гарантируется указание на действительную память, и не гарантируют, что они являются ненулевыми указателями (в отличие от Box и &);
  • Разрешено быть нулевым
  • Не выполнять автоматическую очистку (Trait Drop), в отличие от Box, и поэтому требуют ручного управления ресурсами;
  • Это простые структуры данных (plain-old-data), то-есть они не перемещают право собственности, опять же в отличие от Box, следовательно, компилятор Rust не может защитить от ошибок, таких как использование освобождённой памяти (use- after-free);
  • не имеют никаких гарантий относительно псевдонимизации или изменяемости, за исключением изменений, недопустимых непосредственно для *const T
  • Отказавшись от того, чтобы Rust применял эти гарантии, вы можете отказаться от гарантированной безопасности в обмен на большую производительность или способность взаимодействовать с другим языком или оборудованием, если гарантии Rust не применяются.

Отличие &T , &mut T от *const T, *mut T

Отличие &T , &mut T от *const T, *mut T

&T , &mut T - Ссылки (указывает на объект который точно есть)

не может быть NULL гарантирует, что объект жив


fn main(){
    let mut x: i32 = 92;
    let r: &mut i32 = &mut 92; // явное взятие ссылки
    *r += 1; // явное разыменовывание ссылки
}

*const T, *mut T - Указатели (указывает куда-то в память)

  • могут быть NULL *не гарантируют, что объект жив *разыменовывание указателя— unsafe операция *встречаются редко (и продвинутые структуры данных)


fn main(){
    let my_num: i32 = 10;
    let my_num_ptr: *const i32 = &my_num;

    let mut my_speed: i32 = 88;
    let my_speed_ptr: *mut i32 = &mut my_speed; 

// Чтобы получить указатель на значение в Box, разыменуйте поле:

    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;
}

явное приведение *const

неявное принуждение *mut


fn main(){
    // явное приведение
    let mut y = 10;
    let raw_mut = &mut y as *mut i32;
    //  ----------------------------------------- 
    let i: u32 = 1;

    // явное приведение
    let p_imm: *const u32 = &i as *const u32;

    // неявное принуждение
    let mut m: u32 = 2;
    let p_mut: *mut u32 = &mut m;

    // Чтобы получить указатель на значение. Разыменование с помощью конструкции `&*x` является более предпочтительным, чем с использованием transmute.
    unsafe {
        let ref_imm: &u32 = &*p_imm;
        let ref_mut: &mut u32 = &mut *p_mut;
    }
}

Rust предоставляет необработанные указатели , *const Tи *mut T Вы можете преобразовать usize в эти типы, а затем разыменовать или создать ссылку Rust на это значение в небезопасном блоке.

Ссылки в Rust автоматически гарантируют, что они всегда действительны. т.е. если вы используете его в безопасном Rust, то значение, на которое он ссылается, всегда будет правильно инициализировано, и оно не будет удалено. Как только вы имеете дело с необработанными указателями и адресами памяти, вы сами несете ответственность за это и можете вызвать довольно серьезные проблемы, сделав это неправильно.

Если у вас есть значение в стеке, вы можете получить его так:


fn main() {
    let value = 123;
    let value_ref = &value;  // Получить ссылку на значение
    let value_raw_ptr = value_ref as *const i32;// преобразовать ссылку в необработанный указатель
    let value_addr = value_raw_ptr as usize;// Преобразовать необработанный указатель в целое число
    println!("address = {:X}", value_addr);
}

Если значение выделяется в куче, то обычно существует метод получения необработанного указателя. Например, Box и Rc имеют into_raw() метод while, String и Vec имеют as_ptr():


fn main() {
    let my_vec = vec![1, 2, 3];
    let my_vec_ptr = my_vec.as_ptr();// получить необработанный указатель указателя на данные
    let my_vec_addr = my_vec_ptr as usize;// преобразовать необработанный указатель в целое число
    println!("address = {:X}", my_vec_addr);
}

Получение такого адреса совершенно безопасно. Однако вам нужно будет использовать unsafe Rust, чтобы делать с ним что-нибудь полезное. Вы должны убедиться, что значение по этому адресу действительно при каждом обращении к нему.

Не гарантируют, что они указывают на действительную область памяти


fn main(){
// Создание сырого указателя совершенно безопасно:
    let x = 5;
    let raw = &x as *const i32;

// А вот его разыменование не является безопасным.
    let points_at = unsafe { *raw };
    println!("raw points at {}", points_at);
}

Цель состоит в том, что базовые данные действительны только для жизни 'a, поэтому Slice не должны переживать 'a. Однако это намерение не выражено в коде, поскольку нет использования времени жизни, 'a, следовательно, неясно, к каким данным оно относится. Мы можем исправить это, указав компилятор, чтобы действовать, как если Slice структура содержит ссылку &'a T


use std::marker::PhantomData;

struct Foo3<'a, T: 'a> {
    start: *const T,
    end: *const T,
    phantom: PhantomData<&'a T>,
}
fn main(){
    let vec = vec![1,2,3];
    let ptr = vec.as_ptr();
    Foo3 {
        start: ptr,
        end: unsafe { ptr.offset(vec.len() as isize) },
        phantom: PhantomData,
    };
}

Выразить необработанный указатель *const T и *mut T (raw-указатели)


fn main(){
    let mut num = 5;

    let r1 = &num as *const i32;
    let r2 = &mut num as *mut i32;

    unsafe {
        println!("r1 is: {}", *r1); // оператор разыменования *
        println!("r2 is: {}", *r2);
    }
}

Мы создали *const i32 и *mut i32 необработанные указатели, которые указывали на то же место памяти и можем изменить данные с помощью изменяемого указателя, потенциально создающего гонку данных.

Если бы мы вместо этого пытались создать неизменяемую и изменяемую ссылку num, код не был бы скомпилирован, потому что правила владения Rust не допускают изменчивую ссылку одновременно с любыми неизменяемыми ссылками.



fn main(){
    &vec![42_u8] as *const String // ошибка: неверное преобразование
    &vec![42_u8] as *const Vec as *const String; // так можно
}

Преобразовать неизменяемую ссылку &T в изменяемою &mut T

fn very_trustworthy(shared: &i32) {
    unsafe {
        // Преобразовать разделяемую ссылку в изменяемый указатель.
        // Это неопределенное поведение.
        let mutable = shared as *const i32 as *mut i32;
        *mutable = 20;
    }
}

#[derive(Debug)]
struct T{
    data:i32
}
fn main() {
    let t = T{data:8};
    let shared = &t;
    unsafe {
        let mutable = shared as *const T as *mut T;
        (*mutable).data=7;
    }
    assert_eq!(7,t.data);
}

Принуждение от неизменяемой ссылки &T в *const без unsafe

Так устроена внутренняя изменчивость Cell

nomicon/transmutes

interior-mutability-behind-the-curtain

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

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

T:Copy необходим иначе это приведет к созданию изменяемых ссылок с псевдонимом и нарушению правил псевдонима


use std::fmt::Debug;
#[derive(Debug)]
struct A{
    value:T
}
impl A where T:Debug+Copy {
 fn get(&self) -> *mut T {
    &self.value as *const T as *mut T
 }
}
fn main() {
    let a = A{value:12};
    let res:*mut i32 = a.get();// 0x7fff1b795db0
    unsafe{ 
        println!("{:?}",*res);// 12
        *res = 3;
    }
    println!("{:?}",a);// A { value: 3 }
}

Создание безопасной абстракции над небезопасным кодом


// Пример реализации ф-ции split_at_mut для среза i32
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();

    assert!(mid <= len);// мы проверяем что разделитель попадает в размер среза 
     // Rust компилятор не допускает заимствования среза дважды хотя мы знаем что срезы не пересекаются
    (&mut slice[..mid], &mut slice[mid..])
}

// Перепишем на unsafe реализацию где мы сами гарантируем непересекаемость срезов
use std::slice;
fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    let ptr:*mut i32 = slice.as_mut_ptr();// доступ к необработанному указателю среза с типом *mut i32

    assert!(mid <= len);

    unsafe {
        (slice::from_raw_parts_mut(ptr, mid),
         slice::from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
    }
//from_raw_parts_mut для создания среза, который начинается с ptr и mid длится долго.
// Затем мы вызываем offset метод on ptrс mid аргументом для получения исходного указателя, который начинается с mid,
// и мы создаем срез, используя этот указатель, и оставшееся количество элементов после midдлины.
}
// Обратите внимание, что нам не нужно отмечать результирующую split_at_mut функцию как unsafe, и мы можем назвать эту функцию безопасным Rust.
// Мы создали безопасную абстракцию для небезопасного кода с реализацией функции, которая безопасно использует unsafe код, 
//поскольку она создает только действительные указатели из данных, к которым имеет доступ эта функция
}
fn main(){
    let mut vec_to_split = vec![1, 2, 3, 4, 5, 6];
    let ref_to_vec = &mut vec_to_split[..];
    let (part_1, part_2) = split_at_mut(ref_to_vec,3);
    assert_eq!(part_1, &mut [1, 2, 3]);
    assert_eq!(part_2, &mut [4, 5, 6]);
}

не является безопасной абстракцией

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


use std::slice;
fn main(){
    let address = 0x012345usize;
    let r = address as *mut i32;

    let slice = unsafe {
        slice::from_raw_parts_mut(r, 10000)
    };
}

Преобразование *mut в usize и обратно в мутирующие данные

Так можно передать mut Vec<T> в многопоточную среду, так как usize просто Copy ,а мы из него обратно получаем mut Vec<T>


fn main(){
     let mut arr:Vec = vec![1,2,3,4,5];
     let ptr:*mut Vec = &mut arr;
     
     let addr:usize = ptr as usize;
     
     let mut ptr2:*mut Vec = addr as *mut Vec;
     let mut arr2:&mut Vec = unsafe {&mut *ptr2};
     arr2[4]=9;
     println!("{:?}",arr);
     assert_eq!(arr,*arr2);
}

так можно передать mut Vec<T> в многопоточную среду


fn main(){
    let mut arr:Vec = vec![1,2,3,4,5];
    let ptr:*mut Vec = &mut arr;
     
    let addr:usize = ptr as usize;
   
    let mut buff = vec![]; 
    let h1 = std::thread::spawn(move ||{
       let mut ptr2:*mut Vec = addr as *mut Vec;
       let mut arr2:&mut Vec = unsafe {&mut *ptr2};
       arr2[4]=9;
    });
    buff.push(h1);
    let h2 = std::thread::spawn(move ||{
       let mut ptr2:*mut Vec = addr as *mut Vec;
       let mut arr2:&mut Vec = unsafe {&mut *ptr2};
       arr2[3]=8;
    });
    buff.push(h2);
    for h in buff{
        h.join().unwrap();
    }
    assert_eq!(vec![1, 2, 3, 8, 9],arr);
}

Get it from C


extern crate libc;
use std::mem;
fn main() {
    unsafe {
        let my_num: *mut i32 = libc::malloc(mem::size_of::()) as *mut i32;
        if my_num.is_null() {
            panic!("failed to allocate memory");
        }
        libc::free(my_num as *mut libc::c_void);
    }
}

FFI Использование extern функций для вызова внешнего кода


extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("Absolute value of -3 according to C: {}", abs(-3));
    }
}

// Вызов функций ржавчины с других языков
// Мы также можем использовать externдля создания интерфейса, который позволяет другим языкам вызывать функции Rust
//В следующем примере мы делаем call_from_cфункцию доступной из C-кода, после того как она скомпилирована в общую библиотеку и связана с C:

#[no_mangle]
pub extern "C" fn call_from_c() {
    println!("Just called a Rust function from C!");
}

Доступ или изменение изменчивой статической переменной

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


static mut COUNTER: u32 = 0;

fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}
// Этот код компилируется и печатает, COUNTER: 3 как и следовало ожидать, потому что он однопоточный. 
// Наличие нескольких потоков доступа к COUNTER, вероятно, приведет к гонке данных.
fn main() {
    add_to_count(3);

    unsafe {
        println!("COUNTER: {}", COUNTER);
    }
}

Внедрение небезопасного Trait

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

unsafe trait Foo {
    // methods go here
}

// Используя unsafe impl, мы обещаем, что мы будем поддерживать инварианты, которые компилятор не может проверить
unsafe impl Foo for i32 {
    // method implementations go here
}

Если мы реализуем тип, который содержит тип, который не является Send или Sync, например, необработанные указатели, и мы хотим отметить этот тип как Send или Sync, мы должны использовать unsafe.

Rust не может подтвердить, что наш тип поддерживает гарантии того, что он может быть безопасно отправлен по потокам или доступен из нескольких потоков, поэтому мы должны сделать эти проверки вручную и указать как таковые unsafe.

  • Создание и использование постоянных исходных указателей *const T
  • Создание и использование изменяемых исходных указателей *mut T
  • Инициализация исходного указателя на нулевой ptr::null, ptr::null_mut
  • Отображение исходных указателей

learntutorials.net/7270


fn main(){
    let raw_ptr = &pointee как *const type // создать постоянный необработанный указатель на некоторые данные
    let raw_mut_ptr = &mut pointee как *mut type // создать изменяемый необработанный указатель на некоторые изменчивые данные
    let deref = *raw_ptr // разыменовать необработанный указатель (требуется небезопасный блок)
}

Создание и использование постоянных исходных указателей


fn main(){
    // Возьмем произвольный фрагмент данных, в данном случае 4-байтовое целое число
    let some_data: u32 = 14;

    // Создайте постоянный необработанный указатель, указывающий на данные выше
    let data_ptr: *const u32 = &some_data as *const u32;

    // Примечание: создание необработанного указателя полностью безопасно, но для разыменования необработанного указателя требуется небезопасный блок
    unsafe {
        let deref_data: u32 = *data_ptr;
        println!("Dereferenced data: {}", deref_data);
    }
}

Создание и использование изменяемых исходных указателей


fn main(){

    // Возьмем изменяемый фрагмент данных, в данном случае 4-байтовое целое число.
    let mut some_data: u32 = 14;

    // Создайте изменяемый необработанный указатель, указывающий на данные выше
    let data_ptr: *mut u32 = &mut some_data as *mut u32;

    // Примечание: создание необработанного указателя полностью безопасно, но для разыменования необработанного указателя требуется unsafe block
    unsafe {
        *data_ptr = 20;
        println!("Dereferenced data: {}", some_data);
    }
}

Инициализация исходного указателя на нулевой. В отличие от обычных ссылок Rust, raw-указатели могут принимать нулевые значения.


use std::ptr;
fn main(){
    // Create a const NULL pointer
    let null_ptr: *const u16 = ptr::null();

    // Create a mutable NULL pointer
    let mut_null_ptr: *mut u16 = ptr::null_mut();

    Цепной разыменования
    Как и в C, Rust raw указатели могут указывать на другие необработанные указатели (которые, в свою очередь, могут указывать на дополнительные raw-указатели).

    // Возьмите обычный slice string 
    let planet: &str = "Earth";

    // Создайте постоянный указатель, указывающий на наш string slice
    let planet_ptr: *const &str = &planet as *const &str;

    // Создайте постоянный указатель, указывающий на указатель
    let planet_ptr_ptr: *const *const &str = &planet_ptr as *const *const &str;

    // Это может продолжаться ...
    let planet_ptr_ptr_ptr = &planet_ptr_ptr as *const *const *const &str;

    unsafe {
        // Direct usage
        println!("The name of our planet is: {}", planet);
        // Single dereference
        println!("The name of our planet is: {}", *planet_ptr);
        // Double dereference
        println!("The name of our planet is: {}", **planet_ptr_ptr);
        // Triple dereference
        println!("The name of our planet is: {}", ***planet_ptr_ptr_ptr);
    }
}

Отображение исходных указателей. У Rust есть форматирование по умолчанию для типов указателей, которые могут использоваться для отображения указателей.


use std::ptr;
fn main(){
// Создайте некоторые данные, необработанный указатель, указывающий на них, и нулевой указател
    let data: u32 = 42;
    let raw_ptr = &data as *const u32;
    let null_ptr = ptr::null() as *const u32;

//  {: p} показывает значения указателей как шестнадцатеричные адреса памяти
    println!("Data address: {:p}", &data);
    println!("Raw pointer address: {:p}", raw_ptr); 
    println!("Null pointer address: {:p}", null_ptr);

// Это выведет что-то вроде этого:
// Data address: 0x7fff59f6bcc0
// Raw pointer address: 0x7fff59f6bcc0
// Null pointer address: 0x0
}

Пример как реализованна функция смещения по указателю на элемент массива


/*
fn offset(self: *const T, count: isize) -> *const T
                where T: Sized
{
                let bytes_per_element = std::mem::size_of::() as isize;
                let byte_offset = count * bytes_per_element;
                (self as isize).checked_add(byte_offset).unwrap() as *const T
}
*/
fn main() {
    let array = [1, 2, 3, 4, 5];
    let ptr = array.as_ptr(); // Получаем указатель на первый элемент
    
    unsafe {
        // Вызываем функцию offset
        let third_element_ptr = ptr.offset(2); // Указатель на третий элемент (индекс 2)
    
    
        let value = *third_element_ptr;
        println!("Третий элемент: {}", value); // Выведет: 3
    }
}


#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let points = [
        Point { x: 1, y: 2 },
        Point { x: 3, y: 4 },
        Point { x: 5, y: 6 },
    ];
    
    let ptr = points.as_ptr();
    unsafe {
        // Получаем указатель на второй элемент
        let second_point_ptr =  ptr.offset(1); 

        println!("Второй элемент: {:?}", *second_point_ptr); // Выведет: Point { x: 3, y: 4 }
   
        // Можно использовать отрицательные значения
        let first_again_ptr = second_point_ptr.offset(-1);
    
        println!("Снова первый: {:?}", *first_again_ptr); // Выведет: Point { x: 1, y: 2 }
    }
}

Доступ к приватным полям структуры через *mut указатель

без выравнивания #[repr(C)] и с выравниванием


use t::{T,R};
mod t{
    //#[repr(C)]
    #[derive(Debug)]
    pub struct T(u8,u8,u8);
    impl T{
        pub fn new(data:u8)->Self{
            Self(data,data,data)
        }
        pub fn get(&self)->&u8{
            &self.1
        }
    }
    //#[repr(C)] 
    #[derive(Debug)]
    pub struct R{data:u8,data2:u32,data3:u16 }
    impl R {
        pub fn new(data:u8)->Self{
            Self{data:data,data2:data as u32,data3:data as u16 }
        }
    }
}
fn main() {
    let t = T::new(8u8);
    let shared = &t;
    unsafe {
        let mutable = shared as *const T as *mut T;
         
        println!("read = {:?}",mutable.read());// read = T(8, 8, 8)
        // Установит все поля одним значением
        std::ptr::write_bytes(mutable, 255u8, 1);
        println!("{:p}\n{:X}\n{:?}",mutable,mutable as usize,*mutable);
        // 0x7ffeac8d5e98 
        // 7FFEAC8D5E98 
        // T(255, 255, 255)
        
        // Точечно можно попасть в поле если взять ссылку на значение
        let g:&u8 = t.get();
        let mutable = g as *const u8 as *mut u8;
        println!("read = {:?}",mutable.read());// read = 255
        *mutable = 72u8;
        println!("{:?}",t);// T(255, 72, 255)
        // Установить первые поля 
        let mutable2 = shared as *const T as *mut u8;
        std::ptr::write_bytes(mutable2, 30u8, 2); 
        println!("{:?}",t);// T(30, 30, 255)
        // Точечно можно попасть в поле
        let slice = std::ptr::slice_from_raw_parts_mut(mutable2,3);
        (*slice)[1] = 99u8;
        println!("{:?}",t);// T(30, 99, 255)
    }
    // #################################################################3
    
     let r = R::new(2u8);
     let shared = &r;
     unsafe {
        let mutable = shared as *const R as *mut u8;
        // Из-за того что структура R без #[repr(C)] т.е. данные выравниваются и первым становится поле data2:u32, вторым u16
        // Далее первые 4 байта это одно поле u32 self.data2
        // т.е. первый байт (*slice)[0] т.е. 8 бит это значения от 0-255
        let slice = std::ptr::slice_from_raw_parts_mut(mutable,7);// u32 4 байта + u8 1 байт + u16 2 байта = 7 байт
        (*slice)[0] = 0u8;// self.data2 u32 Если тут 1u8 = 1 (от 0-255)
        (*slice)[1] = 1u8;// self.data2 u32 Если тут 1u8 = 256 (от 256-65535)
        (*slice)[2] = 0u8;// self.data2 u32 Если тут 1u8 = 65536
        (*slice)[3] = 0u8;// self.data2 u32 Если тут 1u8 = 16777216 (В итоге 0000.0000.0000.0001.0000.0000=256 )
        (*slice)[4] = 2u8;// self.data3 u16 
        (*slice)[5] = 1u8;// self.data3 u16 Если тут 1u8 = 256 ( В итоге 0000.0001.0000.0010 =2+256=258)
        (*slice)[6] = 31u8;// self.data u8 ( В итоге 0001.1111 =1+2+4+8+16=31)
        println!("slice={:?} ",(*slice)[0] );
     }
     println!("R={:?}",r);// R { data: 31, data2: 256, data3: 258 }
     println!("size_of_val={:?}",std::mem::size_of_val(&r));//8
     println!("size_of={:?}",std::mem::size_of::());//8
    println!("{:032b}\n{:09b}", 256u32,31u8);
}

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


#[derive(Clone,Debug)] 
struct Foo(i32);
/*
impl Drop for Foo {
    fn drop(&mut self) {
        println!("exit");
    }
}
*/

// без необработанного указателя нельзя вернуть ссылку на локальную переменную
// таким образом мы обходим ограничение что-бы сработал деструктор локального обьекта
fn test_ptr() -> *mut Foo{
  let mut local_foo = Foo(2);
  let var_ref = &mut local_foo;  
  let var_ref_raw_ptr = var_ref as *mut Foo;
  unsafe{ 
    (*var_ref_raw_ptr).0+=1i32;
    println!("befor drop foo = {:?} addr:{:p}", *var_ref_raw_ptr,var_ref_raw_ptr);
  }
   var_ref_raw_ptr
}

fn main() {
 // При взятии необработанного указателя, на программиста возлагается ответственность освободить память 
 // Но так как мы закоментировали реализацию Drop для Foo то после выхода из области видимости локальной переменной local_foo
 // память не освобождается и мы можем использовать ее данные через ее адресс
  let ptr = test_ptr();
  unsafe{ 
   println!("after drop foo = {:?} addr:{:p}", *ptr,ptr);
   assert_eq!(3,(*ptr).0);
  }
  //println!("address = {:X}", ptr as usize);// 7FFC8F9C8FF0
  //println!("address = {:p}", ptr );// 0x7ffc8f9c8ff0

  let foo:&Foo = unsafe{ &*ptr };
  println!("foo = {:?}", foo );
  assert_eq!(3,(*foo).0); 
}

Трюк упаковки бит

В этом коде используется тот факт, что многие типы необходимо размещать по четному адресу в памяти: поскольку младший бит четного адреса всегда равен нулю, мы можем сохранить в нем все что угодно, а затем надежно реконструировать исходный адрес, просто замаскировав младший бит. Не все типы подходят для такого трюка; например, типы u8 и (bool, [i8; 2]) можно разместить по любому адресу

Книга: "Блэнди Дж., Орендорф Дж. - Программирование на языке Rust" 495 стр

Определен тип RefWithFlag<'a,T>, в котором хранятся значения типа &'a T и bool, упакованные в кортеж (&'a T,bool) , и тем не менее он занимает всего одно машинное слово, а не два!


mod ref_with_flag {
    use std::marker::PhantomData;
    use std::mem::align_of;
    /// `&T` и `bool`, упакованные в одно слово.
    /// Тип `T` должен быть выровнен хотя бы на границу двух байтов.
    ///
    /// Если вы относитесь к числу программистов, которые и хотели бы
    /// заимствовать младший бит указателя, да не знали как, то теперь можете
    /// сделать это безопасно!
     /// ("Но так совсем не интересно...")
    pub struct RefWithFlag<'a, T: 'a> {
         ptr_and_bit: usize,
         behaves_like: PhantomData<&'a T> // не занимает места
    }
    
    impl<'a, T: 'a> RefWithFlag<'a, T> {
        pub fn new(ptr: &'a T, flag: bool) -> RefWithFlag {
            assert!(align_of::() % 2 == 0);
            RefWithFlag {
                ptr_and_bit: ptr as *const T as usize | flag as usize,
                behaves_like: PhantomData
            }
        }
        pub fn get_ref(&self) -> &'a T {
            unsafe {
                let ptr = (self.ptr_and_bit & !1) as *const T;
                &*ptr
            }
        }
        pub fn get_flag(&self) -> bool {
                self.ptr_and_bit & 1 != 0
        }
    }
}

use ref_with_flag::RefWithFlag;
fn main(){
    let vec = vec![10, 20, 30];
    let flagged = RefWithFlag::new(&vec, true);
    assert_eq!(flagged.get_ref()[1], 20);
    assert_eq!(flagged.get_flag(), true);
}

#[derive(Clone,Debug)] 
struct Foo(i32);
/* impl Drop for Foo {fn drop(&mut self) { println!("exit"); }} */

// без необработанного указателя нельзя вернуть ссылку на локальную переменную
// таким образом мы обходим ограничение что-бы сработал деструктор локального обьекта
fn test_ptr() -> *mut Foo{
  let mut local_foo = Foo(2);
  let var_ref = &mut local_foo;  
  let var_ref_raw_ptr = var_ref as *mut Foo;
  unsafe{ 
    (*var_ref_raw_ptr).0+=1i32;
    println!("befor drop foo = {:?} addr:{:p}", *var_ref_raw_ptr,var_ref_raw_ptr);
  }
   var_ref_raw_ptr
}

fn main() {
 // При взятии необработанного указателя, на программиста возлагается ответственность освободить память 
 // Но так как мы закоментировали реализацию Drop для Foo то после выхода из области видимости локальной переменной local_foo
 // память не освобождается и мы можем использовать через ее адресс ее данные
  let ptr = test_ptr();
  unsafe{ 
   println!("after drop foo = {:?} addr:{:p}", *ptr,ptr);
   assert_eq!(3,(*ptr).0);
  }
  //println!("address = {:X}", ptr as usize);// 7FFC8F9C8FF0
  //println!("address = {:p}", ptr );// 0x7ffc8f9c8ff0

  let foo:&Foo = unsafe{ &*ptr };
  println!("foo = {:?}", foo );
  assert_eq!(3,(*foo).0); 
   
//String
   let mut s:String = "Hello".to_string();
   let mut s = std::mem::ManuallyDrop::new(s);// Так как реализация Drop уже есть, то нам нужно запретить автоматическое удаление данных
   
   let ptr:*mut u8 = s.as_mut_ptr();
   let len = s.len();
   let capacity = s.capacity();
    
    unsafe{
        for i in 0..len{
           // мутируем строку
           // изменим регистр первого символа с H => h
            *ptr.add(0) = *(String::from_utf8(vec!(*ptr.add(0))).unwrap()).to_ascii_lowercase().as_bytes().first().unwrap();
            println!("{}", *ptr.add(i) );
        }
    }
    println!("address = {:p}", ptr );
   
   // Восстановление строки
    let s = unsafe {
        let v:Vec = Vec::from_raw_parts(ptr, s.len(),s.capacity());
        //println!("{:?}", v );//[72, 101, 108, 108, 111]
        String::from_utf8( v ).unwrap()
    };
    assert_eq!(String::from("hello"), s);
    
   // или так
   let s = unsafe { String::from_raw_parts(ptr, len, capacity) } ;
   assert_eq!(String::from("hello"), s);

// Vec
    let mut my_vec: Vec = vec![-1, 2, 3];
    let my_vec_ptr = my_vec.as_mut_ptr();// получить необработанный указатель указателя на данные
    let (len,cap) = (3,3);
    //let (my_vec_ptr, len, cap) = my_vec.into_raw_parts();// получить необработанный указатель указателя на данные, емкость и длину 
    let new_vec: Vec = unsafe{
        // можем мутировать данные
        for i in 0..len as i32 {
            std::ptr::write(my_vec_ptr.offset(i as isize), 4 + i);
        }
        // или так
        *my_vec_ptr.add(2) = 222_i32;
        // отдадим необработанный указатель обратно для освобождения занимаемой им памяти после выхода из области видимости (RAII)  
        Vec::from_raw_parts(my_vec_ptr, len,cap)
    };
    println!("new_vec = {:?}", new_vec);// [4, 5, 222]
    let my_vec_addr = my_vec_ptr as usize;// преобразовать необработанный указатель в целое число
    println!("address = {:X}", my_vec_addr);// 556BE0B489D0

// Box
    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));
    }
}

Вручную управляйте памятью с помощью необработанных указателей.


#![feature(ptr_internals, allocator_api)]
use std::alloc::{Allocator, Global, GlobalAlloc, Layout};
use std::mem;
use std::ptr::{drop_in_place, NonNull, Unique};

struct Box{ ptr: Unique }

impl Drop for Box {
    fn drop(&mut self) {
        unsafe {
            drop_in_place(self.ptr.as_ptr());
            let c: NonNull = self.ptr.into();
            Global.deallocate(c.cast(), Layout::new::())
        }
    }
}

//📌  !!! ЧТО ЭТО, ЗАЧЕМ !!!!
// Это оболочка на `*mut T` но T ограничен оболочкой для произвольных преобразований
fn main(){

}

std::ptr::NonNull позволяет компилятору оптимизировать None в Null

(помимо гарантии не нулевого значения)


#![allow(dead_code)]
use std::mem;
use std::ptr::NonNull;

struct Foo {
    a: *mut u64,
    b: *mut u64,
}
struct FooUsingNonNull {
    a: *mut u64,
    b: NonNull<*mut u64>,
}
fn main() {
    println!("NonNull allows `Option`'s discriminant to be collapsed into the pointer:");
    println!("*mut u64: {} bytes", mem::size_of::<*mut u64>());
    println!("Option<*mut u64>: {} bytes", mem::size_of::>());
    println!("Option>: {} bytes", mem::size_of::>>());
    
    println!("\nIt even works transitively:");
    println!("Option: {} bytes", mem::size_of::>());
    println!("Option: {} bytes", mem::size_of::>());
}

std::ptr::null_mut

ptr/fn.null_mut

Указатель на mut null


fn main(){
    let p: *mut i32 = std::ptr::null_mut();
    assert!(p.is_null());
}

std::ptr::write_bytes

инициализировать массив значений , вызвав функцию std::mem::write_bytes (эквивалент C memset в Rust)

ptr/fn.write_bytes


use std::ptr;
fn main(){
    let mut vec = vec![0u32; 4];
    unsafe {
        let vec_ptr = vec.as_mut_ptr();
        std::ptr::write_bytes(vec_ptr, 254, 2);// заполнить первых два значения u32 полностью значенеиями 254u8
    }
    assert_eq!(4278124286,u32::from_be_bytes([254,254,254,254]));
    assert_eq!(vec, [4278124286, 4278124286, 0, 0]);
}

std::ptr::read(src)

std::ptr::write(dest, value)

std::ptr::read(src) передает владение значением в области, на которую указывает src, вызывающей стороне. После вызова read память *src следует считать неинициализированной. Аргумент src должен быть простым указателем типа *const T, где T – размерный тип

std::ptr::write(dest, value) перемещает значение value в область, на которую указывает dest . До вызова эта область должна быть неинициализированной. Значением теперь владеет объект, на который направлен указатель. Аргумент dest должен быть простым указателем типа *mut T, а value – иметь тип T, где T – размерный тип.

Функции read_unaligned и write_unaligned похожи на read и write, но указатель необязательно должен быть выровнен, как обычно требуется для типов объектов, на которые ведет указатель.

std::ptr::copy(src, dst, count) перемещает массив count значений из области с начальным адресом src в область с начальным адресом dst , как если бы это делалось в цикле, где read и write попеременно вызываются для перемещения одного значения. Конечная область памяти перед вызовом должна быть неинициализированной, а после вызова неинициализированной становится исходная область. Аргументы src и dest должны быть простыми указателями типа *const T и *mut T соответственно, count должен иметь тип usize ;

std::ptr::copy_nonoverlapping(src, dst, count) похожа на соответствующий вызов copy , но ее контракт дополнительно требует, чтобы исходная и конечная области памяти не пересекались. Операция может оказаться немного быстрее, чем copy