Rust для JavaScript-разработчиков - переменные и типы данных

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

Это вторая часть из серии о знакомстве с языком Rust для JavaScript-разработчиков. Вот все главы:

  1. Обзор инструментальной экосистемы
  2. Переменные и типы данных
  3. Функции и поток управления
  4. Сопоставление с образцом и перечисления

Переменные

В JavaScript есть три способа объявления переменных - var, const и let. В Rust также есть const и let, но они работают иначе, чем в JavaScript.

let

В Rust мы объявляем переменные с помощью let.

let a = 123;

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

let a = 123;
a = 456; // Error! :(

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

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

let mut a = 123;
a = 456; // No Error :)

const

Const в Rust также сильно отличается от const в JavaScript - лучше думать о const в Rust как о «метке» для постоянного значения. Во время компиляции они заменяются своими фактическими значениями во всех местах, где они используются. Обычно он используется для констант, таких как номера портов, значения тайм-аута, коды ошибок и т.д.

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

Типы данных

Числа

В JavaScript тип Number используется как для целых (числа без десятичной точки), так и для чисел с плавающей запятой (числа с десятичной точкой). В Rust есть множество опций для целых чисел и чисел с плавающей запятой, но по умолчанию мы можем использовать i32 для целых чисел и f64 для чисел с плавающей запятой.

let x = 123; // i32
let y = 4.5; // f64

Булевы

Довольно просто - и JavaScript, и Rust имеют логические значения со значениями true / false.

let x = false; // bool

Строки

Обычно мы мало думаем о строках при работе с JavaScript - они «просто работают». В Rust существует множество типов строк, но давайте остановимся на наиболее широко используемых - String и &str.

Строка может увеличиваться, тогда как &str - неизменяемый и фиксированный размер.

Когда вы создаете строку с помощью строкового литерала, она создает тип &str:

let name = "Saitama"; // &str

Вам нужно использовать методы String::from или to_string для создания типа String:

let name  = String::from("Genos"); // String
let name2 = "King".to_string();    // String

Вы можете преобразовать из String в &str с помощью функции as_str

let name2 = "King".to_string(); // String
let name3 = name2.as_str();     // &str

Мы узнаем больше о строках в будущих публикациях.

Опции

В JavaScript есть два типа пустых значений - undefined и null. Undefined используется, когда переменная, свойство и т.д. Не определены, а null используется, когда что-то намеренно пусто.

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

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

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

Вот как это можно было бы написать на JavaScript / TypeScript:

function read_file(path: string): string | null {
  const contents = "hello";

  if (path !== "") {
    return contents;
  }

  return null;
}

Реализация того же самого с помощью Rust's Option:

fn read_file(path: &str) -> Option<&str> {
  let contents = "hello";

  if path != "" {
    return Some(contents);
  }

  return None;
}

Вы можете видеть, что мы возвращаем None для нулевого значения, но для ненулевого значения мы не возвращаем содержимое в том виде, в каком оно есть, а скорее «оборачиваем» его в Some и возвращаем его. Тип возвращаемого значения также не является «строковым или нулевым» в соответствии с примером TypeScript, а является типом «Option, который содержит &str».

Вот как бы вы могли вызвать эту функцию в JavaScript / TypeScript:

function main() {
  const file_contents = read_file("/path/to/file");

  if (file_contents !== null) {
    console.log(file_contents); // file_contents is refined to string type
  } else {
    console.log("Empty!");
  }
}

Вызов функции в Rust:

fn main() {
  let file = read_file("path/to/file");

  if file.is_some() {
    let contents = file.unwrap();
    println!("{}", contents);
  } else {
    println!("Empty!");
  }
}

Как видите, нам нужно вручную «развернуть» Option, чтобы получить содержимое внутри.

Массивы

Подобно строкам, существует два типа массивов - один с фиксированным размером (называемый просто «массивом»), а другой, который может увеличиваться / уменьшаться в размере (называемый «векторами»).

Массивы:

fn main() {
  let list = [1, 2, 3];
  println!("{:?}", list);
}

Векторы:

fn main() {
  let mut list = vec![1, 2, 3];
  list.push(4);
  println!("{:?}", list);
}

Объекты

Технически все непримитивные типы являются «объектами» в JavaScript, но мы обычно используем термин «объект» для двух вещей - пакета данных или хеш-карты.

Пакет данных:

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

Чтобы создать объект сотрудника в JavaScript:

function main() {
  const employee = {
    name: "Saitama",
    age: 25,
    occupation: "Hero",
  };
}

Чтобы создать такой же объект в Rust, мы можем использовать структуры:

struct Employee {
  name: String,
  age: i32,
  occupation: String,
}

fn main() {
  let employee = Employee {
    name: "Saitama".to_string(),
    age: 25,
    occupation: "Hero".to_string(),
  };
}

HashMap:

В JavaScript для создания объекта с произвольными парами ключ-значение мы можем использовать либо обычные литералы объекта, либо объект Map:

function main() {
  const colors = new Map();

  colors.set("white", "#fff");
  colors.set("black", "#000");

  console.log(colors.get("white")); // #fff
}

В Rust вы можете сделать то же самое, используя тип HashMap:

use std::collections::HashMap;

fn main() {
  let mut colors = HashMap::new();

  colors.insert("white".to_string(), "#fff");
  colors.insert("black".to_string(), "#000");

  println!("{:?}", colors.get("white").unwrap()); // #fff
}

Обратите внимание на использование развёртки выше. Метод get HashMap возвращает тип Option, который нам нужно развернуть, чтобы получить значение внутри.

Спасибо за чтение! Не стесняйтесь подписываться на меня в Twitter, чтобы увидеть больше подобных сообщений :)