Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Синтаксис Rust

 1. Let
 2. Функции
 3. Типы данных
 4. Операторы цикла: loop, while, for
 5. Вектора (vec!)
 6. Изменяемость (mut)
 7. Методы (impl)
 8. Строки
 9. Дженерики
 10. Типажи (traits)
 11. Анонимные функции(closures)
 12. UFCS
 13. Const и static
 
 

Для инициализации переменных в расте используется команда let
 	let x =	5;
Можно проинициализировать сразу несколько переменных:
    let (a,b,c) = (1,2,3); 
При инициализации переменной ее тип можно указать явно:
  let i: i32 = 5;  
По умолчанию команда let создает константу, и следующий код работать не будет :
 let	x = 5;
  x = 10;   
Для этого нужно использовать модификатор mut
 let	mut x = 5;
  x = 10;   
Переменные в расте имеют область видимости:

      let	x:i32	= 17;
     {
         let y: i32 = 3;
         println!("Значение x равно {} и	значение y равно {}", x, y);
     }
     //	Ошибка	компиляции
     println!("Значение	x равно	{} и значение y	равно	{}", x,	y);
    
Функции в расте определяются с помощью ключевого слова fn
 fn	print_sum(x: i32, y: i32)	{
     println!("сумма чисел:	{}", x + y);
 } 
Функция в раст может вернуть одно значение - обратите внимание, что в данном случае точка с запятой не нужна
 fn	add_one(x: i32)	-> i32	{
     x + 1
 }   
Из функции значение можно вернуть досрочно с помощью ключевого слова return
 fn	add_one(x: i32)	-> i32	{
     return x;
     x + 1
 }   
Если нам нужна функция, которая закончит выполнение всей программы
 fn	diverges() -> ! {
     panic!("Эта	функция	не возвращает управление!");
 }   
Для получения подробной отладочной информации можно использовать переменную среды
 RUST_BACKTRACE=1 cargo run
В расте можно создать указатель на функцию
  
 fn plus_one(i: i32) -> i32 {
  i + 1
 }
 
 let f = plus_one;
 let six = f(5);
 
Раст включает в себя стандартные встроенные типы. Логический тип bool
 let x = true;
 let y: bool = false;   
Тип char представляет одиночные юникодные символы размером в 4 байта
 let x = 'x';    
Rust имеет целый ряд числовых типов, разделённых на несколько категорий: знаковые и беззнаковые, фиксированного и переменного размера, числа с плавающей точкой и целые числа. Список числовых типов
 i8
 i16
 i32
 i64
 u8
 u16
 u32
 u64
 isize
 usize
 f32
 f64
Массив в расте - это последовательность элементов одного и того же типа, имеющая фиксированный размер. Массивы неизменяемы по умолчанию.
 let a = [1, 2, 3]; // a: [i32;	3]
 let mut m = [1, 2, 3]; // m: [i32; 3]
Для инициализации всех элементов массива одним и тем же значением есть специальный синтаксис. В следующем примере каждый элемент a будет инициализирован значением 0 :
 let a = [0; 20];	   
Число элементов массива можно получить с помощью метода
  a.len()  
Можно получить элемент массива с помощью индекса, при этом индекс начинается с нуля
  println!("Второе	имя: {}", names[1]);  
Срез в расте - это традиционная выборка, как и в других языках, использует квадратные скобки и ссылку на массив
 let a = [0, 1, 2, 3, 4];
 let complete = &a[..]; // Срез, содержащий	все	элементы	массива	`a`
 let middle = &a[1..4]; // Срез a : только	элементы	1,	2,	и	3
Строковый тип в расте неограничен в размере.

Кортеж — это последовательность фиксированного размера

 let x = (1, "привет"); 
Можно присваивать один кортеж другому, если они содержат значения одинаковых типов
 let mut x = (1, 2);
 let y = (2, 3);
 x = y; 
