Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Languages
 С
 GNU С Library 
 Qt 
 STL 
 Threads 
 C++ 
 Samples 
 stanford.edu 
 ANSI C
 Libs
 LD
 Socket
 Pusher
 Pipes
 Encryption
 Plugin
 Inter-Process
 Errors
 Deep C Secrets
 C + UNIX
 Linked Lists / Trees
 Asm
 Perl
 Python
 Shell
 Erlang
 Go
 Rust
 Алгоритмы
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 MINIX...3057 
 Solaris...2933 
 LD...2904 
 Linux Kernel 2.6...2470 
 William Gropp...2180 
 Rodriguez 6...2011 
 C++ Templates 3...1945 
 Trees...1937 
 Kamran Husain...1865 
 Secure Programming for Li...1792 
 Максвелл 5...1710 
 DevFS...1693 
 Part 3...1682 
 Stein-MacEachern-> Час...1632 
 Go Web ...1624 
 Ethreal 4...1618 
 Arrays...1607 
 Стивенс 9...1603 
 Максвелл 1...1592 
 FAQ...1538 
 
  01.01.2024 : 3621733 посещений 

iakovlev.org

Семантика Rust

 1. Контейнеры (crates) и модули (modules)
 2. Приведение типов
 3. Ассоциированные	типы
 4. Перегрузка	операций
 5. Макросы
 6. Сырые указатели
 7. unsafe
 

Контейнеры и модули

Rust имеет два различных термина, которые относятся к модульной системе: контейнер и модуль. Контейнер — это синоним библиотеки или пакета на других языках. Именно поэтому инструмент управления пакетами в Rust называется Cargo: вы пересылаете ваши контейнеры другим с помощью Cargo. Контейнеры могут производить исполняемый файл или библиотеку, в зависимости от проекта.
Каждый контейнер имеет неявный корневой модуль, содержащий код для этого контейнера. В рамках этого базового модуля можно определить дерево суб-модулей. Модули позволяют разделить ваш код внутри контейнера.
В качестве примера, давайте сделаем контейнер phrases, который выдает нам различные фразы на разных языках. Чтобы не усложнять пример, мы будем использовать два вида фраз: «greetings» и «farewells», и два языка для этих фраз: английский и японский.
Создадим новый контейнер из командной строки с помощью Cargo
 cargo new phrases
Откроем файл src/lib.rs и пропишем в нем два модуля с помощью ключевого слова mod

 mod english {
     mod greetings {
     }
 
     mod farewells {
     }
 }
 
 mod japanese {
     mod greetings {
     }
 
     mod farewells {
     }
 }
Внутри модуля мы можем обьявлять вложенный модули. Соберем библиотеку
 cargo build
Появится скомпилированный контейнер libphrases.rlib. Разобьем исходник этого контейнера на несколько исходных файлов. Файл src/lib.rs будет следующего содержания

 mod english;
 mod japanese;
Добавляем два подкаталога - src/english/ src/japanese/. Добавляем два файла - src/english/mod.rs и src/japanese/mod.rs одинакового содержания

 mod greetings;
 mod farewells;
Добавляем еще 4 файла - src/english/greetings.rs, src/english/farewells.rs, src/japanese/greetings.rs, src/japanese/farewells.rs. Поместим в src/english/greetings.rs код

 fn hello() -> String {
     "Hello!".to_string()
 }
Поместим в src/english/farewells.rs код

 fn goodbye() -> String {
     "Goodbye.".to_string()
 }
Поместим в src/japanese/greetings.rs код

 fn hello() -> String {
     "こんにちは".to_string()
 }
Поместим в src/japanese/farewells.rs код

 fn goodbye() -> String {
     "さようなら".to_string()
 }
Собираем контейнер
 cargo build
У нас есть библиотечный контейнер. Создадим исполняемый контейнер, который будет его использовать. В текущем каталоге src создаем файл src/main.rs следующего содержания

 extern crate phrases;
  
 fn main() {
     println!("Hello in English: {}", phrases::english::greetings::hello());
     println!("Goodbye in English: {}", phrases::english::farewells::goodbye());
 
     println!("Hello in Japanese: {}", phrases::japanese::greetings::hello());
     println!("Goodbye in Japanese: {}", phrases::japanese::farewells::goodbye());
 }
 
Если мы теперь попробуем сбилдить проект, то получим ошибку компиляции о том, что вложенные модули приватны. Это происходит потому, что в расте все интерфейсы по умолчанию приватны. Чтобы сделать его публичным, надо использовать ключевое слово pub. В файле src/lib.rs обьявим модули публичными

 pub mod english;
 pub mod japanese;
