ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 11.01.2024
Просмотров: 1137
Скачиваний: 5
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
специальное ключевое слов 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
));
}
}
, которое облегчает создание и использование
интерфейса внешних функций (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);
}
Константам, с другой стороны, разрешено дублировать свои данные с помощью компилятора при любом их использовании.
Другое отличие констант от статических переменных в том, что последние могут быть изменяемыми. Чтение и изменение статических переменных является небезопасным.
Листинг 19-10 показывает как объявлять, получать доступ и изменять изменяемую статическую переменную с именем
COUNTER
:
Имя файла: src/main.rs
Листинг 19-10: Чтение и запись изменяемой статической переменной является небезопасным
Как и с обычными переменными, мы определяем изменяемость с помощью ключевого слова mut
. Любой код, который читает из или пишет в переменную
COUNTER
должен находиться в unsafe блоке. Этот код компилируется и печатает
COUNTER: 3
, как и следовало ожидать, потому что выполняется в одном потоке. Наличие нескольких потоков с доступом к
COUNTER
приведёт к ситуации гонки данных.
Наличие изменяемых данных, которые доступны глобально, делает трудным реализацию гарантии отсутствия гонок данных, поэтому Rust считает изменяемые статические переменные небезопасными. Там, где это возможно, предпочтительно использовать техники многопоточности и умные указатели, ориентированные на многопоточное исполнение, которые мы обсуждали в главе 16. Таким образом, компилятор сможет проверить, что обращение к данным, доступным из разных потоков, выполняется безопасно.
Реализация небезопасных типажей
Ещё один случай, в котором требуется unsafe
, это реализация небезопасного типажа.
Типаж небезопасен, если хотя бы один из его методов имеет некоторый инвариант,
который компилятор не может проверить. Мы можем объявить типаж как unsafe
,
static mut
COUNTER: u32
=
0
; fn add_to_count
(inc: u32
) { unsafe
{
COUNTER += inc;
}
} fn main
() { add_to_count(
3
); unsafe
{ println!
(
"COUNTER: {}"
, COUNTER);
}
}
добавив ключевое слово unsafe перед trait
, а также пометив реализацию типажа как unsafe
, как показано в листинге 19-11.
Листинг 19-11: Объявление и реализация небезопасного типажа
Используя unsafe impl
, мы даём обещание поддерживать инварианты, которые компилятор не может проверить.
Для примера вспомним маркерные типажи
Sync и
Send
, которые мы обсуждали в разделе "Расширяемый параллелизм с помощью типажей
Sync и
Send
"
главы 16:
компилятор реализует эти типажи автоматически, если наши типы полностью состоят из типов
Send и
Sync
. Если мы создадим тип, который содержит тип, не являющийся
Send или
Sync
, такой, как сырой указатель, и мы хотим пометить этот тип как
Send или
Sync
,
мы должны использовать unsafe блок. Rust не может проверить, что наш тип поддерживает гарантии того, что он может быть безопасно передан между потоками или доступен из нескольких потоков; поэтому нам нужно добавить эти проверки вручную и указать это с помощью unsafe
Доступ к полям объединений (union)
Последнее действие, которое работает только с unsafe
, это доступ к полям объединений,
union. union похож на struct
, но только одно объявленное поле используется в конкретном экземпляре в один момент времени. Объединения в основном используются для взаимодействия с объединениями в коде C. Доступ к полям объединения небезопасен, потому что Rust не может гарантировать тип данных, хранящихся в данный момент в экземпляре объединения. Вы можете узнать больше об объединениях в справочнике
Когда использовать небезопасный код
Использование unsafe для выполнения одного из пяти действий (супер способностей),
которые только что обсуждались, не является ошибочным или не одобренным. Но получить корректный unsafe код сложнее, потому что компилятор не может помочь в обеспечении безопасности памяти. Если у вас есть причина использовать unsafe код, вы unsafe trait
Foo
{
// methods go here
} unsafe impl
Foo for i32
{
// method implementations go here
} fn main
() {}
, а также пометив реализацию типажа как unsafe
, как показано в листинге 19-11.
Листинг 19-11: Объявление и реализация небезопасного типажа
Используя unsafe impl
, мы даём обещание поддерживать инварианты, которые компилятор не может проверить.
Для примера вспомним маркерные типажи
Sync и
Send
, которые мы обсуждали в разделе "Расширяемый параллелизм с помощью типажей
Sync и
Send
"
главы 16:
компилятор реализует эти типажи автоматически, если наши типы полностью состоят из типов
Send и
Sync
. Если мы создадим тип, который содержит тип, не являющийся
Send или
Sync
, такой, как сырой указатель, и мы хотим пометить этот тип как
Send или
Sync
,
мы должны использовать unsafe блок. Rust не может проверить, что наш тип поддерживает гарантии того, что он может быть безопасно передан между потоками или доступен из нескольких потоков; поэтому нам нужно добавить эти проверки вручную и указать это с помощью unsafe
Доступ к полям объединений (union)
Последнее действие, которое работает только с unsafe
, это доступ к полям объединений,
union. union похож на struct
, но только одно объявленное поле используется в конкретном экземпляре в один момент времени. Объединения в основном используются для взаимодействия с объединениями в коде C. Доступ к полям объединения небезопасен, потому что Rust не может гарантировать тип данных, хранящихся в данный момент в экземпляре объединения. Вы можете узнать больше об объединениях в справочнике
Когда использовать небезопасный код
Использование unsafe для выполнения одного из пяти действий (супер способностей),
которые только что обсуждались, не является ошибочным или не одобренным. Но получить корректный unsafe код сложнее, потому что компилятор не может помочь в обеспечении безопасности памяти. Если у вас есть причина использовать unsafe код, вы unsafe trait
Foo
{
// methods go here
} unsafe impl
Foo for i32
{
// method implementations go here
} fn main
() {}
можете делать это, а наличие явной unsafe аннотации облегчает отслеживание источника проблем, если они возникают.
Продвинутые типажи
Сначала мы рассмотрели типажи в разделе "Типажи: Определение общего поведения"
главы 10, но как и со временами жизни, мы не обсудили более сложные детали. Сейчас что вы знаете о Rust больше и мы можем двинуться дальше.
Указание заполнителей типов в определениях типажей с
ассоциированными типами
Ассоциированные типы (Associated types) связывают заполнитель типа с типажом, таким образом что объявления методов типажа могут использовать эти заполнители типов в своих сигнатурах. Реализация типажа будет указывать конкретный, используемый тип на месте заполнителя типа, при конкретной реализации. Таким образом, мы можем определить типаж пока он не реализован, который использует какие-то типы без необходимости знать, какими точно типами они будут.
Мы описали большинство расширенных возможностей в этой главе, как редко необходимые. Ассоциированные типы находятся где-то посередине: они используются реже чем возможности описанные в остальной части книги, но чаще чем многие другие возможности обсуждаемые в этой главе.
Одним из примеров типажа с ассоциированным типом является типаж
Iterator
,
который предоставляет стандартная библиотека. Ассоциированный тип называется
Item и представляет тип для значений, которые перебирает тип реализующий типаж
Iterator
. В разделе "Типаж
Iterator и метод next
"
главы 13, мы упоминали определение типажа
Iterator показанное в листинге 19-12.
Листинг 19-12: Определение типажа
Iterator
, который имеет ассоциированный тип
Item
Тип
Item является заполнителем и определение метода next показывает, что он будет возвращать значения типа
Option<:item>
. Разработчики типажа
Iterator определит конкретный тип для
Item
, а метод next вернёт
Option содержащий значение этого конкретного типа.
Ассоциированные типы могли бы показаться концепцией похожей на обобщённые типы,
в том смысле, что последние позволяют определить функцию, не указывая, какие типы она может обрабатывать. Так зачем использовать ассоциированные типы?
Давайте рассмотрим разницу между этими двумя понятиями на примере из главы 13,
которая реализует типаж
Iterator у структуры
Counter
. В листинге 13-21 мы указали,
pub trait
Iterator
{ type
Item
; fn next
(&
mut self
) ->
Option
<:item>;
}
что тип для
Item был u32
:
Файл: src/lib.rs
Этот синтаксис весьма напоминает обобщённые типы. Так почему же типаж
Iterator не определён обобщённым типом, как показано в листинге 19-13?
Листинг 19-13: Гипотетическое определение типажа
Iterator
используя обобщённые типы
Разница в том, что при использовании обобщений, как показано в листинге 19-13, мы должны аннотировать типы в каждой реализации; потому что мы также можем реализовать
Iterator for Counter или любого другого типа, мы могли бы иметь несколько реализации
Iterator для
Counter
. Другими словами, когда типаж имеет обобщённый параметр, он может быть реализован для типа несколько раз, каждый раз меняя конкретные типы параметров обобщённого типа. Когда мы используем метод next у
Counter
, нам пришлось бы предоставить аннотации типа, указывая какую реализацию
Iterator мы хотим использовать.
С ассоциированными типами не нужно аннотировать типы, потому что мы не можем реализовать типаж у типа несколько раз. В листинге 19-12 с определением,
использующим ассоциированные типы можно выбрать только один тип
Item
, потому что может быть только одно объявление impl Iterator for Counter
. Нам не нужно указывать, что нужен итератор значений типа u32
везде, где мы вызываем next у
Counter
Параметры обобщённого типа по умолчанию и перегрузка операторов
Когда мы используем параметры обобщённого типа, мы можем указать конкретный тип по умолчанию для обобщённого типа. Это устраняет необходимость разработчикам указывать конкретный тип, если работает тип по умолчанию. Синтаксис для указания типа по умолчанию в случае обобщённого типа выглядит как
Отличным примером ситуации, где этот подход полезен, является перегрузка оператора.
Перегрузка оператора (Operator overloading) реализует пользовательское поведение некоторого оператора (например,
+
) в конкретных ситуациях.
impl
Iterator for
Counter { type
Item
= u32
; fn next
(&
mut self
) ->
Option
<:item> {
// --snip-- pub trait
Iterator
{ fn next
(&
mut self
) ->
Option
;
}
Item был u32
:
Файл: src/lib.rs
Этот синтаксис весьма напоминает обобщённые типы. Так почему же типаж
Iterator не определён обобщённым типом, как показано в листинге 19-13?
Листинг 19-13: Гипотетическое определение типажа
Iterator
используя обобщённые типы
Разница в том, что при использовании обобщений, как показано в листинге 19-13, мы должны аннотировать типы в каждой реализации; потому что мы также можем реализовать
Iterator
Iterator для
Counter
. Другими словами, когда типаж имеет обобщённый параметр, он может быть реализован для типа несколько раз, каждый раз меняя конкретные типы параметров обобщённого типа. Когда мы используем метод next у
Counter
, нам пришлось бы предоставить аннотации типа, указывая какую реализацию
Iterator мы хотим использовать.
С ассоциированными типами не нужно аннотировать типы, потому что мы не можем реализовать типаж у типа несколько раз. В листинге 19-12 с определением,
использующим ассоциированные типы можно выбрать только один тип
Item
, потому что может быть только одно объявление impl Iterator for Counter
. Нам не нужно указывать, что нужен итератор значений типа u32
везде, где мы вызываем next у
Counter
Параметры обобщённого типа по умолчанию и перегрузка операторов
Когда мы используем параметры обобщённого типа, мы можем указать конкретный тип по умолчанию для обобщённого типа. Это устраняет необходимость разработчикам указывать конкретный тип, если работает тип по умолчанию. Синтаксис для указания типа по умолчанию в случае обобщённого типа выглядит как
Отличным примером ситуации, где этот подход полезен, является перегрузка оператора.
Перегрузка оператора (Operator overloading) реализует пользовательское поведение некоторого оператора (например,
+
) в конкретных ситуациях.
impl
Iterator for
Counter { type
Item
= u32
; fn next
(&
mut self
) ->
Option
<:item> {
// --snip-- pub trait
Iterator
(&
mut self
) ->
Option
}
Rust не позволяет создавать собственные операторы или перегружать произвольные операторы. Но можно перегрузить перечисленные операции и соответствующие им типажи из std::ops путём реализации типажей, связанных с этими операторами.
Например, в листинге 19-14 мы перегружаем оператор
+
, чтобы складывать два экземпляра
Point
. Мы делаем это реализуя типаж
Add для структуры
Point
:
Файл: src/main.rs
Листинг 19-14: Реализация типажа
Add
для перезагрузки оператора
+
у структуры
Point
Метод add складывает значения x
двух экземпляров
Point и значения y
у
Point для создания нового экземпляра
Point
. Типаж
Add имеет ассоциированный тип с именем
Output
, который определяет тип, возвращаемый из метода add
Обобщённый тип по умолчанию в этом коде находится в типаже
Add
. Вот его определение:
Этот код должен выглядеть знакомым: типаж с одним методом и ассоциированным типом. Новый синтаксис это
RHS=Self
. Такой синтаксис называется параметры типа по
use std::ops::Add;
#[derive(Debug, Copy, Clone, PartialEq)]
struct
Point
{ x: i32
, y: i32
,
} impl
Add for
Point { type
Output
= Point; fn add
(
self
, other: Point) -> Point {
Point { x: self
.x + other.x, y: self
.y + other.y,
}
}
} fn main
() { assert_eq!
(
Point { x:
1
, y:
0
} + Point { x:
2
, y:
3
},
Point { x:
3
, y:
3
}
);
} trait
Add
> { type
Output
; fn add
(
self
, rhs: Rhs) -> Self::Output;
}
умолчанию (default type parameters). Параметр обобщённого типа
RHS
(сокращённо “right hand side”) определяет тип параметра rhs в методе add
. Если мы не укажем конкретный тип для
RHS
при реализации типажа
Add
, то типом для
RHS
по умолчанию будет
Self
,
который будет типом для которого реализуется типаж
Add
Когда мы реализовали
Add для структуры
Point
, мы использовали стандартное значение для
RHS
, потому что хотели сложить два экземпляра
Point
. Давайте посмотрим на пример реализации типажа
Add
, где мы хотим пользовательский тип
RHS
вместо использования типа по умолчанию.
У нас есть две разные структуры
Millimeters и
Meters
, хранящие значения в разных единицах измерения. Мы хотим добавить значения в миллиметрах к значениям в метрах и хотим иметь реализацию типажа
Add
, которая делает правильное преобразование единиц. Можно реализовать
Add для
Millimeters с типом
Meters в качестве
Rhs
, как показано в листинге 19-15.
Файл: src/lib.rs
1 ... 49 50 51 52 53 54 55 56 ... 62