Как войти в Rust как разработчик JavaScript/TypeScript

Перевод | Автор оригинала: Matthew Pagan

Rust - это язык моей мечты. Я работаю в основном как разработчик JavaScript, поэтому мой набор навыков в основном включает в себя Интернет, а также среды NodeJS. Я люблю JavaScript, но мне хотелось погрузиться в новый язык. На этом новом языке нужно было отметить несколько флажков:

Изначально я думал, что моим новым языком будет C++. У него желаемая скорость, и это язык со строгой типизацией, который также имеет статическую типизацию. Однако большая проблема с C++ заключалась в том, что не было никаких трэйтов официальной документации, и сообщество кажется гораздо менее дружелюбным к новичкам, чем я надеялся.

Затем появился Rust. Rust проверяет все флажки. Если говорить только об их документации, то это ЗАМЕЧАТЕЛЬНО! Это универсальный магазин для всех моих языковых потребностей. Для новичка это ТАКАЯ экономия времени. Спасибо mdbook за один из самых чистых форматов документации, которые я когда-либо видел.

Скорость Rust

Я вкратце упомянул о своем желании получить отличную документацию, но теперь давайте посмотрим на скорость!

Это сообщение в блоге Шона Рэгга подводит итог скорости Rust в сравнении с веб-фреймворками. В своей статье он сравнил Rocket (веб-фреймворк, написанный на Rust) с Restify (еще один веб-фреймворк, написанный на NodeJS). Он измерил, сколько запросов в секунду может обработать каждая служба, и результаты оказались ошеломляющими.

Rocket vs Restify ответов в секунду

Количество запросов в секунду, которые может обработать каждая служба:

Restify: 7,996.19 Rocket: 72,133.75

Основываясь на этих результатах тестирования, служба на базе Rocket смогла обработать в 9 раз больше запросов, чем Restify. Это дико!

Давайте погрузимся в Rust и посмотрим, как мы можем переключить наши мысли с JavaScript на Rust.

Предварительные требования

Нам нужно будет установить Rust на наш компьютер для локальной разработки Rust. Этот процесс невероятно прост. Перейдите сюда, и их документация даст вам способ установить Rust, адаптированный к любой ОС, которую вы используете, когда вы нажимаете эту ссылку.

Выполнение процесса установки установит всю цепочку инструментов Rust на ваш компьютер:

Самым важным компонентом этой цепочки инструментов является Cargo.

Ваш новый НПМ, Cargo

Из книги Cargo

Cargo - это пакетный менеджер Rust. Cargo загружает зависимости вашего пакета Rust, компилирует ваши пакеты, создает распространяемые пакеты и загружает их в crates.io, реестр пакетов сообщества Rust. Вы можете внести свой вклад в эту книгу на GitHub.

В основном мы собираемся использовать 3 команды cargo:

Как разработчик JavaScript, я чувствую себя как дома. Такое ощущение, что я работаю со сценариями npm в файле package.json.

Привет мир

Наконец-то создадим проект на Rust. Выполните следующую команду, чтобы инициализировать новый пакет.

cargo new hello_world

Новое в VSCode Cargo

Это создаст нашу структуру папок для пакета rust и создаст два важных файла:

Считайте Cargo.toml версией package.json для Rust. Однако главный файл, который нас интересует, - это файл main.rs. Этот файл содержит основную функцию - отправную точку во всех программах на Rust.

fn main() {
    println!("Hello, world!");
}

За несколько секунд мы уже создали нашу программу Hello World. Это отличная основа, с которой мы можем глубже погрузиться в основы Rust.

Типы

Один из инструментов экосистемы JavaScript, который мне понравился, - TypeScript. Недавно было время, когда я работал над проектом, чувствительным ко времени, и просто не было времени тестировать приложение в той мере, в какой я хотел.

Я решил использовать TypeScript вместо JavaScript для написания интерфейса, и он вылавливал ТАКОЕ много ошибок, когда я печатал свой код. Я был уверен, что понимаю структуру всех моих данных, и в целом я чувствовал себя более уверенным в кодовой базе, поскольку она строго типизирована.

Вот несколько простых типов из TypeScript:

const anInt: number = 17;
const aString: string = "Hello";
const aBoolean: boolean = true;
const anArray: number[] = [1, 2, 3, 4, 5];

Система набора текста в Rust похожа на написание кода TypeScript. Вот аналогичное объявление переменных, но на этот раз на Rust.

let _an_int: i32 = 32;
let _a_string: &str = "Hello, world!";
let _a_bool: bool = true;
let _an_array: [i32; 3] = [1, 2, 3];

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

Структуры и перечисления

Давайте посмотрим на более сложный тип. Допустим, я хотел объявить форму объекта, представляющего человека. Вот пример того, как я бы реализовал этот тип объекта в TypeScript.