В src/english/mod.rs и src/japanese/mod.rs обьявим

 pub mod greetings;
 pub mod farewells;
 
И во все остальные файлы перед определением функции поставим pub. Если теперь собрать проект, то соберутся оба контейнера - и библиотечный, и исполняемый.
Теперь нам нужно что-то сделать с эстетикой интерфейса - он неудобен

 phrases::english::greetings::hello()
Для импорта имен в расте есть ключевое слово
 use
Изменим файл src/main.rs

 extern crate phrases;
 
 use phrases::english::greetings;
 use phrases::english::farewells;
 
 fn main() {
     println!("Hello in English: {}", greetings::hello());
     println!("Goodbye in English: {}", farewells::goodbye());
 }
Все работает. Эту форму можно еще больше сократить

 use phrases::english::{greetings, farewells};





Приведение типов

Rust, со своим акцентом на безопасность, обеспечивает два различных способа преобразования различных типов между собой. Первый — as , для безопасного приведения. Второй — transmute , в отличие от первого, позволяет произвольное приведение типов и является одной из самых опасных возможностей Rust.
Наиболее простой случай - удаление mutability из ссылки:

     &mut T   ->   &T
Аналогично удаление mutability из сырого указателя:

     *mut T   ->   *const T
Ссылки можно конвертировать в сырые указатели:

     &T   ->   *const T
     &mut T   ->   *mut T
Ключевое слово as выполняет обычное приведение типов:

 let x: i32 = 5;
 let y = x as i64;
Есть 3 вида надежных (safe) приведений типов: явные, приведения между числовыми типами и приведения указателей. Пример числовых приведений

 let one = true as u8;
 let at_sign = 64 as char;
 let two_hundred = -56i8 as u8;
Приведение сырых указателей в числа и обратно - это safe приведения. Разименование указателя не является safe:

 let a = 300 as *const char; // a pointer to location 300
 let b = a as u32;
Функция transmute предоставляется внутренними средствами компилятора, и то, что она делает, является очень простым, но в то же время очень опасным. Она сообщает Rust, чтобы он воспринимал значение одного типа, как будто это значение другого типа. Это делается независимо от системы проверки типов, и поэтому полностью на ваш страх и риск. Для того чтобы компиляция прошла успешно, мы должны обернуть эту операцию в unsafe блок. Например, следующий код вызовет ошибку при компиляции:

 let a = [0u8, 0u8, 0u8, 0u8];
 let b = a as u32; // four u8s makes a u32
Это «нескалярное преобразование», потому что у нас здесь преобразуются множественные значения: четыре элемента массива. В следующем варианте все соберется без ошибок

 use std::mem;
 
 fn main() {
     unsafe {
         let a = [0u8, 1u8, 0u8, 0u8];
         let b = mem::transmute::<[u8; 4], u32>(a);
         println!("{}", b); // 256
         // or, more concisely:
         let c: u32 = mem::transmute(a);
         println!("{}", c); // 256
     }
 }





Ассоциированные типы

Ассоциированные (связанные) типы — это мощная часть системы типов в Rust. Они связаны с идеей 'семейства типа', другими словами, группировки различных типов вместе. Рассмотрим конкретный пример: если вы хотите написать типаж Graph , то нужны два обобщенных параметра типа: тип узел и тип ребро. Исходя из этого, вы можете написать типаж Graph , который выглядит следующим образом:

 trait Graph<N, E> {
     fn has_edge(&self, &N, &N) -> bool;
     fn edges(&self, &N) -> Vec<E>;
 }
Такое решение вроде бы достигает своей цели, но, в конечном счете, является неудобным. Например, любая функция, которая принимает Graph в качестве параметра, также должна быть обобщённой с параметрами N и E :

 fn distance<N, E, G: Graph>(graph: &G, start: &N, end: &N) -> u32 { ... }
Параметр E в этой сигнатуре является лишним и только отвлекает. Что действительно нужно заявить, это чтобы сформировать какого-либо вида Graph , нужны соответствующие типы E и N , собранные вместе. Мы можем сделать это с помощью ассоциированных типов:

 trait Graph {
     type N;
     type E;
 
     fn has_edge(&self, &Self::N, &Self::N) -> bool;
     fn edges(&self, &Self::N) -> Vec<Self::E>;
 }
Теперь наши клиенты могут абстрагироваться от определенного Graph :

 fn distance<G: Graph>(graph: &G, start: &G::N, end: &G::N) -> u32 { ... }
