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

Введение в Rust

Rust — компилируемый язык программирования общего назначения, спонсируемый Mozilla Research, поддерживающий функциональное программирование, модель акторов и процедурное программирование. Объектно-ориентированное программирование как таковое языком не поддерживается, но язык позволяет реализовать большинство понятий ООП при помощи других абстракций, например, типажей.
Rust — это системный язык программирования, который предназначен для решения трёх основных задачах: безопасность, скорость и параллелизм. Он сопоставим по скорости и возможностям с C++, однако, даёт большую безопасность при работе с памятью, что обеспечивается механизмами ограничения.
Более подробнее читайте на википедии

Для того, чтобы установить раст, нужно сходить на официальный сайт и выбрать инсталлятор в виде архива по адресу
https://www.rust-lang.org/en-US/downloads.html
После распаковки архива раст нужно установить с помощью файла
install.sh 
После чего раст готов к работе.

Hello World

Для начала напишем первую программу - Hello World. Создадим каталог с таким названием. В нем создадим подкаталог src. В подкаталог положим файл
main.rs
следующего содержания

 fn main() {
     println!("Привет,	мир!");
 }
Зайдем в подкаталог src и запустим из командной строки две команды
 $	rustc	main.rs
 $	./main
 
Начало каждой программы на расте начинается с функции main. Каждое выражение в программе на расте заканчивается точкой с запятой. Раст - компилятор, и перед выполнением любая программа должна быть вначале скомпилирована.

rustc подходит для небольших программ. В случае роста программ нам потребуется специальный инструмент, который называется
Cargo 
Cargo — это система сборки и пакетный менеджер для Rust. Cargo делает 3 вещи :
1 сборка кода
2 загрузка библиотек, от которых зависит ваш код,
3 сборка этих библиотек.

По умолчанию при инсталяции раста cargo устанавливается вместе с ним.
Теперь соберем наш проект Hello world с помощью cargo. Внутри каталога Hello world создаим конфигурационный файл
Cargo.toml 
следующего содержания
 [package]
 name	=	"hello_world"
 version	=	"0.0.1"
 authors	=	["iakovlev.org"	]
 
Осталось запустить команду
Cargo run  
Сборку можно найти в подкаталоге ./target. Для сборки релиза можно запустить команду
Cargo build --release  


Всю только что описанную процедуру по созданию проекта можно выполнить с помощью одной единственной команды
 cargo new hello_world --bin   
При этом будет созданы каталоги и все необходимые файлы для генерация исполняемого файла.




Угадай число

Следующая программа будет более осмысленной - мы сделаем реализацию игры Угадай Число. Для начала сгенерируем шаблон для проекта:
 cargo new game --bin   
Функция main будет иметь вид
 
 use	std::io;
 fn	main()	{
     println!("Угадайте	число!");
     println!("Пожалуйста, введите	предположение.");
     let	mut	guess	= String::new();
     io::stdin().read_line(&mut	guess).expect("Не	удалось	прочитать	строку");
     println!("Ваша	попытка:	{}",	guess);
 }
Вначале мы импортируем стандартную библиотеку ввода io. Выражение let используется для создания переменной. Модификатор mut разрешает менять переменную. String - строковый тип переменной utf8 произвольной длины. Далее идет стандартный обработчик ввода с терминала в виде функции read_line, которой в качестве аргумента передается ссылка на переменную. Метод expect проверяет, правильный ли тип мы ввели, и в случае ошибки вызовет панику и останов программы. Теперь можно запустить программу
 cargo run   
Теперь нам нужно расширить функционал этой программы. Добавим в нее генератор случайного числа. Для этого в файле Cargo.toml добавим пару строк, в которых будет ссылка на стандартную библиотеку rand
   
 [dependencies]
 rand="0.3.0"
 
