Язык программирования Rust

и его использование в Web

Докладчик Антон Шрамко

Почему я хочу рассказать о Rust?

  • Rust это удобный компилируемый язык высокого уровня, с высокой производительностью.
  • Это язык который можно использовать для широкого круга задач, от системных до прикладных.
  • Это удобный и практичный язык для написания высоко-производительных web сервисов.

What is Rust?

  • Язык спроектирован Грэйдоном Хором, в 2006 году, но изначально полномасштабной разработки и продвижения языка не было.
  • В 2009 году к разработке языка подключились Mozilla для использования в проекте Servo, в 2010 году язык впервые был представлен на конференции Mozilla широкой публике.
  • В 2012 году была представлена первая альфа версия компилятора Rust версии 0.1.
  • Первая стабильная версия языка вышла в мае 2015 (1.0)

История создания

What is Rust?

  • Основные цели языка: безопасность, скорость, паралелизм, управление ресурсами.
  • По заявлениям разработчиков в других языках есть различные недочеты, на уровне абстракции языка связанные с основными целями.

Цели языка

What is Rust?

  • Rust не собирается быть «принципиально новым языком для всего» он делает только то что от него нужно, и старается быть полезным для большинства возможных случаев и целей.
  • Язык не являет в себе догматизма по каким либо пунктам, будь то 100% безопасность, стабильность или эффективность.
  • Rust не ставит целей полностью заместить собой С++, или воспроизвести полный набор его возможностей. Впрочем как и любого другого языка.

Какие цели не преследует Rust?

Обзор языка Rust

Базовые типы: Строка, Булев, Массив, Кортеж

fn main() {
  let variable = "Hello world!"; 
  let boolean: bool = true;
  let array: [i32; 3] = [1,2,3]; // массив, фиксированный размер
  let vec: Vec<i32> = vec![1,2,3]; // Вектор - расширяемый массив
  let tuple: (i32, &str) = (21, "String"); // Кортеж
}

Обзор языка Rust

Базовые типы: Функции

fn void_function() {
  println!("Hello World!");
}

fn number_square(number: i32) -> i32 {
  number*number
}

// Функция обертка над макросом прерывания потока. 
fn throw_panic() -> ! {
  panic!("Oh, shit - unknown error");
}

// Базовая исполняемая функция
fn main() {
  void_function();  
  // Вызов `fn number_square()`
  let result_square = number_square(5);
  println!("{}", result_square);
  // Функция указатель - указывает на `fn number_square()`
  let f_pointer: fn(i32) -> i32 = number_square;
  let result_pointer = f_pointer(4);
  println!("{}", result_pointer);
  throw_panic();
}

Обзор языка Rust

Владения, Заимствования, Жизненный цикл

  • Каждое значение может иметь только одного владельца, значение уничтожается вместе с владельцем, но при этом есть возможность передавать право владения дальше.
  • Нельзя чтобы существовало несколько ссылок разных видов, например может быть несколько неизменяемых ссылок или одна изменяемая.
  • Ссылка на значение не может пережить владельца.

Обзор языка Rust

Пример: Владение

fn your_action() {
  // присваиваем значение
  let data = !vec[1,2,3];
  // при выходе из области 
  // (по завершению работы функции)
  // данные уничтожаются
}

сколько живут данные?

fn main() {
  let data = vec![1,2,3];
  // Присваивая data переменной bind
  // Мы передаем ей право владения данными
  let bind = data;
  
  // Ошибка, право владения уже передано bind
  println!("{}", data[1]);
}

присваивание = владение

fn bar(x: Vec<i32>) {
  unimplemented!();
}

fn main() {
  let vec: Vec<i32> = vec![1,2,3];
  bar(vec);
  // Ошибка, владение на данные уже передано
  println!("{}", vec[1]);
}

Отдаем данные в функцию -
передаем владение

Обзор языка Rust

Пример: Заимствования