Больше нет необходимости иметь дело с типом E.
Типаж, который включает ассоциированные типы, как и любой другой типаж, для реализации использует ключевое слово impl . Вот простая реализация Graph :

 struct Node;
 
 struct Edge;
 
 struct MyGraph;
 
 impl Graph for MyGraph {
     type N = Node;
     type E = Edge;
 
     fn has_edge(&self, n1: &Node, n2: &Node) -> bool {
         true
     }
 
     fn edges(&self, n: &Node) -> Vec<Edge> {
         Vec::new()
     }
 }





Перегрузка операций

Rust позволяет ограниченную форму перегрузки операций. Есть определенные операции, которые могут быть перегружены. Есть специальные типажи, которые вы можете реализовать для поддержки конкретной операции между типами. В результате чего перегружается операция. Например, операция + может быть перегружена с помощью типажа Add :

 use std::ops::Add;
 
 #[derive(Debug)]
 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() {
     let p1 = Point { x: 1, y: 0 };
     let p2 = Point { x: 2, y: 3 };
 
     let p3 = p1 + p2;
 
     println!("{:?}", p3);
 }
В main мы можем использовать операцию + для двух Point , так как мы реализовали типаж Add<Output=Point> для Point . Есть целый ряд операций, которые могут быть перегружены таким образом, и все связанные с этим типажи расположены в модуле std::ops .
Мы можем использовать типаж операцию в обобщенных структурах, например типаж HasArea и структуру Square:

 use std::ops::Mul;
 
 trait HasArea<T> {
     fn area(&self) -> T;
 }
 
 struct Square<T> {
     x: T,
     y: T,
     side: T,
 }
 
 impl<T> HasArea<T> for Square<T>
         where T: Mul<Output=T> + Copy {
     fn area(&self) -> T {
         self.side * self.side
     }
 }
 
 fn main() {
     let s = Square {
         x: 0.0f64,
         y: 0.0f64,
         side: 12.0f64,
     };
 
     println!("Area of s: {}", s.area());
 }
Мы просто объявляем тип-параметр T и используем его вместо f64 в определении HasArea и Square . Чтобы реализовать area , нам нужно умножить операнды друг на друга, поэтому мы объявляем T как реализующий std::ops::Mul . Как и Add , Mul принимает параметр Output : т.к. мы знаем, что числа не меняют своего типа, когда их умножают, Output также объявлен как T . T также должен поддерживать копирование, чтобы Rust не пытался переместить self.side в возвращаемое значение.




Макросы

Макросы позволяют абстрагироваться на синтаксическом уровне. Вызов макроса является сокращением для «расширенной» синтаксической формы. Это расширение происходит в начале компиляции, до начала статической проверки. В результате, макросы могут охватить много шаблонов повторного использования кода, которые невозможны при использовании лишь ключевых абстракций Rust.
У макросов есть недостаток: код, основанный на макросах, может быть трудным для понимания, потому что к нему применяется меньше встроенных правил. Подобно обычной функции, качественный макрос может быть использован без понимания его реализации. Тем не менее, может быть трудно разработать качественный макрос! Кроме того, ошибки компилятора в макро коде сложнее интерпретировать, потому что они описывают проблемы в расширенной форме кода, а не в исходной сокращенной форме кода, которую используют разработчики.
Это не означает, что макросы это плохо; они являются частью Rust, потому что иногда они все же нужны для по-настоящему краткой записи хорошо абстрагированной части кода.
Есть стандартный макрос vec! , который используется для инициализации вектора с произвольным количеством элементов

 let x: Vec<u32> = vec![1, 2, 3];
Его нельзя реализовать в виде обычной функции, так как он принимает любое количество аргументов. Но мы можем представить его в виде синтаксического сокращения для следующего кода

 let x: Vec<u32> = {
     let mut temp_vec = Vec::new();
     temp_vec.push(1);
     temp_vec.push(2);
     temp_vec.push(3);
     temp_vec
 };
Мы можем реализовать это сокращение, используя макрос:

 macro_rules! vec {
     ( $( $x:expr ),* ) => {
         {
             let mut temp_vec = Vec::new();
             $(
                 temp_vec.push($x);
             )*
             temp_vec
         }
     };
 }
Тут мы определяем макрос с именем vec , аналогично тому, как fn vec определяло бы функцию с именем vec . При вызове мы неформально пишем имя макроса с восклицательным знаком, например, vec! . Восклицательный знак является частью синтаксиса вызова и служит для того, чтобы отличать макрос от обычной функции.
Макрос определяется с помощью ряда правил, которые представляют собой варианты сопоставления с образцом (match). В коде

 ( $( $x:expr ),* ) => { ... };