Доступ к полям кортежа можно получить с помощью индексации
let tuple = (1, 2, 3);
 let x = tuple.0;
 let y = tuple.1;
 let z = tuple.2;
В Rust есть два вида комментариев: строчные комментарии и doc-комментарии
 // строчные комментарии
 /// doc-комментарии
Условный оператор if традиционен
 let x = 5;
 if x == 5 {
  println!("x равняется пяти!");
 } else if x == 6 {
  println!("x это шесть!");
 } else {
  println!("x это ни пять, ни шесть :(");
 }
В расте возможна и такая конструкция
let y = if x == 5 { 10 } else { 15 };	  
В расте есть три возможности для организации циклов
 loop
 while
 for 
Бесконечный цикл
 loop	{
    println!("Зациклились!");
 }  
Цикл while
let mut x = 5;
 let mut done = false;
 while !done {
   x += x - 3;
   println!("{}", x);
   if x % 5 == 0 {
      done = true;
   }
 }
Цикл for
for (x = 0; x < 10; x++) {
   printf("%d\n",x);
 }
 
 for x in 0..10 {
  println!("{}", x);
 }
Для инициализации цикла в диапазоне можно использовать функцию .enumerate()
for (i,j) in (5..10).enumerate() {
  println!("i	= {} и j = {}", i, j);
 }
 
 let lines = "привет\nмир\nhello\nworld".lines();
 for (linenumber, line) in lines.enumerate() {
    println!("{}: {}", linenumber, line);
 }
В Rust для работы с циклами можно использовать стандартные break и continue. Их можно использовать совместно с метками

 'outer: for x in 0..10 {
    'inner: for y in 0..10 {
       if x % 2 == 0 {continue 'outer; } // продолжает цикл по x
       if y % 2 == 0 {continue 'inner; } // продолжает цикл по y
       println!("x: {}, y: {}", x, y);
      }
 }




Вектора

Вектор — это динамический массив, реализованный в виде стандартного библиотечного типа Vec<T> (где <T> является обобщённым типом). Вектора всегда размещают данные в куче. Вы можете создавать их с помощью макроса vec! :

 let v = vec![1, 2, 3, 4, 5];
Обойти элементы вектора можно тремя способами

 let mut v = vec![1, 2, 3, 4, 5];
 
 for i in &v {
   println!("Ссылка {}", i);
 }
 
 for i in &mut v {
   println!("Изменяемая ссылка {}", i);
 }
 
 for i in v {
   println!("Владение вектором и его элементами {}", i);
 }
У раста есть особенности, которые отличают его от других языков. Рассмотрим следующий пример: создадим вектор, потом присвоим этот вектор другому вектору, после чего попробуем вернуться к первому вектору
let v = vec![1, 2, 3];
 let v2 = v;
 println!("v[0] = {}", v[0]);
Мы получим ошибку - вектор был перемещен
error: use of moved value: `v`
 println!("v[0] = {}", v[0]);
То же самое произойдет, когда мы создадим вектор, передадим его в качестве параметра в функцию, а потом попытаемся обратиться к этому вектору
fn take(v: Vec) {
  ...
 }
 
 let v = vec![1,	2,	3];
 take(v);
 println!("v[0]	={}", v[0]);
Если еще раз посмотрим на пример
let v = vec![1, 2, 3];
 let v2 = v;
Данные вектора находятся в куче, а указатель на вектор находится в стеке. Второй указатель также находится в стеке. Проектировщики раста решили, что когда имеются два указателя, указывающие на один и тот же обьект, это ни есть хорошо, и поэтому во второй строке происходит не только создание второго указателя, но и одновременно уничтожение первого указателя. Поэтому мы не можем использовать v.
Это т.н. стандартное умолчательное поведение раста при копировании обьектов. Но в расте есть и другие механизмы копирования - как в любом нормальном языке. Это механизм, использующий типаж
  Copy  