fn some_action(i: &Vec<i32>) {
  unimplemented!();
}
fn main() {
  let vec = vec![1,2,3];
  //  Вызываем с ссылочным аргументом
  some_action(&vec);
  // Данные все еще доступны!
  println!("{}", vec[1]);
}

Ссылки - в функциях

fn main() {
  let mut data = vec![1,2,3];
  // Константные ссылки.
  let other = &data;
  let other2 = &data;
}

... В переменных

fn main() {
  let mut data = vec![1,2,3];
  // Мутабельная ссылка
  let mut_link = &mut data;
}
fn main() {
  let mut data = vec![1,2,3];
  // Выдаст ошибку компиляции
  let mut_link = &mut data;
  let const_link = &data;
}

Правило:
Одна мутабельная ссылка или множество константных

Обзор языка Rust

Кратко о мутабельности...

fn main() {
  // По умолчанию не мутабельны.
  let const_data = vec![1,2,3];
  
  // Мутабельные данные.
  let mut data:i32 = 5;
  data+=10;
  
  // Мутабельная ссылка, на мутабельные данные.
  let mut link = &mut data;
  *link+=10;
}

К уже упомянутым в прошлом слайде правилам - коротко о мутабельности

Мутабельность полей

struct Obj {
  field: i32,
  // По умолчанию нельзя отдельное поле
  // сделать мутабельным, только на уровне
  // конкретного связывания. 
  
  // Это имитация мутабельности через Cell
  mut_field: Cell<i32>
};

let object = Obj { field: 5, mut_field: Cell::new(10) };
// Присвоение нового значения
object.mut_field.set(25);

Обзор языка Rust

Перечисления, использование `match`

// Декларация перечисления
enum Enumeration {
    AsStruct { a: i32, b: i32 },
    Fn(i32),
    BasicEnum
}

fn handle_fn(x: i32, y: i32) {
  unimplemented!();
}

fn main() {
  // Декларация переменной от перечисления
  let data: Enumeration = Enumeration::AsStruct { x: 1, y: 1 };

  // Обработка вариантов, от перечисления
  match data {
    // Проверка и отправление аргументов по ключу
    Enumeration::AsStruct { a: i32, b: i32 } => handle_fn(a, b),
    
    // Как функцию..
    Enumeration::Fn(arg) => println!("{}", arg),
    
    // Для остальных значений...
    // match должен отработать все возможные варианты, 
    // если все варианты не обработаны - ошибка.
    // Для этого существует параметр `_` 
    // для обработки остальных вариантов.
    _ => unimplemented!()
  };
}

Обзор языка Rust

В дополнение о `match`


fn main() {
  let mut value = "Hello";
  // Обработать разные значения
  match value {
    "Hello" => println!("Its 'Hello'!"),
    _ => println!("Otherwise")
  };
  
  // Присвоение значения через обработку вариаций
  let select_value = match value {
    "Привет" => "Привет мир!",
    "Hello" | "Hello!" => "Hello World!",
    _ => "Кто тут с приветом?"
  };

  println!("{}", select_value);

  // С получением ссылки
  match value {
    // Мутабельная ссылка
    ref mut ref_link => println!("{}", ref_link)
  };
  
  // Вместе с структурами
  struct ObjOne {
    a: i32,
    b: i32
  }
  let object = ObjOne { a: 1, b: 1 };
  // Так..
  match object {
    ObjOne { a, b } => unimplemented!(),
  };

  // Или так..
  match object {
    ObjOne { a, ..} => unimplemented!(),
  };
}

Обзор языка Rust

Структуры: Объявление, Методы, Цепочки вызовов


// Объявление структуры
struct Human {
  firstname: String, // Объявление поля типа строка
  lastname: String,
  age: u32
}