Это очень похоже на конструкцию match , но сопоставление происходит на уровне синтаксических деревьев Rust, на этапе компиляции. Точка с запятой не является обязательной для последнего (только здесь) варианта. «Образец» слева от => известен как шаблон совпадений (образец) (обнаружитель совпадений) (matcher). Он имеет свою собственную грамматику в рамках языка.
Образец $x:expr будет соответствовать любому выражению Rust, связывая его дерево синтаксиса с метапеременной $x . Идентификатор expr является спецификатором фрагмента; полные возможности перечислены далее в этой главе. Образец, окруженный $(...),* , будет соответствовать нулю или более выражениям, разделенным запятыми.
Пример

 macro_rules! foo {
     (x => $e:expr) => (println!("mode X: {}", $e));
     (y => $e:expr) => (println!("mode Y: {}", $e));
 }
 
 fn main() {
     foo!(y => 3);
 }
выведет
 mode Y: 3
Другой пример

 foo!(z => 3);
выведет ошибку компиляции
 error: no rules expected the token `z`
С правой стороны макро правил используется, по большей части, обычный синтаксис Rust. Но мы можем соединить кусочки раздробленного синтаксиса, захваченные при сопоставлении с соответствующим образцом. Из предыдущего примера:

 $(
     temp_vec.push($x);
 )*
Каждое соответствующее выражение $x будет генерировать одиночный оператор push в развернутой форме макроса. Повторение в развернутой форме происходит синхронно с повторением в форме образца.
Поскольку $x уже объявлен в образце как выражение, мы не повторяем :expr с правой стороны. Кроме того, мы не включаем разделяющую запятую в качестве части оператора повторения. Вместо этого, у нас есть точка с запятой в пределах повторяемого блока.
Еще одна деталь: макрос vec! имеет две пары фигурных скобок в правой части. Они часто сочетаются таким образом:

 macro_rules! foo {
     () => {{
         ...
     }}
 }
Внешние скобки являются частью синтаксиса macro_rules! . На самом деле, вы можете использовать () или [] вместо них. Они просто разграничивают правую часть в целом.
Внутренние скобки являются частью расширенного синтаксиса. Помните, что макрос vec! используется в контексте выражения. Мы используем блок, для записи выражения с множественными операторами, в том числе включающее let привязки. Если ваш макрос раскрывается в одно единственное выражение, то дополнительной слой скобок не нужен.
Сишный синтаксис макросов отличается от растовского. Если например в си написать следующий макрос

 #define FIVE_TIMES(x) 5 * x
 
 int main() {
     printf("%d\n", FIVE_TIMES(2 + 3));
     return 0;
 }
то он вместо ожидаемых 25 напечатает 13, поскольку при развертывание умножение имеет больший приоритет, чем сложение. В расте это выглядит так:

 macro_rules! five_times {
     ($x:expr) => (5 * $x);
 }
 
 fn main() {
     assert_eq!(25, five_times!(2 + 3));
 }
Метапеременная $x обрабатывается как единый узел выражения, и сохраняет свое место в дереве синтаксиса даже после замены, и мы получим ожидаемые 25.
Другой распространенной проблемой в системе макросов является захват переменной (variable capture). Вот C макрос, использующий GNU C расширение, который эмулирует блоки выражениий в Rust

 #define LOG(msg) ({ \
     int state = get_log_state(); \
     if (state > 0) { \
         printf("log(%d): %s\n", state, msg); \
     } \
 })
Вот простой случай использования, применение которого может плохо кончиться:

 const char *state = "reticulating splines";
 LOG(state)
Он раскрывается в

 const char *state = "reticulating splines";
 {
     int state = get_log_state();
     if (state > 0) {
         printf("log(%d): %s\n", state, state);
     }
 }
Вторая переменная с именем state затеняет первую. Это проблема, потому что команде печати требуется обращаться к ним обоим. Эквивалентный макрос в Rust обладает требуемым поведением

 macro_rules! log {
     ($msg:expr) => {{
         let state: i32 = get_log_state();
         if state > 0 {
             println!("log({}): {}", state, $msg);
         }
     }};
 }
 
 fn main() {
     let state: &str = "reticulating splines";
     log!(state);
 }
Это работает, потому что Rust имеет систему макросов с соблюдением гигиены. Раскрытие каждого макроса происходит в отдельном контексте синтаксиса, и каждая переменная обладает меткой контекста синтаксиса, где она была введена. Это как если бы переменная state внутри main была бы окрашена в другой «цвет» в отличае от переменной state внутри макроса, из-за чего они бы не конфликтовали.
Это также ограничивает возможности макросов для внедрения новых связываний переменных на месте вызова. Код, приведенный ниже, не будет работать:

 macro_rules! foo {
     () => (let x = 3;);
 }
 
 fn main() {
     foo!();
     println!("{}", x);
 }