При этом в терминале мы увидим примерно следующее
  Updating registry `https://github.com/rust-lang/crates.io-index`
  Downloading rand v0.3.14
  Downloading libc v0.2.17
    Compiling libc v0.2.17
    Compiling rand v0.3.14
    Compiling game v0.1.0 (file:///Rust/game)
    Finished debug [unoptimized + debuginfo] target(s) in 5.78 secs
 
Если вы запустили эту команду первый раз, вам может потребоваться время на ее выполнение. При этом cargo скачает с интернета две библиотеки. Их версии прописываются в специальном файле
 Cargo.lock   
И каждый раз, когда мы будем собирать проект, будут использоваться те номера версий, которые прописаны именно в Cargo.lock. Если же мы хотим использовать более новые версии, нужно использовать префикс
 cargo update  
Теперь используем rand в новой версии программы. Окончательный вариант программы с циклической проверкой будет выглядеть так:
   
 extern	crate	rand;
 use	std::io;
 use	std::cmp::Ordering;
 use	rand::Rng;
 fn	main()	{
     println!("Угадайте	число!");
     let	secret_number	=	rand::thread_rng().gen_range(1,	101);
     loop	{
                     println!("Пожалуйста,	введите	предположение.");
                     let	mut	guess	=	String::new();
                     io::stdin().read_line(&mut	guess)
                                     .expect("Не	удалось	прочитать	строку");
                     let	guess:	u32	=	match	guess.trim().parse()	{
                                                     Ok(num)	=>	num,
                                                     Err(_)	=>	continue,
                     };
                     println!("Ваша	попытка:	{}",	guess);
                     match	guess.cmp(&secret_number)	{
                             Ordering::Less =>	println!("Слишком	маленькое!"),
                             Ordering::Greater	=>	println!("Слишком	большое!"),
                             Ordering::Equal	=>	{
                                                             println!("Вы	выиграли!");
                                                             break;
                             }
                     }
     }
 }
 
Здесь метод
 guess.cmp  
может использоваться для сравнения обьектов. Результатом сравнения будет обьект типа
  Ordering 
который фактически является перечислением - enum. В качестве селектора мы используем оператор
  match 
Мы переопределяем тип переменной
  guess 




Обедающие философы

В следующей задаче мы будем использовать многопоточность.
В древние времена богатые филантропы пригласили погостить пятерых философов. Им выделили каждому по комнате, в которой они могли заниматься своей профессиональной деятельностью — мышлением. Также была общая столовая, где стоял большой круглый стол, а вокруг него пять стульев. Каждый стул имел табличку с именем философа, который должен был сидеть на нем. Слева от каждого философа лежала вилка, а в центре стола стояла большая миска со спагетти, которая постоянно пополнялась. Как подобает философам, они большую часть своего времени проводили в раздумьях. Но однажды они почувствовали голод и отправились в столовую. Каждый сел на свой стул, взял по вилке и воткнул её в миску со спагетти. Но сущность запутанных спагетти такова, что необходима вторая вилка, чтобы отправлять спагетти в рот. То есть философу требовалась ещё и вилка справа от него. Вилка может быть использована только одним философом одновременно. Если другой философ захочет её взять, то ему придётся ждать когда она освободится. Нужно придумать сценарий, при котором все философы cмогут поесть.
Для начала создадим новый проект командой
  cargo new phil --bin 
В файле main.rs создадим структуру для философов и напишем первую версию программы
   
 struct	Philosopher	{
     name:	String,
 }
 
 impl	Philosopher	{
 	fn	new(name:	&str)	->	Philosopher	{
 		Philosopher	{
                     name:	name.to_string(),
 				}
 	}
 }
 
 fn	main()	{
     let	p1	=	Philosopher::new("Джудит	Батлер");
     let	p2	=	Philosopher::new("Рая	Дунаевская");
     let	p3	=	Philosopher::new("Зарубина	Наталья");
     let	p4	=	Philosopher::new("Эмма	Гольдман");
     let	p5	=	Philosopher::new("Анна	Шмидт");
 }
Мы обьявляем структуру struct и делаем для нее реализацию impl, внутри реализации с помощью статической функции new мы создаем новый эксземляр структуры Philosopher. Функция new - это вариант для другой реализации, которая могла бы выглядеть так, если бы мы не использовали new:
  	let	p1	=	Philosopher	{	name:	"Джудит	Батлер".to_string()	};
что выглядит не совсем изящно.
Далее нужно написать метод для окончания приема пищи и реализовать цикл по приему пищи. Вторая версия программы
   
 struct	Philosopher	{
     name:	String,
 }
 
 impl	Philosopher	{
     fn	new(name:	&str)	->	Philosopher	{
 	Philosopher	{
             name:	name.to_string(),
 			}
 }
 
     fn	eat(&self)	{
 	println!("{}	закончила	есть.",	self.name);
     }
 }
 
 
 fn	main()	{
     let	philosophers	=	vec![
         Philosopher::new("Джудит	Батлер"),
         Philosopher::new("Рая	Дунаевская"),
         Philosopher::new("Зарубина	Наталья"),
         Philosopher::new("Эмма	Гольдман"),
         Philosopher::new("Анна	Шмидт"),
     ];
 
     for	p	in	&philosophers	{
                  p.eat();
     }
 }
 
Мы создали для философов массив - или вектор
  Vec< T > 
Вектор является расширенной версией массива. В цикле for мы перебираем вектор, получая ссылку на очередного философа на каждой итерации.
В расте методы ожидаемо получают обязательный параметр
  self 
Мы сразу видим , что new - это статический метод, а eat() - метод.

В следующей - третьей - версии программы философы будут есть одновременно

 use	std::thread;
 use	std::time::Duration;
 struct	Philosopher	{
     name:	String,
 }
 
 impl	Philosopher	{
     fn	new(name:	&str)	->	Philosopher	{
 		Philosopher	{
 			name:	name.to_string(),
 		}
     }
 
     fn	eat(&self)	{
         println!("{}	начала	есть.",	self.name);
         thread::sleep(Duration::from_millis(1000));
         println!("{}	закончила	есть.",	self.name);
     }
 }
 
 fn	main()	{
     let	philosophers	=	vec![
         Philosopher::new("Джудит	Батлер"),
         Philosopher::new("Рая	Дунаевская"),
         Philosopher::new("Зарубина	Наталья"),
         Philosopher::new("Эмма	Гольдман"),
         Philosopher::new("Анна	Шмидт"),
     ];
 
     let	handles:	Vec<_>	=	philosophers.into_iter().map(|p|	{
         thread::spawn(move	||	{
                                         p.eat();
         })
     }).collect();
     
     for	h	in	handles	{
           h.join().unwrap();
     }
 }
Прием пищи длится условно 1 секунду
   thread::sleep(Duration::from_millis(1000));
Мы создаем вектор для того, чтобы создать несколько потоков, для каждого потока будет создан дескриптор.
 let	handles:	Vec<_>	=  
Итерация по вектору выполняется с помощью стандартного метода
  philosophers.into_iter()  
Итератор передает элемент вектора в поток. У итератора вызывается метод
  map 
который принимает замыкание в качестве аргумента и вызывает это замыкание для каждого из элементов итератора
  	thread::spawn(move	||	{
 		p.eat();
 	}) 
Функция
  thread::spawn 
берет в качестве аргумента замыкание и исполняет это замыкание в отдельном потоке. При этом переменная, называемая
  p 
блокируется с помощью ключевого слова
   move
При этом в потоке у переменной p вызывается метод eat() Метод
  }).collect(); 