Такое копирование используется в типе данных i32, в который встроен Copy, в отличие от предыдущего примера
let v = 1;
 let v2 = v;
 println!("v	= {}", v);
При передаче ссылок в функцию раст ведет себя стандарным образом. Ссылки по умолчанию - неизменяемые обьекты, и ее нельзя будет изменить внутри этой функции.
Но ссылку можно сделать изменяемой с помощью модификатора mut. При этом действовать нужно аккуратно. Область видимости любой ссылки должна находиться в пределах области видимости владельца. Поясним на примере: следующий код не будет работать:

 let	mut	x	=	5;
 let	y	=	&mut	x;
 *y	+=	1;
 println!("{}",	x);
 
 Этот код выдает нам такую ошибку:
 
 error: cannot borrow `x` as immutable because it is also borrowed as mutable
 println!("{}",	x);
А следующий код будет работать:

 let mut x = 5;
  {
      let y = &mut x;
     *y += 1;
  }
  println!("{}", x);



Изменяемость

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

 let x = 5;
 x = 6; // ошибка
Чтобы изменить переменную x, нужно добавить ключевое слово mut:

 let mut x = 5;
 x = 6; // работает
Т.е. фактически мы создаем копию обьекта x. Если же мы не хотим создавать копию обьекта, но хотим все же его изменить, тогда нужно использовать изменяемую ссылку:

 let mut x = 5;
 let y = &mut x;
При этом y - неизменяемый обьект. Но и его можно сделать изменяемым:

 let mut x = 5;
 let mut y = &mut x;
Мы видим, что в расте изменение обьектов отличается от стандартного подхода и более запутанно.

Вообще, изменяемость — это свойство либо ссылки (&mut), либо имени ( let mut ). Это значит, что, например, у вас не может быть структуры, часть полей которой изменяется, а другая часть — нет. По умолчанию все поля структуры неизменяемы. И так писать нельзя:

 struct Point{
  x: i32,
  mut y: 32,
 }
Изменяемость структуры обьявляется на уровне создания обьектов структуры:

 struct Point{
  x: i32,
  y: 32,
 }
 
 let mut a = Point(x:5, y:6);
 a.x = 10;
В расте изменяемые поля структуры можно задавать с помощью &mut - здесь 'a - время жизни:

 struct Point{
  x: i32,
  y: 32,
 }
 
 struct PointRef<'a>{
  x: &'a mut i32,
  y: &'a mut i32,
 }
 
 let mut point  = Point {x:0, y:0};
 {
 let r = PointRef(x: &mut point.x,  y: &mut point.y);
 *r.x = 5;
 *r.y = 6;
 }
В расте можно обьявить т.н. кортежную структуру - в ней поля не именуются:

 struct Color(i32, i32, i32);
 struct Point(i32, i32, i32);
 
 let black = Color(0, 0, 0);
 let origin = Point(0, 0, 0);
Полезной разновидностью кортежной структуры является структура с одни полем - она называется новым типом

 struct Inches(i32);
 let length = Inches(10);
 let Inches(integer_length) = length;
 println!("Длина в дюймах: {}", integer_length);
В Rust перечисление ( enum ) — это тип данных, который представляет собой один из нескольких возможных вариантов. Каждый вариант в перечислении может быть также связан с другими данными:

 enum Message {
     Quit,
     ChangeColor(i32, i32, i32),
     Move { x: i32, y: i32 },
     Write(String),
 }
 
 let x: Message = Message::Move { x: 3, y: 4 };
Простого if / else часто недостаточно, потому что нужно проверить больше, чем два возможных варианта. Да и к тому же условия в else часто становятся очень сложными. Как же решить эту проблему? В Rust есть ключевое слово match , позволяющее заменить группы операторов if / else чем-то более удобным

 let x = 5;
  match x {
    1	=>	println!("один"),
    2	=>	println!("два"),
    3	=>	println!("три"),
    4	=>	println!("четыре"),
    5	=>	println!("пять"),
    _	=>	println!("что-то	ещё"),
 }



