Rust для JavaScript-разработчиков - функции и поток управления

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

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

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

Функции

Синтаксис функций в Rust очень похож на синтаксис JavaScript.

fn main() {
  let income = 100;
  let tax = calculate_tax(income);
  println!("{}", tax);
}

fn calculate_tax(income: i32) -> i32 {
  return income * 90 / 100;
}

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

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

fn main() {
  let income = 100;
  let tax = calculate_tax(income);
  println!("{}", tax);
}

fn calculate_tax(income: i32) -> i32 {
- return income * 90 / 100;
+ income * 90 / 100
}

Стрелочные функции

Стрелочные функции - популярная функция в современном JavaScript - они позволяют писать функциональный код в сжатой форме.

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

Синтаксис замыкания Rust очень похож на стрелочные функции JavaScript:

Без аргументов:

// JavaScript
let greet =() => console.log("hello");

greet(); // "hello"
// Rust
let greet = || println!("hello");

greet(); // "hello"

С аргументами:

// JavaScript
let greet = (msg) => console.log(msg);

greet("good morning!"); // "good morning!"
// Rust
let greet = |msg: &str| println!("{}", msg);

greet("good morning!"); // "good morning!"

Возвращаемые значения:

// JavaScript
let add = (a, b) => a + b;

add(1, 2); // 3
// Rust
let add = |a: i32, b: i32| -> i32 { a + b };

add(1, 2); // 3

Многострочный:

// JavaScript
let add = (a, b) => {
  let sum = a + b;
  return sum;
};

add(1, 2); // 3
// Rust
let add = |a: i32, b: i32| -> i32 {
  let sum = a + b;
  return sum;
};

add(1, 2); // 3

Вот шпаргалка:

Замыкания в большинстве случаев не нуждаются в аннотациях типов, но я добавил их сюда для ясности.

If Else

fn main() {
  let income = 100;
  let tax = calculate_tax(income);
  println!("{}", tax);
}

fn calculate_tax(income: i32) -> i32 {
  if income < 10 {
    return 0;
  } else if income >= 10 && income < 50 {
    return 20;
  } else {
    return 50;
  }
}

Loops

While loops:

fn main() {
  let mut count = 0;

  while count < 10 {
    println!("{}", count);
    count += 1;
  }
}

Обычных циклов for в Rust не существует, нам нужно использовать циклы while или for..in. Циклы for..in похожи на циклы for..of в JavaScript, и они перебирают итератор.

fn main() {
  let numbers = [1, 2, 3, 4, 5];

  for n in numbers.iter() {
    println!("{}", n);
  }
}

Обратите внимание, что мы не выполняем итерацию непосредственно по массиву, а вместо этого используем метод iter массива.

Мы также можем перебирать диапазоны:

fn main() {
  for n in 1..5 {
    println!("{}", n);
  }
}

Итераторы

В JavaScript мы можем использовать методы массива, такие как map / filter / reduce / etc, вместо циклов for для выполнения вычислений или преобразований в массиве.

Например, здесь мы берем массив чисел, удваиваем их и отфильтровываем элементы меньше 10:

function main() {
  let numbers = [1, 2, 3, 4, 5];

  let double = (n) => n * 2;
  let less_than_ten = (n) => n < 10;

  let result = numbers.map(double).filter(less_than_ten);

  console.log(result); // [2, 4, 6, 8]
}

В Rust мы не можем напрямую использовать map / filter / etc для векторов, нам нужно выполнить следующие шаги:

  1. Преобразуйте вектор в итератор с помощью методов iter, into_iter или iter_mut.
  2. Цепочка адаптеров, таких как map / filter / и т.д. На итераторе.
  3. Наконец преобразуйте итератор обратно в вектор, используя такие потребители, как сбор, поиск, суммирование и т.д.

Вот эквивалентный код на Rust:

fn main() {
  let numbers = vec![1, 2, 3, 4, 5];

  let double = |n: &i32| -> i32 { n * 2 };
  let less_than_10 = |n: &i32| -> bool { *n < 10 };

  let result: Vec<i32> = numbers.iter().map(double).filter(less_than_10).collect();

  println!("{:?}", result); // [2, 4, 6, 8]
}

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

& - это оператор ссылки, а * - оператор разыменования. Метод iter вместо того, чтобы копировать элементы в векторе, передает их как ссылки на следующий адаптер в цепочке. Вот почему мы используем & i32 в закрытии карты (двойное). Это закрытие возвращает i32, но фильтр вызывает его закрытие (less_than_10) со ссылкой, поэтому нам нужно снова использовать & i32. Чтобы разыменовать аргумент, мы используем оператор *. Мы рассмотрим это более подробно в следующих главах.

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

Помимо карты и фильтра, есть масса других полезных адаптеров, которые мы можем использовать в итераторах.

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