создает коллекцию, состоящую из дескрипторов потоков. Далее мы блокируем функцию main до тех пор, пока не выполним все потоки
 for	h	in	handles	{
 	h.join().unwrap();
 }  
В последней - четвертой - версии программы - мы добавим вилки :-)
 
 use	std::thread;
 use	std::time::Duration;
 use	std::sync::{Mutex,	Arc};
 
 struct	Philosopher	{
     name:	String,
     left:	usize,
     right:	usize,
 }
 
 impl	Philosopher	{
     fn	new(name:	&str,	left:	usize,	right:	usize)	->	Philosopher	{
         Philosopher	{
             name:	name.to_string(),
             left:	left,
             right:	right,
         }
     }
 
     fn	eat(&self,	table:	&Table)	{
         let	_left	=	table.forks[self.left].lock().unwrap();
         thread::sleep(Duration::from_millis(150));
         let	_right	=	table.forks[self.right].lock().unwrap();
         println!("{}	начала	есть.",	self.name);
 
         thread::sleep(Duration::from_millis(1000));
         println!("{}	закончила	есть.",	self.name);
     }
 }
 
 struct	Table	{
     forks:	Vec<Mutex<()>>,
 }
 
 fn	main()	{
 
     let	table	=	Arc::new(Table	{	forks:	vec![
             Mutex::new(()),
             Mutex::new(()),
             Mutex::new(()),
             Mutex::new(()),
             Mutex::new(()),
     ]});
     
     let	philosophers	=	vec![
                 Philosopher::new("Джудит	Батлер",	0,	1),
                 Philosopher::new("Рая	Дунаевская",	1,	2),
                 Philosopher::new("Зарубина	Наталья",	2,	3),
                 Philosopher::new("Эмма	Гольдман",	3,	4),
                 Philosopher::new("Анна	Шмидт",	0,	4),
     ];
     
     let	handles:	Vec<_>	=	philosophers.into_iter().map(|p|	{
     let	table	=	table.clone();
     
     thread::spawn(move	||	{
                                     p.eat(&table);
     })
     }).collect();
     
     for	h	in	handles	{
            h.join().unwrap();
     }
 }
