могут быть null не реализуют автоматическую очистку памяти
Отказавшись от этих гарантий, вы можете обменять безопасность на большую производительность или возможность взаимодействия с другим языком или оборудованием, где гарантии Rust не применяются.
В листинге 19-1 показано, как создать неизменяемый и изменяемый сырой указатель из ссылок.
Листинг 19-1. Создание необработанных указателей из ссылок
Обратите внимание, что мы не используем ключевое слово unsafe в этом коде. Можно создавать сырые указатели в безопасном коде; мы просто не можем разыменовывать сырые указатели за пределами небезопасного блока, как вы увидите чуть позже.
Мы создали сырые указатели, используя as для приведения неизменяемой и изменяемой ссылки к соответствующим им типам сырых указателей. Поскольку мы создали их непосредственно из ссылок, которые гарантированно являются действительными, мы знаем, что эти конкретные сырые указатели являются действительными, но мы не можем делать такое же предположение о любом сыром указателе.
Далее мы создадим сырой указатель, в достоверности которого мы не можем быть уверены. В листинге 19-2 показано, как создать сырой указатель на произвольный адрес памяти. Результат попытки использовать произвольную память не определён
(undefined): по этому адресу могут быть данные или их может не быть, компилятор может оптимизировать код так, что не будет кода доступа к памяти или программа может выдать ошибку сегментации при выполнении. Обычно нет веских причин для написания такого кода, но это возможно.
Листинг 19-2: Создание сырого указателя на произвольный адрес памяти
Напомним, что можно создавать сырые указатели в безопасном коде, но нельзя
разыменовывать сырые указатели и читать данные, на которые они указывают. В
листинге 19-3 мы используем оператор разыменования
*
для сырого указателя, который требует unsafe блока.
let mut num =
5
; let r1 = &num as
*
const i32
; let r2 = &
mut num as
*
mut i32
; let address =
0x012345usize
; let r = address as
*
const i32
;
Листинг 19-3: Разыменование сырых указателей внутри
unsafe
блока
Создание указателей безопасно. Только при попытке доступа к объекту по адресу в указателе мы можем получить недопустимое значение.
Также обратите внимание, что в примерах кода 19-1 и 19-3 мы создали
*const i32
и
*mut i32
, которые ссылаются на одну и ту же область памяти, где хранится num
. Если мы попытаемся создать неизменяемую и изменяемую ссылку на num вместо сырых указателей, такой код не скомпилируется, т.к. будут нарушены правила заимствования,
запрещающие наличие изменяемой ссылки одновременно с неизменяемыми ссылками.
С помощью сырых указателей мы можем создать изменяемый указатель и неизменяемый указатель на одну и ту же область памяти и изменять данные с помощью изменяемого указателя, потенциально создавая эффект гонки данных. Будьте осторожны!
С учётом всех этих опасностей, зачем тогда использовать сырые указатели? Одним из основных применений является взаимодействие с кодом C, как вы увидите в следующем разделе "Вызов небезопасной функции или метода"
. Другой случай это создание безопасных абстракций, которые не понимает анализатор заимствований. Мы введём понятие небезопасных функций и затем рассмотрим пример безопасной абстракции,
которая использует небезопасный код.
Вызов небезопасной функции или метода
Второй тип операции, который требует небезопасного блока - это вызов небезопасных функций. Небезопасные функции и методы выглядят точно так же, как обычные функции и методы, но они имеют дополнительное указание unsafe перед остальной частью определения. Ключевое слово unsafe в этом контексте указывает, что у функции есть требования, которые мы должны соблюдать при её вызове, потому что Rust не может гарантировать выполнение этих требований. Вызывая небезопасную функцию в unsafe блоке, мы говорим, что прочитали документацию по этой функции и несём ответственность за соблюдение её контрактов.
Вот небезопасная функция с именем dangerous которая ничего не делает в своём теле:
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);
}
Мы должны вызвать функцию dangerous в отдельном unsafe блоке. Если мы попробуем вызвать dangerous без unsafe блока, мы получим ошибку:
Вставив unsafe блок вокруг нашего вызова dangerous
, мы утверждаем, что мы прочитали документацию к функции, мы понимаем как её использовать правильно и мы убедились, что выполняем контракт функции.
Тела небезопасных функций являются фактически unsafe блоками, поэтому для выполнения других небезопасных операций внутри небезопасной функции не нужно добавлять ещё один unsafe блок.
Создание безопасных абстракций вокруг небезопасного кода
Тот факт, что функция содержит небезопасный код, не означает, что мы должны пометить всю функцию как небезопасную. Фактически, упаковка небезопасного кода в безопасную функцию является обычной абстракцией. В качестве примера давайте изучим функцию из стандартной библиотеки split_at_mut
, для которой требуется небезопасный код и исследуем, как мы могли бы её реализовать. Этот безопасный метод определён для изменяемых срезов: он берёт один срез и делит его на два, разделяя срез по индексу, указанному в качестве аргумента. В листинге 19.4 показано, как использовать split_at_mut unsafe fn dangerous
() {} unsafe
{ dangerous();
}
$
cargo run
Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example) error[E0133]: call to unsafe function is unsafe and requires unsafe function or block
-->
src/main.rs:4:5
|
4 | dangerous();
| ^^^^^^^^^^^ call to unsafe function
|
= note: consult the function's documentation for information on how to avoid undefined behavior
For more information about this error, try `rustc --explain E0133`. error: could not compile `unsafe-example` due to previous error
Листинг 19-4: Использование безопасной функции
split_at_mut
Эту функцию нельзя реализовать, используя только безопасный Rust. Попытка реализации могла бы выглядеть примерно как в листинге 19-5, который не компилируется. Для простоты мы реализуем split_at_mut как функцию, а не как метод, и только для значений типа i32
, а не обобщённого типа
T
Листинг 19-5: Попытка реализации функции
split_at_mut
используя только безопасный Rust
Эта функция сначала получает общую длину среза. Затем она проверяет(assert), что индекс, переданный в качестве параметра, находится в границах среза, сравнивая его с длиной. Assert означает, что если мы передадим индекс, который больше, чем длина среза, функция запаникует ещё до попытки использования этого индекса.
Затем мы возвращаем два изменяемых фрагмента в кортеже: один от начала исходного фрагмента до mid индекса (не включая сам mid), а другой - от mid
(включая сам mid) до конца фрагмента.
При попытке скомпилировать код в листинге 19-5, мы получим ошибку.
let mut v = vec!
[
1
,
2
,
3
,
4
,
5
,
6
]; let r = &
mut v[..]; let
(a, b) = r.split_at_mut(
3
); assert_eq!
(a, &
mut
[
1
,
2
,
3
]); assert_eq!
(b, &
mut
[
4
,
5
,
6
]); fn split_at_mut
(values: &
mut
[
i32
], mid: usize
) -> (&
mut
[
i32
], &
mut
[
i32
]) { let len = values.len(); assert!
(mid <= len);
(&
mut values[..mid], &
mut values[mid..])
}
Анализатор заимствований Rust не может понять, что мы заимствуем различные части среза, он понимает лишь, что мы хотим осуществить заимствование частей одного среза дважды. Заимствование различных частей среза в принципе нормально, потому что они не перекрываются, но Rust недостаточно умён, чтобы это понять. Когда мы знаем, что код верный, но Rust этого не понимает, значит пришло время прибегнуть к небезопасному коду.
Листинг 19-6 демонстрирует, как можно использовать unsafe блок, сырой указатель и вызовы небезопасных функций чтобы split_at_mut заработала:
1 ... 48 49 50 51 52 53 54 55 ... 62
Листинг 19-6. Использование небезопасного кода в реализации функции split_at_mutНапомним, из раздела "Тип срез" главы 4, что срезы состоят из указателя на некоторые данные и длины. Мы используем метод len для получения длины среза и метод as_mut_ptr для доступа к сырому указателю среза. Поскольку у нас есть изменяемый срез на значения типа i32, функция as_mut_ptr возвращает сырой указатель типа *mut i32, который мы сохранили в переменной ptr$ cargo run Compiling unsafe-example v0.1.0 (file:///projects/unsafe-example) error[E0499]: cannot borrow `*values` as mutable more than once at a time --> src/main.rs:6:31 | 1 | fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { | - let's call the lifetime of this reference `'1` 6 | (&mut values[..mid], &mut values[mid..]) | --------------------------^^^^^^-------- | | | | | | | second mutable borrow occurs here | | first mutable borrow occurs here | returning this value requires that `*values` is borrowed for `'1` For more information about this error, try `rustc --explain E0499`. error: could not compile `unsafe-example` due to previous error use std::slice; fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { let len = values.len(); let ptr = values.as_mut_ptr(); assert!(mid <= len); unsafe { ( slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.add(mid), len - mid), ) } }
Далее проверяем, что индекс mid находится в границах среза. Затем мы обращаемся к небезопасному коду: функция slice::from_raw_parts_mut принимает сырой указатель,
длину и создаёт срез. Мы используем эту функцию для создания среза, начинающегося с ptr и имеющего длину в mid элементов. Затем мы вызываем метод add у ptr с mid в
качестве аргумента, чтобы получить сырой указатель, который начинается с mid
, и создаём срез, используя этот указатель и оставшееся количество элементов после mid в
качестве длины.
Функция slice::from_raw_parts_mut небезопасна, потому что она принимает сырой указатель и должна верить, что этот указатель действителен. Метод offset для сырых указателях также небезопасен, поскольку он должен доверять, что местоположение после смещения также является допустимым указателем. Поэтому нам пришлось поместить unsafe блок вокруг вызовов slice::from_raw_parts_mut и offset
, чтобы мы могли их вызвать. Посмотрев на код и добавив проверку, что mid должно быть меньше или равно len
, мы можем быть уверены, что все сырые указатели, используемые в unsafe блоке будут действительными указателями на данные внутри среза. Это приемлемое и правильное использование unsafe
Обратите внимание, что нам не нужно помечать результирующую функцию split_at_mut как unsafe
, и мы можем вызвать эту функцию из безопасного Rust. Мы создали безопасную абстракцию для небезопасного кода с помощью реализации функции, которая использует код unsafe блока безопасным образом, поскольку она создаёт только допустимые указатели из данных, к которым эта функция имеет доступ.
Напротив, использование slice::from_raw_parts_mut в листинге 19-7 приведёт к вероятному сбою при использовании среза. Этот код использует произвольный адрес памяти и создаёт срез из 10000 элементов.
Листинг 19-7: Создание среза из произвольного адреса памяти
Мы не владеем памятью в этом произвольном месте, и нет никаких гарантий что срез,
создаваемый этим кодом, содержит допустимые значения i32
. Попытка использовать переменную slice как будто это допустимый срез приводит к неопределённому поведению (UB - undefined behavior).
Использование extern функций для вызова внешнего кода
Иногда в вашем Rust коде может появиться необходимость взаимодействия с кодом,
написанным на другом языке программирования. Для этой цели существует use std::slice; let address =
0x01234usize
; let r = address as
*
mut i32
; let values: &[
i32
] = unsafe
{ slice::from_raw_parts_mut(r,
10000
) };
специальное ключевое слов extern
, которое облегчает создание и использование
интерфейса внешних функций (FFI - Foreign Function Interface). FFI в языке программирования является способом определять функции и давать возможность другому (внешнему) языку программирования вызывать эти функции.
Листинг 19-8 демонстрирует, как настроить интеграцию с функцией abs из стандартной библиотеки C. Функции, объявленные внутри блоков extern
, всегда небезопасны для вызова из кода Rust. Причина в том, что другие языки не обеспечивают соблюдение правил и гарантий Rust, Rust также не может проверить гарантии, поэтому ответственность за безопасность ложится на программиста.
Файл : src/main.rs
Листинг 19-8: Объявление и вызов
extern
функции, написанной на другом языке программирования
Внутри блока extern "C"
мы перечисляем имена и сигнатуры внешних функций из другого языка, которые мы хотим вызвать. Часть "C"
определяет какой application binary
interface (ABI - бинарный интерфейс приложений) использует внешняя функция.
Интерфейс ABI определяет как вызвать функцию на уровне ассемблера. Использование
ABI
"C"
является наиболее часто используемым и следует правилам ABI интерфейса языка Си.
Вызов функций Rust из других языков
Также можно использовать extern для создания интерфейса, который позволяет другим языкам вызывать функции Rust. Вместо extern блока мы добавляем ключевое слово extern и указываем ABI для использования непосредственно перед ключевым словом fn
. Также нужно добавить аннотацию
#[no_mangle]
, чтобы компилятор Rust не изменял название этой функции. Искажение (Mangling) - это когда компилятор изменяет имя нашей функции другим именем, которое содержит больше информации для использования другими этапами процесса компиляции, но такие имена являются менее читабельными. Каждый компилятор языка программирования изменяет имена по-своему, поэтому чтобы функция Rust могла быть доступна из других языков, мы должны отключить искажение имён компилятором Rust.
extern
"C"
{ fn abs
(input: i32
) -> i32
;
} fn main
() { unsafe
{ println!
(
"Absolute value of -3 according to C: {}"
, abs(-
3
));
}
}
В следующем примере мы делаем Rust функцию call_from_c доступной из кода на
C, после того как она скомпилирована в разделяемую (shared) библиотеку и скомпонована из C:
Использование extern не требует unsafe
Получение доступа и внесение изменений в изменяемую статическую
переменную
До текущего момента мы не говорили о глобальных переменных (global variables),
поддерживаемых языком Rust, но использование которых может быть проблематичным из-за правил заимствования. Если два потока получают доступ к одной и той же глобальной переменной, то это может вызвать ситуацию гонки данных.
Глобальные переменные в Rust называют статическими (static). Листинг 19-9
демонстрирует пример объявления и использования в качестве значения статической переменной, имеющей тип строкового среза:
Файл : src/main.rs
Листинг 19-9: Определение и использование неизменяемой статической переменной
Статические переменные похожи на константы, которые мы обсуждали в разделе
“Различия между переменными и константами”
главы 3. Имена статических переменных по общему соглашению пишутся в нотации
SCREAMING_SNAKE_CASE
, и мы должны
указывать тип переменной, которым в данном случае является
&'static str
Статические переменные могут хранить только ссылки со временем жизни 'static
, это означает что компилятор Rust может вывести время жизни и нам не нужно прописывать его явно. Доступ к неизменяемой статической переменной является безопасным.
Константы и неизменяемые статические переменные могут казаться похожими друг на друга, но тонкая разница в том, что значения статических переменных имеют фиксированный адрес в памяти. Использование такого значения всегда будет обращаться к одним и тем же данным (по некоторому фиксированному адресу).
#[no_mangle]
pub extern
"C"
fn call_from_c
() { println!
(
"Just called a Rust function from C!"
);
} static
HELLO_WORLD: &
str
=
"Hello, world!"
; fn main
() { println!
(
"name is: {}"
, HELLO_WORLD);
}