export interface Person {
    name: string;
    age: number;
    hobby: string;
    job_title: string;
}

Вот как я мог бы реализовать подобный объект, используя структуры Rust.

#[derive(Debug)]
pub struct Person {
    pub name: String,
    pub age: i32,
    pub hobby: String,
    pub job_title: String,
}

Опять же, это дает мне опыт, очень похожий на написание TypeScript.

Давайте создадим экземпляр объекта типа Person с помощью Rust и распечатаем его на стандартный вывод.

let me: Person = Person {
    name: "Matthew Pagan".to_string(),
    age: 32,
    hobby: "Coding".to_string(),
    job_title: "Software Developer".to_string(),
};

println!("{:?}", me);

Среда разработки Rust

Это Rust, но он очень похож на то, что я уже использую изо дня в день. Обожаю TypeScript, а Rust чешет, что чешется.

Давайте реализуем поле избранного цвета в структуре Person и ограничим доступные цвета, реализовав перечисление Color для этого типа.

Добавление цветного перечисления

Параметры и соответствие

Rust действительно начинает сиять, когда мы начинаем работать с логикой, которая включает в себя необязательные значения, и как обрабатывать необязательные данные.

Давайте расширим наш тип Person. Вот как в настоящее время определяется наш тип Person:

use super::Color;

#[derive(Debug)]
pub struct Person {
    pub name: String,
    pub age: i32,
    pub hobby: String,
    pub job_title: String,
    pub favorite_color: Color,
}

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

TypeScript имеет один способ справиться с этим сценарием, используя необязательные свойства.

Вот как мы могли бы реализовать необязательный job_title в интерфейсе Person в TypeScript.

export interface Person {
    name: string;
    age: number;
    hobby: string;
    job_title?: string;
    favorite_color: Color;
}

В приведенной выше реализации TypeScript говорит, что job_title является либо строкой, либо нулем. Решение Rust блестяще элегантно. Нет понятия null. Вместо этого мы будем использовать перечисление Option для обработки реализации.

Вот как мы определяем Person с необязательным job_title.

pub struct Person {
    pub name: String,
    pub age: i32,
    pub hobby: String,
    pub job_title: Option<String>,
    pub favorite_color: Color,
}

Теперь мы можем написать логику, которая зависит от состояния job_title.

let me: Person = Person {
    name: "Matthew Pagan".to_string(),
    age: 32,
    hobby: "Coding".to_string(),
    job_title: Some("Software Developer".to_string()),
    favorite_color: Color::Cyan,
};

let noah: Person = Person {
    name: "Noah Pagan".to_string(),
    age: 5,
    hobby: "Lego".to_string(),
    job_title: None,
    favorite_color: Color::Blue,
};

match me.job_title {
    Some(title) => println!("{} is a {}", me.name, title),
    None => println!("{} doesn't have a job", me.name),
}

match noah.job_title {
    Some(title) => println!("{} is a {}", noah.name, title),
    None => println!("{} doesn't have a job", noah.name),
}

Вариант Enum

Перечисления Option могут быть 1 из 2 вариантов

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

Функции

Как и в других языках, мы можем инкапсулировать повторяющуюся логику в функциях. Мы повторяем нашу логику, когда обрабатываем, что печатать, в зависимости от значения job_title. Давайте создадим функцию, которая принимает Person и регистрирует стандартное правильное сообщение.

fn handle_job_title(person: Person) ->() {
    match person.job_title {
        Some(title) => println!("{} is a {}", person.name, title),
        None => println!("{} doesn't have a job", person.name),
    }
}

let me: Person = Person {
    name: "Matthew Pagan".to_string(),
    age: 32,
    hobby: "Coding".to_string(),
    job_title: Some("Software Developer".to_string()),
    favorite_color: Color::Cyan,
};

let noah: Person = Person {
    name: "Noah Pagan".to_string(),
    age: 5,
    hobby: "Lego".to_string(),
    job_title: None,
    favorite_color: Color::Blue,
};

handle_job_title(me);
handle_job_title(noah);

Я даже получаю хороший IntelliSense для всего, пока использую VSCode, если импортирую эту функцию из модуля.

Функция Intellisense

Массивы и владение

Мы можем еще больше очистить этот код. Мы объявляем и инициализируем 2 объекта Person. Мы могли бы сохранить это в массиве, а затем перебирать этот массив, выполняя функцию на каждой итерации.

Вот пример того, как это реализовать с помощью Rust.

