Начало работы с Rust в качестве веб-разработчика


Обратите внимание, что я все еще активно изучаю язык. Я сделаю все возможное, чтобы описать свои текущие знания, но если вы найдете что-то неверным, пожалуйста, свяжитесь со мной, и я постараюсь это исправить.

Почему именно Rust?

Было несколько других кандидатов, таких как Go и C. Я остановился на Rust, потому что это

  • относительно молодой системный язык с множеством “перспективных” вариантов использования (таких как WASM и криптовалюты)
  • принципиально отличается от Javascript (но при необходимости вполне совместим).

Вот несколько ключевых отличий:

Характеристика Rust Javascript
Реализация Компилируемый язык Интерпретированный язык
Типизация Строго статическая Динамическая
Память Проверка заимствований Сборщик мусора

Во многих источниках в Интернете Rust называют крутой кривой обучения. Изучить язык системного программирования будет очень непросто.

Следующая статья посвящена четырем вышеперечисленным характеристикам. В ней будут приведены описания и некоторые примеры кода.

Для лучшего восприятия статьи вы должны владеть Javascript и терминалом Linux / Windows.

Установите Rust

Перед началом работы вам понадобится rustup для установки компилятора Rust и Cargo, менеджера пакетов Rust. Для Linux и macOS установите его с помощью следующего скрипта:

curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
Войдите в полноэкранный режим Выйти из полноэкранного режима

Для Windows загрузите и запустите rustup-init.exe. Вы можете найти его здесь:

https://forge.rust-lang.org/infra/other-installation-methods.html#other-ways-to-install-rustup

Теперь проверьте, все ли было установлено правильно:

rustup -V  # The Rustup toolchain
rustc -V   # The Rust compiler
cargo -V   # Rust's package manager
Войдите в полноэкранный режим Выйдите из полноэкранного режима

Сравнение Rust и Javascript – мое первое впечатление

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

Мне показалось, что знание Typescript немного облегчило мой опыт. Если вы не готовы к жесткому сокращению, вам стоит начать изучать его сначала. Вы можете начать с переноса Javascript-приложения на Typescript.

Простая +1 – функция – в Javascript и Rust  

Давайте посмотрим на некоторый код. Обе следующие функции принимают ввод пользователя и добавляют 1, а затем выводят его на консоль.

В Javascript (Node.js) введите следующее:

const readline = require('readline').createInterface({
  input: process.stdin,
  output: process.stdout,
});

function plusOne() {
  readline.question('Enter a number: ', (answer) => {
    console.log(`${answer} plus one is ` + (parseInt(answer) + 1));
    readline.close();
  });
}

plusOne();
Войти в полноэкранный режим Выйти из полноэкранного режима

Реализация в Rust требует немного больше работы:

use std::io;

pub fn plus_one() {
  let mut user_input = String::new();

  println!("Enter a number: ");
  io::stdin().read_line(&mut user_input).expect("Could not read user input");


  let user_input_san: &str = user_input.trim();
  let user_input_num: usize = user_input_san.parse().unwrap();
  let result: usize = user_input_num + 1;

  println!("{} plus 1 is {}", user_input_san, result)
}

