Эта статья — перевод оригинальной статьи Michael Salim "Rust from 0 to 80% for JavaScript Developers"

Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.

Вступление

Это список тем, которые помогут вам быстро понять Rust, если вы разработчик JavaScript. Есть много учебников, которые начинаются с нуля. Но если вы уже знаете что-то еще, почему бы не сравнить их?

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

Предупреждение!

Я очень далек от того, чтобы хорошо разбираться в Rust. Документация по Rust обширна, поэтому, если вам нужны подробности, поищите их в Google. Эта книга также является хорошей отправной точкой для изучения Rust. Моя цель — перечислить важные вещи, чтобы вы (и я в будущем) могли пропустить общие концепции программирования и сосредоточиться на различиях, основанных на уже известных вам знаниях.

Типы

Rust — типизированный язык, поэтому он ближе к TypeScript. У вас будет гораздо лучший опыт, если вы уже знаете TS.

По большей части синтаксис похож (variable_name: Type). Ура!

snake_case

Ага, не обойти.

Что это за символ?

1. Вопросительный знак (?)

Вы можете встретить (?) после вызова функции, например: my_function()?;.

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

Документация

2. Восклицательный знак на функциях (!)

Пример: println!("{:?}", my_variable);

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

3. Символ (&)

Пример: &my_var

Это способ получить ссылку. Вы встречали это, если использовали языки низкого уровня, такие как C. Подробнее позже.

Синтаксис

  1. Точка с запятой (;) в конце каждой строки обязательна.

  2. Исключение: точка с запятой (;) не обязательна в последней строке функции. В данном случае это сокращение для возврата этой строки.

  3. Синтаксис функций немного отличается. Не ахти какое дело.

    fn foo(num: i32) -> i32 {
      3 // See no.2
      // or
      // return 3;
    }
  1. Синтаксис декоратора также отличается. Его также называют атрибутами.

Что это за ключевые слова?

struct

Это объект JSON. (Хорошо, может быть, сложнее, но см. документы для этого).

type Person = {
  firstName: string;
  lastName: string;
};
struct Person {
    first_name: String,
    last_name: String,
}

trait

Интерфейс

impl

Реализация trait. Ближе всего к этому у нас есть классы. Это связь между trait и типом. я не использовал его

enum

В некотором смысле очень похоже на enum в Typescript. Но вы можете хранить в нем данные. Это довольно изящная и довольно важная концепция для понимания асинхронности.

Console.log

Не так просто, к сожалению. Больше похоже на printf из других языков

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

Library/Dependencies

Используйте Cargo.toml вместо package.json. Вам надо будет добавить их вручную (вместо использования такой команды, как yarn add)

Документация

Пример:

[dependencies]
chrono = "0.4"
egg-mode = "0.16.0"

Импорт

В Rust есть модули. Они сильно отличается от JS, но в основном:

Это что-то вроде пространств имен. Вот разбивка по импорту зависимости

use rocket::serde::{json::Json, Deserialize, Serialize};

use - используется вместоimport

rocket - это название пакета

:: - доступ к модулю

serde - это название модуля

{json::Json, Deserialize, Serialize} - штуки, которые нужно импортировать

Еще немного синтаксиса:

use chrono::prelude::*;

use rusqlite::Result;

Импортирование из локальных файлов

Лучшее объяснение: https://doc.rust-lang.org/rust-by-example/mod/split.html

Используйте mod для пути/файла, который вы хотите импортировать, чтобы компилятор включил модуль.

Затем используйте use для импорта. Примечание: mod также автоматически импортирует его. В этом случае вам понадобится префикс crate.

Пример:

use crate::your_file_or_module;

Примечание: mod.rs — это специальное имя файла, которое действует как index.js.

Const vs let

В JavaScript вы бы использовали const большую часть времени, потому что она неизменяемая.

В Rust лучше использовать let. Он не переопределяемый по умолчанию. Если вы хотите, чтобы он был изменяемым, используйте ключевое слово mut. const зарезервированы для фактических констант (поэтому вы не можете вычислить значение из другой переменной)

let immutable_variable = ...;
let mut mutable_variable = ...;
const MY_CONSTANT = "CONSTANT";

Документация библиотек

Если в репозитории Github нет ссылки на страницу документации, вы, вероятно, можете перейти к ней следующим образом:

Асинхронность

Безусловно, две самые запутанные темы — это futures и ownership. Я бы порекомендовал прочитать более полную документацию для них. Сначала поговорим о Future.

Future похоже на Promise. В отличие от JS, в Rust есть тип результата promise/future который называется Result. Он также принимает тип ошибки в дженериках (хотелось бы, чтобы JS имел это). Вы также можете использовать Result без future.

Вызов Future

Стандартной библиотеки обычно не хватает, поэтому вам нужно будет импортировать что-то еще (например, bluebird для JS). Вам нужен исполнитель, чтобы управлять Future. Я рекомендую использовать tokio и читать их документацию.