Методы

В расте методы реализуются с помощью impl - в следующем примере метод area

 struct	Circle	{
    x:	f64,
    y:	f64,
    radius:	f64,
 }
 
 impl	Circle	{
     fn	area(&self) -> f64	{
     std::f64::consts::PI * (self.radius * self.radius)
 				}
 }
 
 fn	main()	{
     let c = Circle { x: 0.0, y: 0.0, radius: 2.0 };
     println!("{}", c.area());
 }
Как и везде, в расте первым аргументом метода является &self. Есть три варианта
 self
 &self
 &mut self
В расте можно выполнить вызов цепочки вложенных методов типа

 foo.bar().baz()
В предыдущем примере с Circle добавим в имплементацию еще один метод

 impl	Circle	{
     fn	area(&self) -> f64	{
     std::f64::consts::PI * (self.radius * self.radius)
 				}
 				
    fn grow(&self, increment: f64) -> Circle {
      Circle { x: self.x, y: self.y, radius: self.radius + increment}
    }
 }
 
 fn main() {
    let c = Circle { x: 0.0, y: 0.0, radius: 2.0 };
    println!("{}",	c.area());
    let	d	=	c.grow(2.0).area();
    println!("{}",	d);
 }
Если вы не хотите self, тогда можно применить статические методы. В следующем примере со все тем же Circle мы используем стандартный вариант вызова статического метода в виде Struct::method()

 impl	Circle	{
   fn new(x: f64, y: f64, radius: f64) -> Circle {
     Circle	{
         x: x,
         y: y,
         radius: radius,
       }
     }
 }
 fn main() {
     let c = Circle::new(0.0, 0.0, 2.0);
 }
В расте нет перегрузки методов, именованных аргументов или переменного количества аргументов.



Строки

Строки — важное понятие для любого программиста. Система обработки строк в Rust немного отличается от других языков, потому что это язык системного программирования. Работать со структурами данных с переменным размером довольно сложно, и строки — как раз такая структура данных. Кроме того, работа со строками в Rust также отличается и от некоторых системных языков, таких как C.
string — это последовательность скалярных значений юникод, закодированных в виде потока байт UTF-8. Все строки должны быть гарантированно валидными UTF-8 последовательностями. Кроме того, строки не оканчиваются нулём и могут содержать нулевые байты.
В Rust есть два основных типа строк: &str и String . &str — это строковый срез. Строковые срезы имеют фиксированный размер и не могут быть изменены. Они представляют собой ссылку на последовательность байт UTF-8:
 let greeting = "Всем привет."; // greeting: &'static str
Строковые литералы могут состоять из нескольких строк. Такие литералы можно записывать в двух разных формах. Первая будет включать в себя перевод на новую строку и ведущие пробелы:

 let s = "foo
 bar";
Вторая форма, включающая в себя \ , вырезает пробелы и перевод на новую строку:

 let s = "foo\
 bar";	
Тип String представляет собой строку, размещенную в куче. Эта строка расширяема, и она также гарантированно является последовательностью UTF-8 . String обычно создаётся путем преобразования из строкового среза с использованием метода to_string .

 let mut s = "Привет".to_string(); // mut s: String
 println!("{}", s);
 s.push_str(", мир.");
 println!("{}", s);
String преобразуются в &str с помощью & :

 fn takes_slice(slice: &str) {
  println!("Получили: {}", slice);
 }
 
 fn main() {
     let s = "Привет".to_string();
     takes_slice(&s);
 }
Строки не поддерживают индексацию

 let s = "привет";
 println!("Первая буква s — {}", s[0]); // ОШИБКА!!!
Можно получить срез строки с помощью синтаксиса

 let dog = "hachiko";
 let hachi = &dog[0..5];
Если у вас есть String , то вы можете присоединить к нему в конец &str :

 let hello = "Hello	".to_string();
 let world ="world!";
 let hello_world = hello + world;
Но если у вас есть две String , то необходимо использовать & . Это сделано потому, что &String может быть автоматически приведен к &str . Эта возможность называется «Приведение при разыменовании»

 let hello = "Hello ".to_string();
 let world = "world!".to_string();
 let hello_world = hello + &world;




Дженерики

Иногда, при написании функции или типа данных, мы можем захотеть, чтобы они работали для нескольких типов аргументов. У Rust есть возможность, которая даёт нам лучший способ реализовать это с помощью дженериков. Дженерики еще называют «параметрическим полиморфизмом». Это означает, что типы или функции имеют несколько форм (poly — кратно, morph — форма) по данному параметру («параметрический»). Стандартная библиотека Rust предоставляет несколько дженериков, в том числе Option:

 enum Option<T> {
     Some(T),
     None,
 }
Пример использования:

 let x: Option<i32> = Some(5);
 let y: Option<f64> = Some(5.0f64);
Дженерик позволяет использовать несколько параметров с помощью другого стандартного дженерика - Result<T, E> - :

 enum Result {
     Ok(T),
     Err(E),
 }
Вместо заглавных букв T, E можно использовать любые другие. Этот дженерик дает возможность возвращать кроме результата вычислений еще и ошибку, если таковая имеется.

Дженерик-функции имеют аналогичный синтаксис:

 fn takes_anything<T>(x: T) {
     // do something with x
 }
Несколько аргументов могут иметь один и тот же обобщённый тип:

 fn takes_two_of_the_same_things<T>(x: T, y: T) {
     // ...
 }
Дженерик-функция может иметь несколько обобщенных типов:

 fn takes_two_things<T, U>(x: T, y: U) {
     // ...
 }


Можно создать дженерик структуру

 struct Point<T> {
     x: T,
     y: T,
 }
 
 let int_origin = Point { x: 0, y: 0 };
 let float_origin = Point { x: 0.0, y: 0.0 };




Traits

Трэйты - их еще называют типажами - дают возможность придать типу дополнительную функциональность. Трэйты похожи на методы, но отличается тем, что дают лишь определение для метода:

 struct Circle {
     x: f64,
     y: f64,
     radius: f64,
 }
 
 trait HasArea {
     fn area(&self) -> f64;
 }
 
 impl HasArea for Circle {
     fn area(&self) -> f64 {
         std::f64::consts::PI * (self.radius * self.radius)
     }
 }
Трэйты могут быть использованы для ограничения обобщенных типов. Реализация обычных методов для стандартных типов в расте считается плохой практикой программирования, и вместо них рекомендуется использовать трэйты, хотя код при этом становится менее читабельным. Трэйт может наследовать другой трэйт:

 trait Foo {
     fn foo(&self);
 }
 
 trait FooBar : Foo {
     fn foobar(&self);
 }
В расте есть атрибуты, которые позволяют унаследовать другие трэйты с помощью синтаксиса

 #[derive(Debug)]
 struct Foo;
 
 fn main() {
     println!("{:?}", Foo);
 }
У раста есть встроенные стандартные трэйты, в частности - Drop. Он срабатывает тогда, когда программа выходит из зоны видимости обьекта, которому принадлежит дроп. Этот трэйт может использоваться например для того, чтобы освободить какие-то ресурсы:

 struct HasDrop;
 
 impl Drop for HasDrop {
     fn drop(&mut self) {
         println!("Dropping!");
     }
 }
 
 fn main() {
     let x = HasDrop;
 
     // do stuff
 
 } // здесь автоматически сработает дроп




Closures

Помимо именованных функций Rust предоставляет еще и анонимные функции . Анонимные функции, которые имеют связанное окружение, называются 'замыкания', или closures. Они так называются потому что они замыкают свое окружение. Например

 let plus_one = |x: i32| x + 1;
 
 assert_eq!(2, plus_one(1));
Мы создаем связывание, plus_one , и присваиваем ему анонимную функцию. Аргументы замыкания располагаются между двумя вертикальными символами | , а телом замыкания является выражение, в данном случае: x + 1 . Помните, что { } также является выражением, поэтому тело замыкания может содержать много строк:

 let plus_two = |x| {
     let mut result: i32 = x;
 
     result += 1;
     result += 1;
 
     result
 };
 
 assert_eq!(4, plus_two(2));
Обратите внимание, что есть несколько небольших различий между замыканиями и обычными функциями, определенными с помощью fn . Первое отличие состоит в том, что для замыкания мы не должны указывать ни типы аргументов, которые оно принимает, ни тип возвращаемого им значения. Второе отличие — синтаксис очень похож, но все же немного отличается:

 fn  plus_one_v1   (x: i32) -> i32 { x + 1 }
 let plus_one_v2 = |x: i32| -> i32 { x + 1 };
 let plus_one_v3 = |x: i32|          x + 1  ;




UFCS

Когда раст имеет дело с перегрузкой функций, т.е. с функциями с одинаковыми именами, нужно использовать специальное правило для вызова таких функций - оно называется - Универсальный синтаксис вызова функций - universal function call syntax (ufcs). Рассмотрим пример с двумя типажами:

 trait Foo {
     fn f(&self);
 }
 
 trait Bar {
     fn f(&self);
 }
 
 struct Baz;
 
 impl Foo for Baz {
     fn f(&self) { println!("Baz’s impl of Foo"); }
 }
 
 impl Bar for Baz {
     fn f(&self) { println!("Baz’s impl of Bar"); }
 }
 
 let b = Baz;
 b.f();
Этот код вызовет ошибку. Нам нужен способ указать, какой конкретно метод нужен, чтобы устранить неоднозначность. Эта возможность называется ucfs, и выглядит это так:

 Foo::f(&b);
 Bar::f(&b);
Когда мы вызываем метод, используя синтаксис вызова метода, как например b.f() , Rust автоматически заимствует b , если f() принимает в качестве аргумента &self . В этом же случае, Rust не будет использовать автоматическое заимствование, и поэтому мы должны явно передать &b. Сокращенная форма ufcs выглядит так

 Trait::method(args);
Расширенная форма ufcs

 <Type as Trait>::method(args);
Синтаксис <>:: является средством предоставления подсказки типа. Тип располагается внутри <> . В этом случае типом является Type as Trait , указывающий, что мы хотим здесь вызвать Trait версию метода. Часть as Trait является необязательной, если вызов не является неоднозначным. То же самое что с угловыми скобками, отсюда и короткая форма. Вот пример использования длинной формы записи с использованием угловых скобок, позволяющих вызывать трэйт(типаж):

 trait Foo {
     fn foo() -> i32;
 }
 
 struct Bar;
 
 impl Bar {
     fn foo() -> i32 {
         20
     }
 }
 
 impl Foo for Bar {
     fn foo() -> i32 {
         10
     }
 }
 
 fn main() {
     assert_eq!(10, <Bar as Foo>::foo());
     assert_eq!(20, Bar::foo());
 }    





Const и static

В расте константы задаются с помощью ключевого слова const, при этом нужно явно указать тип

 const N: i32 = 5;
Константы живут в течении всего времени работы программы.
В расте можно создать глобальную переменную

 static N: i32 = 5;
Статические значения также живут в течение всего времени работы программы. Статическое значение можно сделать изменяемым

 static mut N: i32 = 5;
Чтение и одновременное изменение статического значения в расте небезопасно (unsafe), и делать это нужно в соответствующих блоках

 unsafe {
     N += 1;
 
     println!("N: {}", N);
 }






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

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

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