fn main() {
  plus_one();
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Простой +1 – функция – различия в коде

Моими первыми мыслями были: Этот код выглядит очень причудливо. Давайте посмотрим на общие черты и различия:

  • Вы вводите модули в игру с помощью ключевого слова use.
  • Переменные объявляются с помощью ключевого слова let и по умолчанию являются неизменяемыми. Ключевое слово mut позволяет изменить их значение.
  • Функция read_line вместо значения возвращает тип Result. Это может быть либо ожидаемое значение, либо объект Err.
  • Вместо прямого доступа к переменной, вы можете получить доступ к ее ссылке, добавив к ней префикс & . Этот механизм необходим для системы “проверки заимствований” Rust.
  • Типизация обязательна, и есть обязательная функция main.

И тут мне в голову пришло несколько вопросов (подсказки прилагаются):

  • Почему после println!() стоит восклицательный знак? Подсказка
  • Что означает String::new()? Является ли это конструктором? Подсказка
  • В чем разница между Number и usize? Подсказка

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

Давайте закончим с первым впечатлением и перейдем к филейным частям.

Отличие 1: компилятор Rust

Javascript выполняется в браузере или в среде исполнения Node.js. Код, который вы пишете, компилируется (= транслируется) во время выполнения. Полученный машинный код дает инструкции вашему компьютеру.

Такой тип компиляции классифицирует Javascript как интерпретируемый язык.

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

Вы можете скомпилировать один файл Rust с помощью команды rustc <filename>. Для больших проектов рекомендуется использовать [cargo](https://doc.rust-lang.org/book/ch01-03-hello-cargo.html?highlight=cargo#hello-cargo).

Различие 2: Статические типы

Необходимость вводить переменные показалась мне непривычной, когда я делал это в первый раз. Давайте снова на секунду посмотрим на функции plusOne, особенно на console.log, чтобы увидеть возможные последствия.

function plusOne() {
  readline.question('Enter a number: ', (answer) => {
    console.log(`${answer} plus one is ` + (parseInt(answer) + 1));
    readline.close();
  });
}
Вход в полноэкранный режим Выход из полноэкранного режима

Можете ли вы догадаться, что произойдет, когда мы уберем внешние скобки из (parseInt(answer) + 1)?

Настоящая классика.

Ошибки, подобные этой, реже встречаются в программах на Rust. Давайте рассмотрим plus_one, удалим все типы и метод .expect() из io:

use std::io;

pub fn plus_one() {
    let mut user_input = String::new();

    println!("Enter a number: ");
    io::stdin().read_line(&mut user_input);

    let user_input_san = user_input.trim();
    let user_input_num = user_input_san.parse().unwrap();
    let result = user_input_num + 1;

    println!("{} plus 1 is {}", user_input_san, result)
}

fn main() {
    plus_one();
}

Вход в полноэкранный режим Выйти из полноэкранного режима

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

Rust не может вывести тип user_input_num. Мы должны обеспечить безопасность типов, прежде чем компилятор сможет выполнить свою работу.

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

Добавьте аннотацию типа usize к user_input_num и скомпилируйте снова. Вы должны увидеть следующее предупреждение:

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

Если компиляция и типизация выглядят слишком чужеродно, попробуйте попробовать Typescript, прежде чем двигаться дальше.

Давайте подведем итоги.

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

Различие 3: Память

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

В Javascript используется сборка мусора

Сборка мусора заботится об освобождении памяти после выполнения определенного набора правил. Она регулярно проверяет, используется ли еще переменная или нет.

Следующий код дает простой пример того, как Javascript управляет памятью:

// Two objects are created and stored in memory.
// - One that's called `user`
// - One that's called `address`.
// Address is embedded into user.
let user = {
  firstName: "John",
  secondName: "Doe",
  address: {
    street: "123 Main Street",
    city: "Anytown",
  },
}

// We're creating a second reference here.
// `newUser` points to the same data in memory as `user`
let newUser = user;

// Let's also create a reference to the embeddedd address object.
let myAdress = user.address;

// By setting user to `null`, we remove the initial memory reference.
// It cannot be garbage collected yet because it's still referenced by newUser.
user = null;

// Removing the final reference of `newUser` will mark `user` as garbage.
// There's still a reference to `myAddress`, so it cannot be garbage collected yet.
newUser = null;

// By removing the final reference, `user` can be garbage collected.
myAdress = null;
Вход в полноэкранный режим Выход из полноэкранного режима

Rust использует средство проверки заимствований

Сборка мусора связана с некоторыми компромиссами:

  • Производительность программ, использующих GC, трудно предсказать.
  • GC работает рядом с реальной программой, увеличивая рабочую нагрузку, что приводит к снижению производительности
  • Программное обеспечение, использующее GC, плохо масштабируется

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

Rust использует уникальный подход для решения этой проблемы – введение права собственности и средства проверки заимствований. Они сводятся к трем простым правилам:

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

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

use std::io;

pub fn prompt() -> String {
    let mut user_input = String::new();
    println!("Enter a number: ");
    io::stdin().read_line(&mut user_input).expect("Could not read user input");
    let prompt_value = user_input.trim().to_string();
    return prompt_value;
}

pub fn plus_one(user_input: String) {
    let user_input_num: usize = user_input.parse().unwrap();
    let result = user_input_num + 1;
    println!("{} plus 1 is {}", user_input, result)
}

fn main() {
    let input = prompt();
    plus_one(input);
    println!("The initial value was {}", input);
}
Вход в полноэкранный режим Выход из полноэкранного режима

Если вы попытаетесь скомпилировать приведенный выше код, то столкнетесь со следующей ошибкой:

Давайте посмотрим, почему это происходит:

  • Мы создаем переменную input. Она становится владельцем значения, возвращаемого prompt.
  • Используя ее в качестве аргумента для plus_one, право собственности передается в область видимости функции
  • plus_one выполняется, и input выходит из области видимости. После этого он недоступен для печати.

Мы можем решить эту проблему двумя способами:

  1. Вывести значение до выполнения plus_one.
  2. Если мы не хотим менять порядок, мы можем попросить plus_one заимствовать input.

Заимствование означает: Мы передаем ссылку на переменную вместо переменной в целом. Таким образом, право собственности остается постоянным, и компилятор доволен.

Вы можете передавать ссылки на переменную, добавляя к ней префикс &.

use std::io;

pub fn prompt() -> String {
    let mut user_input = String::new();
    println!("Enter a number: ");
    io::stdin().read_line(&mut user_input).expect("Could not read user input");
    let prompt_value = user_input.trim().to_string();
    return prompt_value;
}

pub fn plus_one(user_input: &str) {
    let user_input_num: usize = user_input.parse().unwrap();
    let result = user_input_num + 1;
    println!("{} plus 1 is {}", user_input, result)
}

fn main() {
    let input = prompt();
    plus_one(&input);
    println!("The initial value was {}", input);
}

Вход в полноэкранный режим Выход из полноэкранного режима

Двигаемся дальше

Давайте подведем итоги.

Вы прочитали достаточно много о Rust, Javascript, общих чертах и различиях. Особенно о том, как Rust реализует более строгие правила для сохранения эффективности вашего кода.

Поначалу эти концепции выглядят экзотично. В конце концов, это компромисс, на который вы идете. Следуйте нескольким простым рекомендациям. Взамен вы получаете программу, которая работает молниеносно и экономит память.

Как веб-разработчик, вы можете рассчитывать на большее. Web Assembly позволяет интегрировать код Rust в проекты Javascript, используя все преимущества безопасности и производительности Rust. Проект поддерживает амбициозную дорожную карту, с которой вы можете ознакомиться здесь.

Дополнительная литература

  • Книга: Официальная книга по языку программирования Rust
  • Книга: Writing Rust in easy english
  • Видео: Traversy Media Rust Crash Course

Этот пост был первоначально опубликован на https://blog.q-bit.me/get-started-with-rust-as-a-web-developer/
Спасибо за прочтение. Если вам понравилась эта статья, давайте оставаться на связи в Twitter 🐤 @qbitme

Оцените статью
Procodings.ru
Добавить комментарий