.await чтобы подождать функцию

async_function().await; Интересный синтаксис, да? На самом деле он довольно приятный, так как вам не нужно оборачивать его скобками, как в JS.

Управление Result

Это еще один важный момент. Rust безопасен, поэтому вам нужно будет обработать все ошибки. Да, обязательно все случаи ошибок в отличие от JS!

В Result enum есть Ok и Err. Если Future успешно, оно возвращает Ok, иначе Err.

Наиболее полный способ обработки обоих случаев:

let f = File::open("hello.txt");

let mut f = match f {
    Ok(file) => file,
    Err(e) => return Err(e),
};

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

Но он довольно многословный, поэтому есть 2 распространенных способа сократить его:

  1. Используя .unwrap()

Пример: let my_value = async_function().await.unwrap();

Он получает значение успеха или кидает ошибку, если Err

Используйте этот способ только тогда, когда вы уверены, что это не приведет к ошибке или только в тестовой среде.

  1. Используя ? синтаксис

Это передает ошибку вверх. Таким образом, ваша функция также должна иметь возможность возвращать ошибку (либо Result, либо Option).

Глянь этот пример и его эквивалент

Ownership и References

Слышали о borrow checker? Здесь мне особо нечего сказать. Это самая сложная вещь в этом списке, поскольку она уникальна для Rust. И если вы никогда раньше не работали со ссылками, эта тема может быть немного сложной.

К счастью, книга о Rust снова спасает положение.

Заключение

Этот список на самом деле короче, чем я ожидал. Я надеюсь, что это вам поможет.

Комментарии (13)


  1. Koyanisqatsi
    19.05.2022 09:16
    +14

    Скорее "Rust с 0 до 0% для JavaScript разработчиков".


    1. dopusteam
      19.05.2022 09:34
      +18

      Я даже немного в минус ушёл


  1. ushankax
    19.05.2022 09:32
    +2

    Кстати, книга есть и на русском: https://doc.rust-lang.ru/book/

    Статья сводится к тому, что JS и Rust не похожи, в расте много принципиально других вещей, поэтому почитайте книгу )


    1. afanasiyz
      19.05.2022 10:39
      +1

      Да, я ещё бы порекомендовал вот этот цикл задач, они привязаны к учебнику и на практике позволяют то, что только что прочитано применить - https://github.com/rust-lang/rustlings


  1. DarkEld3r
    19.05.2022 10:59
    +7

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

    Можно вот так: println!("{my_variable:?}");


    Вам надо будет добавить их вручную (вместо использования такой команды, как yarn add)

    Можно использовать cargo add (установив cargo-edit).


    1. blood_develop
      19.05.2022 14:43
      +3

      Тот самый коммент, который полезней всей статьи


  1. SergeiMinaev
    19.05.2022 13:40
    +1

    точка с запятой (;) не обязательна в последней строке функции. В данном случае это сокращение для возврата этой строки

    "Возврат строки" - как-то странно звучит. Точка с запятой меняет поведение. Она ставится после инструкций (statement) и не ставится после выражений (expression). Выражения возвращают значение, а инструкции - нет. "abc" - выражение, let a = "abc"; - инструкция. Поэтому, "abc" в расте равноценно return "abc". Точка с запятой подавляет возвращение значения, поэтому если написать "abc";, то значение выражения "abc" возвращено не будет.

    Кстати, получается, что точка с запятой после return является исключением, ведь она не мешает возвращению значения.


    1. mayorovp
      19.05.2022 14:03
      +1

      Кстати, получается, что точка с запятой после return является исключением, ведь она не мешает возвращению значения.

      Исключением тут является не точка с запятой после return, а сам оператор return (кстати, он не один такой). Проще всего это увидеть на следующем примере:


      fn foo() -> i32 {
          let bar = {
              return 5;
          };
      }

      Здесь оператор return возвращает значение из функции foo, но это ничуть не мешает переменной bar принять тип ()


      1. PROgrammer_JARvis
        19.05.2022 22:49
        +1

        Не совсем:
        Тип выражения { return 5; } -- ! (он же never-type).

        Другое дело, что never-type, формально, приводим к любому типу и, действительно,

        fn foo() -> i32 {
            let bar: () = { return 1; };
        }

        также скомпилируется.


  1. eee
    19.05.2022 13:43
    -1

    Наверно самый бесполезный (даже вредный) способ учить Rust )

    JavaScript-разработчику его знания не помогут практически ничем в освоении Rust-а.


    1. orekh
      20.05.2022 14:06
      +2

      Ну нет. Преувеличиваете. Если знаешь один язык, то следующие изучаются проще.

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


  1. amarao
    19.05.2022 13:55
    +4

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


  1. Hett
    19.05.2022 22:45

    Это все банальщина, давай про заимствования и времена их жизни.