Вместо этого вы должны передавать имя переменной при вызове, тогда она будет обладать меткой правильного контекста синтаксиса.

 macro_rules! foo {
     ($v:ident) => (let $v = 3;);
 }
 
 fn main() {
     foo!(x);
     println!("{}", x);
 }
Это справедливо для let привязок и меток loop, но не для элементов. Код, приведенный ниже, компилируется:

 macro_rules! foo {
     () => (fn x() { });
 }
 
 fn main() {
     foo!();
     x();
 }
Раскрытие макроса также может включать в себя вызовы макросов, в том числе вызовы того макроса, который раскрывается. Эти рекурсивные макросы могут быть использованы для обработки древовидного ввода, как показано на этом (упрощенном) HTML сокращение:

 macro_rules! write_html {
     ($w:expr, ) => (());
 
     ($w:expr, $e:tt) => (write!($w, "{}", $e));
 
     ($w:expr, $tag:ident [ $($inner:tt)* ] $($rest:tt)*) => {{
         write!($w, "<{}>", stringify!($tag));
         write_html!($w, $($inner)*);
         write!($w, "", stringify!($tag));
         write_html!($w, $($rest)*);
     }};
 }
 
 fn main() {
     use std::fmt::Write;
     let mut out = String::new();
 
     write_html!(&mut out,
         html[
             head[title["Macros guide"]]
             body[h1["Macros are the best!"]]
         ]);
 
     assert_eq!(out,
         "<html><head><title>Macros guide</title></head>\
          <body><h1>Macros are the best!</h1></body></html>");
 }
Чтобы увидеть результаты расширения макросов, выполните команду rustc -- pretty expanded . Вывод представляет собой целый контейнер, так что вы можете подать его обратно в rustc , что иногда выдает лучшие сообщения об ошибках, чем при обычной компиляции. Обратите внимание, что вывод --pretty expanded может иметь разное значение, если несколько переменных, имеющих одно и то же имя (но разные контексты синтаксиса), находятся в той же области видимости. В этом случае --pretty expanded,hygiene расскажет вам о контекстах синтаксиса.
rustc , поддерживает два синтаксических расширения, которые помогают с отладкой макросов. В настоящее время, они неустойчивы и требуют feature gates.
log_syntax!(...) будет печатать свои аргументы в стандартный вывод во время компиляции, и «развертываться» в ничто.
trace_macros!(true) будет выдавать сообщение компилятора каждый раз, когда макрос развертывается. Используйте trace_macros!(false) в конце развертывания, чтобы выключить его.

Код на Rust может быть разобран в синтаксическое дерево, даже когда он содержит неразвёрнутые макросы. Это свойство очень полезно для редакторов и других инструментов, обрабатывающих исходный код. Оно также влияет на вид системы макросов Rust.
Как следствие, когда компилятор разбирает вызов макроса, ему необходимо знать, во что развернётся данный макрос. Макрос может разворачиваться в следующее:
ноль или больше элементов;
ноль или больше методов;
выражение;
оператор;
образец.

Вызов макроса в блоке может представлять собой элементы, выражение, или оператор. Rust использует простое правило для разрешения этой неоднозначности. Вызов макроса, производящего элементы, должен либо
ограничиваться фигурными скобками, т.е. foo! { ... } ;
завершаться точкой с запятой, т.е. foo!(...); .

