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...2905 
 Linux Kernel 2.6...2470 
 William Gropp...2182 
 Rodriguez 6...2014 
 C++ Templates 3...1945 
 Trees...1938 
 Kamran Husain...1866 
 Secure Programming for Li...1792 
 Максвелл 5...1710 
 DevFS...1694 
 Part 3...1684 
 Stein-MacEachern-> Час...1632 
 Go Web ...1626 
 Ethreal 4...1619 
 Arrays...1607 
 Стивенс 9...1604 
 Максвелл 1...1592 
 FAQ...1539 
 
  01.01.2024 : 3621733 посещений 

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






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

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

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