// Имплементация методов для структуры
impl Human {
  fn info(&self) -> String {
    let info_value = format!("{} {}, возраст - {}", &self.firstname, &self.lastname, &self.age);
    println!("Полная информация: {}", info_value);
    String::from(info_value)
  }
  // Метод с мутабельной ссылой на экземпляр + реализация цепочки вызова
  fn set_age(&mut self, age: u32) -> &mut Human {
    self.age = age;
    self
  }
  // Статический метод
  fn new(firstname: &str, lastname: &str, age: u32) -> Human {
    Human {
      firstname: firstname.to_string(),
      lastname: lastname.to_string(),
      age: age
    }
  }
}

fn main() {
  // Экземпляр структуры
  let mut developer = Human {
    lastname: "Шрамко".to_string(),
    firstname: "Антон".to_string(), 
    age: 19
  };

  // Создание экземпляра из статического метода ::new
  let mut manager = Human::new("Максим", "Мясоедов", 45);
  manager.info();

  println!("Имя: {}", developer.firstname); // Доступ к полям экземпляра
  let info_string = developer.info(); // Использование метода
  developer.set_age(25).info(); // Вызов цепочки
}

Обзор языка Rust

Generics

// Перечисление с заданным обобщенным типом
enum Human<N, A> {
  Name(N),
  Age(A)
}

// Структура с заданным обобщенным типом
struct Point<T> {
  x: T,
  y: T
}

// Функция с заданным обобщенным типом
fn generic_fn<A, N>(age: A, name: N) {
  unimplemented!();
}

fn main() {
  use Human::*;

  // Использование перечисления с обобщенным типом
  let name: Human<&str, i32> = Name("Антон");
  let age: Human<&str, i32> = Age(19);

  // Обработка вариаций - как обычных перечислений
  match name {
    Name(n) => println!("{}", n),
    Age(a) => println!("{}", a)
  }

  // Для структур
  let point:Point<i32> = Point { x: 0, y: 0 };
}

Обзор языка Rust

Traits: Интерфейсы, Обобщения

// Интерфейс с объявленной сигнатурой метода
trait Interface {
  fn method(&self) -> u32;
}
// Другой интерфейс, но с зависимостью 
// в реализации другого интерфейса 
trait OtherInterface: Interface {
  fn other_method(&self) -> u32;
}

struct Data {
  field: u32
}

// Имплементация методов из интерфейса 
impl Interface for Data {
  fn method(&self) -> u32 {
    self.field * 2
  }
}

// Имплементация другого интерфейса
impl OtherInterface for Data {
  fn other_method(&self) -> u32 {
    self.field * 10
  }
}

// Функция с заданным аргументом как реализации интерфейса
fn show_result<I: Interface>(result: &I) {
  println!("{}", result.method());
}

// Функция требующая а аргументе имплементацию с 
// реализацией нескольких интерфейсов
fn show_result_more_interfaces<I: Interface + OtherInterface>(result: &I) {
  println!("{}", result.other_method());
}

fn main() {
  let inst = Data { field: 2 };
  // Вызов функции с ссылкой на экземпляр с реализацией
  // интерфейса в качестве аргумента
  show_result(&inst);
  show_result_more_interfaces(&inst);
}

Обзор языка Rust

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

// Импортируем из стандартной библиотеки интерфейс
// Из доступных операций: +, -, /, |, |=, и т.д
use std::ops::Add;

struct Overloading {
  field: i32
}

// Имплементация интерфейса сложения для структуры
impl Add for Overloading {
  type Output = Overloading;

  // Реализация метода отвечающего за сложение 
  // и возвращающего конечную структуру
  fn add(self, other: Overloading) -> Overloading {
    Overloading { field: self.field + other.field }
  }
}

fn main() {
  // Объявляем структуры
  let dataOne = Overloading { field: 5 };
  let dataTwo = Overloading { field: 8 };

  // Проводим операцию сложения
  let result = dataOne + dataTwo;

  // Результат = 13
  println!("{}", result.field);
}

Дополнительные Возможности Rust

Макросы, FFI, Паралеллизм

FFI