Мы создаем новую структуру Table

 use	std::sync::Mutex;
  struct	Table	{
     forks:	Vec<Mutex <()>>,
 }
Структура Table содержит вектор Мьютексов. Мьютекс — способ управления доступом к данным для параллельно выполняющихся потоков: только один поток может получить доступ к глобальным данным в конкретный момент времени. Мы используем пустой кортеж () внутри мьютекса. В структуру философов мы добавляем два поля - левую и правую вилку. Мы получаем доступ к списку вилок через структуру Table, используем идентификаторы вилок в виде self.left и self.right. Мы получаем мьютекс, который регулирует доступ к вилке, и вызываем для него метод
 lock  
блокируя одновременный доступ к вилке. Метод
   unwrap() 
дает нам возможность не задумываться о проблемах, связанных с блокировками и падением программы. Использование символа подчеркивания в переменных
  _left 	 и	 _right 
делается для локальных переменных с ограниченной областью видимости. В функции main() мы создаем экземпляр структуры Table и оборачиваем его в атомарный счетчик ссылок (atomic reference count), который передается в новый поток, при этом счетчик увеличивается, а по завершении работы потока счетчик уменьшается:
  Arc<T>  
Внутри цикла
 map() / collect()   
мы вызываем метод
 table.clone()   
который клонирует счетчик, увеличивает его на единицу, а потом инкрементирует. В программе мы дважды используем переменную table для создания разных обьектов.



Создание библиотеки

Для этого сначала создадим новый проект, выполнив команду
  cargo new embed  
В файл lib.rs вставим следующий библиотечный код
    
 use	std::thread;
 
 #[no_mangle]
 
 pub	extern	fn	process()	{
  let	handles:	Vec<_>	=	(0..10).map(|_|	{
   thread::spawn(||	{
    let	mut	x	=	0;
    for	_	in	0..5_000_000	{
       x	+=	1
    }
    x
    })
   }).collect();
   for	h	in	handles	{
       println!("Поток	завершился	со	счётом={}",
       h.join().map_err(|_|	"Не	удалось	соединиться	с	потоком!").unwrap());
   }
 }
В коде создаются 10 потоков, которые собираются в вектор handles. Внутри каждого потока выполняется по 5 миллионов итераций в цикле. Для того, чтобы эту библиотеку можно было использовать, нужно сделать следующее - добавить в начало кода новый атрибут и сделать функцию публичной для доступа
  #[no_mangle] 
  pub	extern	fn	process()	{ 
Далее в файле Cargo.toml нужно нужно добавить строчки
  
 [lib]
 name	=	"embed"
 crate-type	=	["dylib"]
Собираем библиотеку
  	cargo	build	--release  
Библиотеку можно найти в файле target/release/libembed.so. Файл libembed.so является динамической библиотекой - shared object. Теперь можно написать код, например, на питоне , и вызвать из питона эту библиотеку
   
 from	ctypes	import	cdll
 lib	=	cdll.LoadLibrary("target/release/libembed.so")
 lib.process()
 print("сделано!")

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

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

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