Rust для разработчиков на Node.js

Перевод | Автор оригинала: Josh Hannaford

Ключевые различия между JavaScript и Rust, объяснение

Вы разработчик на Node.js или JavaScript, который интересуется языком программирования Rust, но не знаете, с чего начать? Любопытно, как вы можете перевести свои знания Node.js и JavaScript в «язык Rust». В этой статье я расскажу вам о некоторых из наиболее заметных различий между JavaScript и Rust и о том, как они соотносятся друг с другом, чтобы вы имели лучшее представление о том, чего ожидать, когда вы начнете использовать язык Rust.

Система типов

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

Rust - статический язык со строгой типизацией, а JavaScript - динамический язык со слабой типизацией, поэтому давайте посмотрим, что это означает и как это влияет на ваш повседневный код!

Статическая и динамическая типизация

Статическая типизация просто означает, что все типы переменных должны быть известны во время компиляции, а не во время выполнения. Это означает, что значения в языках со статической типизацией не зависят от ввода пользователя, возможности изменения во время выполнения или чего-либо еще, неизвестного во время компиляции.

Rust использует статическую типизацию, тогда как JavaScript использует динамическую типизацию. Давайте посмотрим, как это преобразовать в небольшой фрагмент кода:

JavaScript (динамический)

let a = 3 // a is a `number` type
a = "Hello, world!" // This is perfectly valid, and a is now a `string`

Rust (Static)

let mut a = 3; // a is now an `i32` value
a = "Hello, world!"; // Compiler Error: The type cannot change at runtime.

Следует отметить один интересный момент, касающийся приведенного выше кода Rust: в отличие от других языков со статической типизацией, таких как C++ или Java, Rust на самом деле имеет более продвинутые возможности вывода типов. Это означает, что часто вам не нужно указывать тип переменной, кроме как в ваших структурах и определениях функций. Я думаю, что это хороший и сбалансированный подход: его легко понять любому, кто смотрит на сигнатуру функции, но человеку, реализующему функцию, не нужно беспокоиться о том, чтобы напечатать все в ней.

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

let num = 10 * get_random_number(); // num is an `i32`
let num = num.to_string(); // num has been shadowed and is now a `String`

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

Теперь, когда мы рассмотрели, чем статическая типизация в Rust отличается от динамически типизированного JavaScript, давайте взглянем на возникающие различия между языками со строгой и слабой типизацией.

Сильная и слабая типизация

Хотя сравнение между статической и динамической типизацией очевидно (типы либо известны во время компиляции, либо нет), сильная и слабая типизация немного более туманна, поскольку эти типы больше похожи на шкалу, по которой языки могут упасть. Это потому, что эти термины измеряют, насколько язык допускает неявные преобразования между одним типом и другим.

Если язык более строго типизирован, то он допускает меньшее количество случаев неявных преобразований, чем язык с большим количеством экземпляров этих преобразований. Учитывая это определение, Rust занимает ОЧЕНЬ высокое место по шкале сильных сторон, поскольку существует только один экземпляр неявного преобразования, и к этому преобразованию можно получить доступ только при очень определенных обстоятельствах.

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

Итак, как это повлияет на ваш код?

Давайте посмотрим на два эквивалентных фрагмента кода на JavaScript и Rust.

JavaScript (слабый)

// Implicitly converts num to a `string`
const num = 10 + "0"

// Prints "Num is a string with a value of 100"
console.log(`Num is a ${typeof num} with a value of ${num}`)

Rust (Крепкая)

#![feature(type_name_of_val)]
use std::any::type_name_of_val;

// No implicit conversion. You have to manually make 10 a `String` first
let num = 10.to_string() + "0";

// Prints "Num is a std::string::String with a value of 100"
println!("Num is a {} with a value of {}", type_name_of_val(num), num);

Как видно из приведенного выше кода, у JavaScript не было проблем с неявным преобразованием числа в строку. В Rust вы должны явно отметить, что вы преобразовываете 10 в строку перед добавлением фрагмента строки, содержащего «0».

Хотя у каждого человека могут быть свои предпочтения, я лично предпочитаю иметь такую явность в моем коде, особенно при работе с более крупными базами кода, где вы не написали весь код самостоятельно. Эта ясность позволяет легче рассуждать о намерениях первоначального автора и предотвращать ошибки, например, задаваться вопросом, почему ваше число теперь равно 100, а не 10 после добавления 0.

Теперь, когда мы рассмотрели некоторые различия в системах типов, мы рассмотрим еще одну очень важную тему - изменчивость.

Изменчивость

Как вы знаете, в современном JavaScript есть три разных способа объявления переменной:

Хотя каждый из них работает по-разному, с целью обсуждения изменчивости я буду ссылаться только на let и const, поскольку и let, и var работают одинаково с точки зрения изменчивости.

В Rust есть три способа объявить переменную:

Что касается изменчивости, все они работают по-разному. Глядя на различные варианты объявления переменной в Rust, довольно легко понять, что let mut объявляет изменяемую переменную, но если это так, в чем разница между let и const? Кроме того, почему вы должны использовать два слова, чтобы обозначить, что переменная должна быть изменяемой?

Чтобы ответить на первый вопрос, вам нужно помнить, что Rust, в отличие от JavaScript, является компилируемым языком. Именно в этом заключается основное различие между let и const. Несмотря на то, что они оба неизменяемы, разница сводится к тому, когда переменная инициализируется (Примечание: я специально использую инициализированный здесь вместо объявленного).

Константная переменная должна быть инициализирована во время компиляции, и каждое вхождение этой переменной фактически встроено в сайт, на который она ссылается. Хорошим примером этого является значение PI. Вероятно, вы захотите сохранить это значение во всем приложении и оставить его неизменным. С другой стороны, переменная let позволяет вам инициализировать ее переменной, известной только во время выполнения, такой как ввод пользователя. Хотя это отвечает на первый вопрос, вы все еще можете задаться вопросом, почему требуется больше усилий для создания изменяемой переменной вместо неизменяемой?

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

Теперь давайте посмотрим на последнее существенное различие между Rust и JavaScript: допустимость значений NULL.

Возможность обнуления

Известно, что изобретатель null, сэр Тони Хоар, сказал, что это была его «ошибка на миллиард долларов». Он называет это ошибкой, потому что, по его оценкам, за последние 40 лет пустые ссылки привели к «бесчисленным ошибкам, уязвимостям и системным сбоям…» на сумму более миллиарда долларов.

Я уверен, что любой разработчик, использующий язык, допускающий пустые ссылки, в какой-то момент своей карьеры сталкивался с нулевым исключением. Я точно знаю! Однако в JavaScript это различие становится еще более запутанным, когда вы понимаете, что вам может быть передана не только переменная с нулевым значением, но и с неопределенной. Необходимость проверять оба этих экземпляра может быть невероятно запутанной и применяется только во время выполнения, что приводит к множеству экземпляров неудачного выполнения. К счастью, Rust не справляется с такой концепцией «ничто».

Ответ Rust на допустимость пустых значений

В Rust есть два основных способа обработки потенциально нулевого значения:

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

Резюме

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

Кроме того, следите за обновлениями в следующем посте, который будет учебным пособием, в котором будут рассмотрены некоторые из более низкоуровневых сравнений между языками путем создания API в Rust! В этом руководстве будут рассмотрены такие концепции, как кортежи, массивы, хэш-карты и манипуляции со строками!