Дополнительные возможности Rust

  • Позволяет создавать связывание кода из языка С, а также вызывать функции написанные на  Rust из C.
  • Кроме работы непосредственно с языком С - существует возможность вызывать функции написанные на Rust в других языках таких как: Python, Ruby, NodeJS, Haskell и т.д.

При желании можете посмотреть наиболее полный список примеров использования Rust в других языках тут:

FFI

Дополнительные возможности Rust

#[no_mangle] // Rust код
// объявляем публичную функцию, для внешнего использования.
pub extern fn double_input(input: i32) -> i32 {
    input * 2
}
from ctypes import cdll
from sys import platform

if platform == 'darwin':
    prefix = 'lib'
    ext = 'dylib'
elif platform == 'win32':
    prefix = ''
    ext = 'dll'
else:
    prefix = 'lib'
    ext = 'so'

lib = cdll.LoadLibrary('target/debug/{}double_input.{}'.format(prefix, ext))
double_input = lib.double_input

input = 4
output = double_input(input)
print('{} * 2 = {}'.format(input, output))

Python

var ffi = require('ffi');

var lib = ffi.Library('target/debug/libdouble_input', {
  'double_input': [ 'int', [ 'int' ] ]
});

var input = 4;
var output = lib.double_input(input);
console.log(input + " * 2 = " + output);

NodeJS

require 'ffi'

if RUBY_PLATFORM.include?('darwin')
  EXT = 'dylib'
else
  EXT = 'so'
end

module Hello
  extend FFI::Library
  ffi_lib 'target/debug/libdouble_input.' + EXT
  attach_function :double_input, [ :int ], :int
end

input = 4
output = Hello.double_input(input)
puts "#{input} * 2 = #{output}"

Ruby

Макросы

Дополнительные возможности Rust

// Декларируем макрос vectors!
macro_rules! vectors {
  ( $( $x:expr ),* ) => {
    {
      let mut temp_vec = Vec::new();
      $(
        temp_vec.push($x);
      )*
      temp_vec
    }
  };
}

fn main() {
  // Использование
  let mut data = vectors![1,2,3];
}
  • Макросы позволяют расширять синтаксические возможности языка
  • В Rust  - макросы не используют обычную текстовую замену по шаблону, а имеют собственный синтаксис определения и сопоставления, который также соответствует требованиям безопасности

Пример простого макроса для реализации `vec!`

WEB - Разработка и Rust

Обзор инструментов для написания Web сервисов на Rust

  • В этом части мы рассмотрим какие есть инструменты для работы с Web в Rust.
  • Будут рассмотрены библиотеки и фреймворки для работы с Http, Шаблонизаторы, ODM

WEB - Разработка и Rust

Обзор инструментов для написания Web сервисов на Rust

  • В этом части мы рассмотрим какие есть инструменты для работы с Web в Rust.
  • Будут рассмотрены библиотеки и фреймворки для работы с Http, Шаблонизаторы, ODM

WEB - Разработка и Rust

Pencil

Маленький и шустрый фреймворк для работы с Web на Rust. По утверждению авторов при написании этого инструмента они вдохновлялись микрофреймворком Flask из мира Python. Благодаря простой семантике и архитектуре, а также удобным "сахарным" возможностям, Pencil отлично подходит для начинающий разработчиков Rust - так как они могут без знания низко-уровневых тонкостей работы с http в rust, начать писать свои web-приложения и сервисы на коленке.

WEB - Разработка и Rust

Pencil

  • Routing - возможность управлять обработчиками в зависимости от адреса запроса.
  • JSON - встроенные утилиты для создания JSON ответов из обычных Map
  • Методы для опредления обработки ошибок ответов, например отдавая ответ 404 вы отдельно можете обработать ответ.
  • Встроенная утилита метод для раздачи статики.
  • Встроенный простой шаблонизатор.
  • Lifecycle Middleware для обработчиков которые срабатывают до и после запроса.
  • Модульная система регистрации новых кусков с другой обработкой и параметрами.