Другое следствие разбора перед раскрытием макросов — это то, что вызов макроса должен состоять из допустимых лексем. Более того, скобки всех видов должны быть сбалансированы в месте вызова. Например, foo!([) не является разрешённым кодом. Такое поведение позволяет компилятору понимать где заканчивается вызов макроса.
Говоря более формально, тело вызова макроса должно представлять собой последовательность деревьев лексем. Дерево лексем определяется рекурсивно и представляет собой либо:
последовательность деревьев лексем, окружённую согласованными круглыми, квадратными или фигурными скобками ( () , [] , {} );
любую другую одиночную лексему.

Внутри сопоставления каждая метапеременная имеет указатель фрагмента, определяющий синтаксическую форму, с которой она совпадает. Вот список этих указателей:
ident : идентификатор. Например: x ; foo .
path : квалифицированное имя. Например: T::SpecialA .
expr : выражение. Например: 2 + 2 ; if true then { 1 } else { 2 } ;f(42) .
ty : тип. Например: i32 ; Vec<(char, String)> ; &T .
pat : образец. Например: Some(t) ; (17, 'a') ; _ .
stmt : единственный оператор. Например: let x = 3 .
block : последовательность операторов, ограниченная фигурными скобками. Например: { log(error, "hi"); return 12; } .
item : элемент. Например: fn foo() { } ; struct Bar; .
meta : «мета-элемент», как в атрибутах. Например: cfg(target_os = "windows") .
tt : единственное дерево лексем.

Есть дополнительные правила относительно лексем, следующих за метапеременной:
за expr должно быть что-то из этого: => , ; ;
за ty и path должно быть что-то из этого: => , : = > as ;
за pat должно быть что-то из этого : => , = ;
за другими лексемами могут следовать любые символы.

Приведённые правила обеспечивают развитие синтаксиса Rust без необходимости менять существующие макросы.
И ещё: система макросов никак не обрабатывет неоднозначность разбора. Например, грамматика $($t:ty)* $e:expr всегда будет выдавать ошибку, потому что синтаксическому анализатору пришлось бы выбирать между разбором $t и разбором $e . Можно изменить синтаксис вызова так, чтобы грамматика отличалась в начале. В данном случае можно написать $(T $t:ty)* E $e:exp .

Макросы разворачиваются на ранней стадии компиляции, перед разрешением имён. Один из недостатков такого подхода в том, что правила видимости для макросов отличны от правил для других конструкций языка.
Компилятор определяет и разворачивает макросы при обходе графа исходного кода контейнера в глубину. При этом определения макросов включаются в граф в порядке их встречи компилятором. Поэтому макрос, определённый на уровне модуля, виден во всём последующем коде модуля, включая тела всех вложенных модулей ( mod ).
Макрос, определённый в теле функции, или где-то ещё не на уровне модуля, виден только внутри этого элемента (например, внутри одной функции).
Если модуль имеет атрибут macro_use , то его макросы также видны в его родительском модуле после элемента mod данного модуля. Если родитель тоже имеет атрибут macro_use , макросы также будут видны в модуле-родителе родителя, после элемента mod родителя. Это распространяется на любое число уровней.
Атрибут macro_use также можно поставить на подключение контейнера extern crate . В этом контексте оно управляет тем, какие макросы будут загружены из внешнего контейнера, т.е.

 #[macro_use(foo, bar)]
 extern crate baz;
Если атрибут записан просто как #[macro_use] , будут загружены все макросы. Если атрибута нет, никакие макросы не будут загружены. Загружены могут быть только макросы, объявленные с атрибутом #[macro_export] .
Чтобы загрузить макросы из контейнера без компоновки контейнера в выходной артефакт, можно использовать атрибут #[no_link] . например

 macro_rules! m1 { () => (()) }
 
 // visible here: m1
 
 mod foo {
     // visible here: m1
 
     #[macro_export]
     macro_rules! m2 { () => (()) }
 
     // visible here: m1, m2
 }
 
 // visible here: m1
 
 macro_rules! m3 { () => (()) }
 
 // visible here: m1, m3
 
 #[macro_use]
 mod bar {
     // visible here: m1, m3
 
     macro_rules! m4 { () => (()) }
 
     // visible here: m1, m3, m4
 }
 
 // visible here: m1, m3, m4
Когда эта библиотека загружается с помощью #[macro_use] extern crate , виден только макрос m2 .

Если макрос используется в нескольких контейнерах, всё становится ещё сложнее. Допустим, mylib определяет

 pub fn increment(x: u32) -> u32 {
     x + 1
 }
 
 #[macro_export]
 macro_rules! inc_a {
     ($x:expr) => ( ::increment($x) )
 }
 
 #[macro_export]
 macro_rules! inc_b {
     ($x:expr) => ( ::mylib::increment($x) )
 }
inc_a работает только внутри mylib , а inc_b — только снаружи. Более того, inc_b сломается, если пользователь импортирует mylib под другим именем.

В Rust пока нет гигиеничных ссылок на контейнеры, но есть простой способ обойти эту проблему. Особая макро-переменная $crate раскроется в ::foo внутри макроса, импортированного из контейнера foo . А когда макрос определён и используется в одном и том же контейнере, $crate станет пустой. Это означает, что мы можем написать

 #[macro_export]
 macro_rules! inc {
     ($x:expr) => ( $crate::increment($x) )
 }
чтобы определить один макрос, который будет работать и внутри, и снаружи библиотеки. Имя функции раскроется или в ::increment , или в ::mylib::increment .
Чтобы эта система работала просто и правильно, #[macro_use] extern crate ... может быть написано только в корне вашего контейнера, но не внутри mod . Это обеспечивает, что $crate раскроется в единственный идентификатор.

Вот некоторые распространённые макросы, которые вы увидите в коде на Rust

 panic!
 
Этот макрос вызывает панику текущего потока. Вы можете указать сообщение, с которым поток завершится:

 panic!("oh no!");
Макрос

 vec!
позволяет вам создавать векторы с повторяющимися значениями. Например, вот сто нолей:

 let v = vec![0; 100];
Макросы

 assert!
 assert_eq!
используются в тестах. assert! принимает логическое значение. assert_eq! принимает два значения и проверяет, что они равны. true засчитывается как успех, а false вызывает панику и проваливает тест. Вот так:

 // A-ok!
 
 assert!(true);
 assert_eq!(5, 3 + 2);
 
 // nope :(
 
 assert!(5 < 3);
 assert_eq!(5, 3);
Макрос

 try!
используется для обработки ошибок. Он принимает нечто возвращающее Result и возвращает T если было возвращено Ok ; иначе он делает возврат из функции со значением Err(E) . Вроде такого:

 use std::fs::File;
 
 fn foo() -> std::io::Result<()> {
     let f = try!(File::create("foo.txt"));
 
     Ok(())
 }
Макрос
 unreachable!
применяется, когда вы хотите пометить какой-то код, который никогда не должен исполняться:

 if false {
     unreachable!();
 }
Макрос
 unimplemented!
можно использовать, когда вы хотите, чтобы ваш код прошёл проверку типов, но пока не хотите реализовывать его настоящую логику. Один из примеров — это реализация типажа с несколькими требуемыми методами. Возможно, вы хотите разбираться с типажом постепенно — по одному методу за раз. В таком случае, определите остальные методы как unimplemented! , пока не захотите наконец реализовать их.




Сырые указатели

Стандартная библиотека Rust содержит ряд различных типов умных указателей, но среди них есть два типа, которые экстра-специальные. Большая часть безопасности в Rust является следствием проверок во время компиляции, но сырье указатели не имеют конкретных гарантий и являются небезопасными для использования.
*const T и *mut T в Rust называются «сырыми указателями» (raw pointers). Иногда, при написании определенных видов библиотек, вам по какой-то причине нужно обойти гарантии безопасности Rust. В этом случае, вы можете использовать сырые указатели в реализации вашей библиотеки, вместе с тем предоставляя безопасный интерфейс для пользователей. Например, * указатели допускают псевдонимы, позволяя им быть использованными для записи типов с разделяемой собственности, и даже поточно-безопасные типы памяти ( Rc<T> и Arc<T> типы и реализован полностью в Rust).
Вот некоторые факты о сырых указателях, которые следует помнить и которые отличают их от других типов указателей. Они:
не гарантируют, что они указывают на действительную область памяти, и не гарантируют, что они является ненулевыми указателями (в отличие от Box и & );
не имеют никакой автоматической очистки, в отличие от Box , и поэтому требуют ручного управления ресурсами;
это простые структуры данных (plain-old-data), то есть они не перемещают право собственности, опять же в отличие от Box , следовательно, компилятор Rust не может защитить от ошибок, таких как использование освобождённой памяти (use- after-free);
лишены сроков жизни в какой-либо форме, в отличие от & , и поэтому компилятор не может делать выводы о висячих указателях;
не имеют никаких гарантий относительно псевдонимизации или изменяемости, за исключением изменений, недопустимых непосредственно для *const T .

Создание сырого указателя совершенно безопасно:

 let x = 5;
 let raw = &x as *const i32;
 
 let mut y = 10;
 let raw_mut = &mut y as *mut i32;
А вот его разыменование таковым не является. Следующий код не будет работать:

 let x = 5;
 let raw = &x as *const i32;
 
 println!("raw points at {}", *raw);
Во время выполнения и сырой указатель, * , и ссылка, указывающая на тот же кусок данных, имеют одинаковое представление. По факту, ссылка &T будет неявно приведена к сырому указателю *const T в безопасном коде, аналогично и для вариантов mut (оба приведения могут быть выполнены явно, с помощью, соответственно, value as *const T и value as *mut T ). Рекомендуемым методом преобразования является

 // explicit cast
 let i: u32 = 1;
 let p_imm: *const u32 = &i as *const u32;
 
 // implicit coercion
 let mut m: u32 = 2;
 let p_mut: *mut u32 = &mut m;
 
 unsafe {
     let ref_imm: &u32 = &*p_imm;
     let ref_mut: &mut u32 = &mut *p_mut;
 }
Разыменование с помощью конструкции &*x является более предпочтительным, чем с использованием transmute . Последнее является гораздо более мощным инструментом, чем необходимо, а более ограниченное поведение сложнее использовать неправильно. Например, она требует, чтобы x представляет собой указатель (в отличие от transmute ).




unsafe

Главная сила Rust — в мощных статических гарантиях правильности поведения программы во время исполнения. Но проверки безопасности очень осторожны: на самом деле, существуют безопасные программы, правильность которых компилятор доказать не в силах. Чтобы писать такие программы, нужен способ немного ослабить ограничения. Для этого в Rust есть ключевое слово unsafe . Код, использующий unsafe , ограничен меньше, чем обычный код.
Рассмотрим синтаксис, а затем поговорим о семантике. unsafe используется в четырёх контекстах. Первый — это объявление того, что функция небезопасна:

 unsafe fn danger_will_robinson() {
     // scary stuff
 }
Например, все функции, вызываемые через FFI, должны быть помечены как небезопасные. Другое использование unsafe — это отметка небезопасного блока:

 unsafe {
     // scary stuff
 }
Третье — небезопасные типажи:

 unsafe trait Scary { }
И четвёртое — реализация ( impl ) таких типажей:

 unsafe impl Scary for i32 {}
Важно явно выделить код, ошибки в котором могут вызвать большие проблемы. Если программа на Rust падает с "segmentation fault", можете быть уверены — проблема в участке, помеченном как небезопасный.

В контексте Rust "безопасный" значит "не делает ничего небезопасного". Также важно знать, что некоторое поведение скорее всего нежелательно, но явно не считается небезопасным:
1 Deadlock'и
2 Утечка памяти или других ресурсов
3 Выход без вызова деструкторов
4 Целочисленное переполнение

Rust не может предотвратить все виды проблем в программах. Код с ошибками может и будет написан на Rust. Вышеперечисленные вещи неприятны, но они не считаются именно что небезопасными.
В дополнение к этому, ниже представлен список неопределённого поведения (undefined behavior) в Rust. Избегайте этих вещей, даже когда пишете небезопасный код:
1 Гонка данных
2 Разыменование нулевого или висячего указателя
3 Чтение неинициализированной памяти
4 Нарушение правил о совпадении указателей с помощью сырых указателей
5 &mut T и &T следуют модели LLVM noalias, кроме случаев, когда &T содержит UnsafeCell<U> . Небезопасный код не должен нарушать эти гарантии совпадения указателей.
6 Изменение неизменяемого значения или ссылки без использования UnsafeCell<U>
7 Получение неопределённого поведения с помощью intrinsic-операций компилятора:
Индексация вне границ объекта с помощью std::ptr::offset ( offset intrinsic), кроме разрешённого случая "один байт за концом объекта".
Использование std::ptr::copy_nonoverlapping_memory (intrinsic- операции memcpy32 / memcpy64 ) с пересекающимися буферами
8 Неправильные значения примитивных типов, даже в скрытых полях:
Нулевые или висячие ссылки или упаковки (boxes)
Любое значение логического типа, кроме false (0) или true (1)
Вариант перечисления, не включённый в его определение
Суррогатное значение char или значение char , превыщающее char::MAX
Последовательности байт, не являющиеся UTF-8, в str
9 Размотка стека в код на Rust из чужого кода (через границы FFI), или размотка из кода на Rust в чужой код

В небезопасном блоке или функции, Rust разрешает три ситуации, которые обычно запрещены:
1. Доступ к или изменение статической изменяемой переменной. Rust позволяет пользоваться глобальным изменяемым состоянием с помощью static
mut . Это может вызвать гонку по данным, и в сущности небезопасно.
2. Разыменование сырого указателя.
Сырые указатели поддерживают произвольную арифметику указателей, и могут вызвать целый ряд проблем безопасности памяти и безопасности в целом. В каком-то смысле, возможность разыменовать произвольный указатель — одна из самых опасных вещей, которые вы можете сделать.
3. Вызов небезопасных функций. Это самая мощная возможность.
Эта возможность затрагивает то, откуда можно делать вызов небезопасного кода: небезопасные функции могут вызываться только из небезопасных блоков. Мощь и полезность этой возможности сложно переоценить. Rust предоставляет некоторые intrinsic-операции компилятора в виде небезопасных функций, а некоторые небезопасные функции обходят проверки безопасности для достижения большей скорости исполнения.
Важно отметить, что unsafe , например, не "выключает проверку заимствования". Объявление какого-то кода небезопасным не изменяет его семантику; небезопасность не означает принятие компилятором любого кода. Но она позволяет писать вещи, которые нарушают некоторые из правил.







Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье