Rust для JavaScript-разработчиков - функции и поток управления
Перевод | Автор оригинала: Shesh
Это третья часть из серии о знакомстве с языком Rust для JavaScript-разработчиков. Вот все главы:
- Обзор инструментальной экосистемы
- Переменные и типы данных
- Функции и поток управления
- Сопоставление с образцом и перечисления
Функции
Синтаксис функций в 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 для векторов, нам нужно выполнить следующие шаги:
- Преобразуйте вектор в итератор с помощью методов iter, into_iter или iter_mut.
- Цепочка адаптеров, таких как map / filter / и т.д. На итераторе.
- Наконец преобразуйте итератор обратно в вектор, используя такие потребители, как сбор, поиск, суммирование и т.д.
Вот эквивалентный код на 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]
}
Вы должны понимать большую часть приведенного выше кода, но можете заметить здесь несколько вещей:
- Использование & и * в закрытии
- Аннотация типа Vec
для переменной результата
& - это оператор ссылки, а * - оператор разыменования. Метод iter вместо того, чтобы копировать элементы в векторе, передает их как ссылки на следующий адаптер в цепочке. Вот почему мы используем & i32 в закрытии карты (двойное). Это закрытие возвращает i32, но фильтр вызывает его закрытие (less_than_10) со ссылкой, поэтому нам нужно снова использовать & i32. Чтобы разыменовать аргумент, мы используем оператор *. Мы рассмотрим это более подробно в следующих главах.
Что касается Vec
Помимо карты и фильтра, есть масса других полезных адаптеров, которые мы можем использовать в итераторах.
Спасибо за чтение! Не стесняйтесь подписываться на меня в Твиттере, чтобы увидеть больше подобных сообщений :)