let people: [Person; 2] = [
    Person {
        name: "Matthew Pagan".to_string(),
        age: 32,
        hobby: "Coding".to_string(),
        job_title: Some("Software Developer".to_string()),
        favorite_color: Color::Cyan,
    },
    Person {
        name: "Noah Pagan".to_string(),
        age: 5,
        hobby: "Lego".to_string(),
        job_title: None,
        favorite_color: Color::Blue,
    },
];

for person in people.iter() {
    handle_job_title(person);
}

Реализация массива

Чтобы это заработало, нам нужно внести некоторые незначительные изменения в реализацию нашей функции и указать функции на заимствование аргумента Person, добавив & перед типом аргумента.

use super::super::types::Person;

pub fn handle_job_title(person: &Person) ->() {
    match &person.job_title {
        Some(title) => println!("{} is a {}", person.name, title),
        None => println!("{} doesn't have a job", person.name),
    }
}

Это затрагивает основную функцию Rust, тему владения. Право собственности имеет 3 основных правила:

  1. Каждое значение в Rust имеет переменную, которая называется его владельцем.
  2. Единовременно может быть только один владелец.
  3. Когда владелец выходит за рамки, значение будет сброшено.

Давайте создадим пару примеров, чтобы продемонстрировать идеи владения.

fn add_numbers(x: i32, y: i32) -> i32 {
    x + y
}

let number = 3;

println!("{} + {} = {}", 1, x, add_numbers(1, number));
println!("{} + {} = {}", 1, x, add_numbers(5, number));

Это работает так же, как мы и ожидали, распечатывая результаты обоих вызовов функций. Поскольку целые числа являются простыми значениями с известным фиксированным размером, это работает путем помещения копии целого числа i32 в стек для значения аргумента функции.

Поскольку мы делаем копии целого числа в стеке, мы придерживаемся правил владения, и число является единственным владельцем своей копии целого числа 3.

Давайте попробуем это с помощью функции, которая принимает String.

fn print_string(x: String) {
    println!("{}", x);
}

let name: String = String::from("Matthew Pagan");

print_string(name);
print_string(name);

Это не работает. VS Code сразу выдает ошибку, когда мы пробуем это при втором вызове функции print_string.

Ошибка владения

Ошибка говорит о том, что мы использовали перемещенное значение. Если мы посмотрим на третье правило владения, оно гласит: «Когда владелец выходит за рамки, значение теряется».

В отличие от целых чисел, тип String является растущим, изменяемым, принадлежащим ему строковым типом в кодировке UTF-8. В отличие от скалярного целого числа, String помещается в кучу, гораздо более дорогое место для хранения памяти.

Из-за этих затрат компилятор Rust не просто копирует значение в аргумент функции. Вместо этого функция arg становится новым владельцем этих данных.

Повторюсь, когда мы вызывали print_string, мы покинули область, в которой мы объявили имя String, а аргумент функции x стал новым владельцем значения «Мэтью Пэган». После того, как функция зарегистрировала мое имя и мы покинули ее область действия, имя больше не было владельцем данных, что вызывало ошибку, когда мы пытались использовать его снова.

Чтобы исправить это, нам нужно указать функции, чтобы она заимствовала только значение String и возвращала право собственности на это значение переменной name, когда мы выходим из области действия функции. Мы делаем это, добавляя & перед типом аргумента функции. Ниже представлена реализация для исправления нашей ошибки перемещения.

fn print_string(x: &String) {
    println!("{}", x);
}

let name = String::from("Matthew Pagan");
print_string(&name);
print_string(&name);

Вывод

Кажется, что Rust взял лучшие части экосистемы JavaScript и превратил их в многофункциональный язык программирования, специально созданный для привлечения разработчиков к системному программированию. Я твердо признателен, что это определенно сработало со мной и привлекло нездоровое количество моего внимания.

Нам остается еще очень много сделать с нашими растущими знаниями о Rust. В моем следующем сообщении в блоге мы рассмотрим, как построить этот фундамент знаний и начать работу с внешними пакетами (крэйтами) Rust для создания веб-сервера, который будет выполнять внутреннюю роль полнофункционального приложения.

А пока установите, если вы еще этого не сделали, установите Rust и начните играть с ним сегодня же! На самом деле это самый простой способ заняться программированием нижнего уровня, особенно если вы из мира веб-разработки, как я.

Dot Labs - это современная веб-консалтинговая компания, цель которой - помочь компаниям реализовать свои усилия по цифровой трансформации. Чтобы получить руководство по архитектуре, обучение или консультации по React, Angular, Vue, веб-компонентам, GraphQL, Node, Bazel или Polymer, посетите thisdotlabs.com.

Этот Dot Media ориентирован на создание инклюзивной и образовательной сети для всех. Мы держим вас в курсе достижений современной сети с помощью мероприятий, подкастов и бесплатного контента. Чтобы узнать, посетите thisdot.co.