WEB - Разработка и Rust

Pencil

extern crate env_logger;
extern crate pencil;
extern crate log;

use std::collections::BTreeMap;

use pencil::{
  PencilResult,
  Response, 
  Request, 
  Pencil
};

fn home_handler(req: &mut Request) -> PencilResult {
  let mut res_message = Response::from("Hello World");
  Ok(res_message)
}

fn route_args_handler(req: &mut Request) -> PencilResult {
  let firstname = req.view_args.get("firstname").unwrap();
  let welcome = format!("welcome {}", firstname).into();
  Ok(welcome)
}

fn template_handler(req: &mut Request) -> PencilResult {
  let mut map_data = BTreeMap::new();
  map_data.insert("value_one".to_string(), "value one".to_string());
  map_data.insert("value_two".to_string(), "value two".to_string());

  req.app.render_template("template.html", &map_data)
}

fn main() {
  let mut app = Pencil::new("/");

  app.set_debug(true);
  app.set_log_level();
  env_logger::init();

  app.get("/", "home", home_handler);
  app.get("/welcome/<firstname:string>", "welcome user as route param", route_args_handler);
  app.get("/template", "response from template", template_handler);

  app.run("127.0.0.1:9000");
}

WEB - Разработка и Rust

Nickel

  • Очень гибкая реализация Routing  с множеством различных видов конфигурации, включая шаблоны, поддержку регулярных выражений, и т.д
  • Также как и в Pencil поддержка JSON - как обработка json/body, так и генерация JSON из родных коллекций.
  • Встроенный парсинг тел запросов, form-data, json,
  • Методы для опредления обработки ошибок ответов.
  • Встроенная утилита метод для раздачи статики.
  • Встроенный шаблонизатор.
  • Lifecycle Middleware
  • Поддержка создания HTTPS сервера, а также поддержка CORS

WEB - Разработка и Rust

Nickel

#[macro_use] extern crate nickel;
extern crate regex;

use regex::Regex;
use nickel::{
  MiddlewareResult,
  HttpRouter, 
  Response,
  Request,
  Nickel, 
  Router,
};

fn main() {
    let route_regexp = Regex::new("/hello/(?P<name>[a-zA-Z]+)").unwrap();
    let mut server = Nickel::new();
    server.utilize(module_for_nickel);

    server
    .get("/path", handler_as_fun)
    .get("/hello", middleware!("Hello DevConf!"))
    .get(route_regexp, middleware! { |request| 
      request.param("name").unwrap();
    })   
    .get("/:arg", middleware! {|request|
      let myArg = request.param("arg").unwrap();
      format!("Вы ввели аргумент: {}", myArg)
    });


    server.listen("127.0.0.1:1488");
}

fn handler_as_fun<'mw>(req: &mut Request, res: Response<'mw>) 
-> MiddlewareResult<'mw> {
  res.send("Вот он ответ на запрос")
}

fn module_for_nickel() -> Router {
  let mut router = Nickel::router();

  router.post("/handle-form", middleware! { |req, res|
    let form_data = try_with!(res, req.form_body());

    ...Ваши обработки
  });

  router
}

WEB - Разработка и Rust

Hyper, Iron

  • Low-level Middleware при Web разработке в Rust. Сложно, проблемно, быстро.
  • Дает основной функционал при работе с Raw HTTP - Header, Chunk Body, etc.
  • Имеет базовую систему работы с HTTP без сахарных плагинов, или если имеет такие плагины - остальная часть архитектуры, остается довольно проблемной для понимания.
  • Подходит как качественно составленная основа, для составления своей оболочки под вашу архитектуру, без особых потерь производительности.

В завершение

Сравнение фреймворков + Посты про написание Web на Rust

Ламповая группа pro.Rust в Telegram

Мои контакты

Telegram: @tapok_satan

Язык программирования Rust и его использование в Web

By Anton Shramko

Язык программирования Rust и его использование в Web

  • 5,184