Базовый синтаксис Rust

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

Эти фундаментальные концепции будут присутствовать в каждой программе на Rust. Их раннее изучение позволит вам как можно быстрее освоить использование Rust.

Переменные

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

По умолчанию переменные в Rust неизменяемы, если только они не объявлены как изменяемые с помощью ключевого слова `mut`.

let a = 123;       // 不可变变量
let mut b = 10;  // 可变变量

Чтобы объявить переменную, нужно использовать ключевое словоlet. Например:

let a = 123;

Разработчики, изучавшие только JavaScript, очень остро реагируют на это утверждение, а разработчикам, изучавшим только C, его трудно понять.

После этого объявления следующие три строки кода запрещены:

a = "abc";
a = 4.56; 
a = 456;

Ошибка в первой строке заключается в том, что после объявления `a` как 123 переменная `a` определяется как целое число, и вы не можете присвоить ей строковое значение.

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

Ошибка в третьей строке заключается в том, что `a` не является изменяемой переменной.

Первые две ошибки легко понять, но что означает третья? Разве `a` не переменная?

Это связано с дизайном Rust для безопасности при высокой конкурентности: на уровне языка он старается свести к минимуму ситуации, когда значения переменных могут быть изменены. Поэтому значение `a` неизменяемо. Но это не означает, что `a` не является "переменной" (английское слово `variable`); в официальной документации переменные вроде `a` называются "неизменяемыми переменными".

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

Конечно, чтобы сделать переменную "изменяемой", достаточно ключевого словаmut.

let mut a = 123;
a = 456;

Эта программа корректна.

Разница между константами и неизменяемыми переменными

Если неизменяемые переменные неизменяемы, разве они не являются просто константами? Почему их называют переменными?

Всё же есть разница между переменными и константами. В Rust следующая программа допустима:

let a = 123;   // 可以编译,但可能有警告,因为该变量没有被使用
let a = 456;

Но если `a` — константа, это недопустимо:

const a: i32 = 123;
let a = 456;

Значение переменной можно "перепривязать", но его нельзя изменить само по себе до "перепривязки". Это гарантирует, что в каждой области после "привязки" компилятор может полностью рассуждать о логике программы. Хотя в Rust есть автоматический вывод типов, в некоторых случаях объявление типа удобнее:

let a: u64 = 123;

Здесь `a` объявлена как 64-битная целочисленная переменная без знака. Если бы тип не был объявлен, `a` автоматически выводилась бы как 32-битная целочисленная переменная со знаком, что существенно влияет на диапазон значений, которые может хранить `a`.

Типы данных

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

Основные типы:i32 (32-битное целое со знаком), u32 (32-битное целое без знака), f64 (64-битное число с плавающей точкой), bool (логический), char (символ)

Пример

let x: i32 = 42;
let y: f64 = 3.14;
let is_true: bool = true;
let letter: char = 'A';

Функции

Функции Rust определяются с помощью ключевого словаfn, а тип возвращаемого значения указывается с помощью символа стрелки->.

Пример

fn add(a: i32, b: i32) -> i32 {
    a + b
}

Если функция не возвращает значение, тип по умолчанию —()(то есть пустой кортеж).

Управление потоком

Выражение `if`

Пример

let number = 7;
if number < 5 {
    println!("меньше 5");
} else {
    println!("больше или равно 5");
}

Цикл `loop`:`loop` — это бесконечный цикл в Rust; для выхода из цикла можно использовать `break`.

Пример

let mut counter = 0;
loop {
    counter += 1;
    if counter == 10 {
        break;
    }
}

Цикл `while`

Пример

let mut number = 3;
while number != 0 {
    println!("{}!", number);
    число -= 1;
}

Цикл `for`

Пример

for number in 1..4 {
    println!("{}!", number);
}

Владение

Владение в Rust — это уникальный механизм управления памятью. Его ключевые концепции включают владение, заимствование и ссылки.

Правила владения:

  • У каждого значения в Rust есть единственный владелец.
  • Каждое значение может иметь только одного владельца в любой момент времени.
  • Когда владелец выходит из области видимости, значение будет удалено.
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权被转移给了 s2
// println!("{}", s1); // 此处编译会报错,因为 s1 已不再拥有该值

Заимствование и ссылки:Заимствование позволяет ссылаться на данные, не забирая владение, реализуется с помощью символа&.

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s);  // 借用
    println!("The length of '{}' is {}.", s, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

Структуры

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

Пример

struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

let user1 = User {
    username: String::from("someusername"),
    email: String::from("[email protected]"),
    sign_in_count: 1,
    active: true,
};

Перечисления

Перечисления позволяют определить один из нескольких возможных типов данных.

Пример

enum IpAddrKind {
    V4,
    V6,
}

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;

Сопоставление с образцом (match)

`match` — мощный инструмент управления потоком в Rust, похожий на оператор `switch`.

Пример

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> u8 {
    match coin {
        Coin::Penny => 1,
        Coin::Nickel => 5,
        Coin::Dime => 10,
        Coin::Quarter => 25,
    }
}

Обработка ошибок

В Rust есть два основных способа обработки ошибок:Result<T, E>иOption<T>。

Результат:

Пример

enum Result<T, E> {
    ОК(T),
    Err(E),
}

fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err(String::from("Деление на ноль"))
    } else {
        ОК(a / b)
    }
}

Option:

Пример

fn get_element(index: usize, vec: &Vec<i32>) -> Опция<i32> {
    if index < vec.len() {
        Some(vec[index])
    } else {
        Отсутствует
    }
}

Времена жизни владения и заимствования

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

Пример

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

Затенение

Концепция затенения отличается от «переопределения» или «перегрузки» в других объектно-ориентированных языках. Затенение — это то, что ранее называлось «перепривязкой»; кавычки использовались для обозначения этого понятия до того, как оно было введено.

Затенение — это механизм, с помощью которого имя переменной может быть использовано повторно:

Пример

fn main() {
    let x = 5;
    let x = x + 1;
    let x = x * 2;
    println!("Значение x: {}", x);
}

Вывод этой программы:

The value of x is: 12

Затенение — это не то же самое, что присваивание изменяемой переменной. Затенение означает использование одного и того же имени для представления другой сущности переменной, при этом её тип, изменяемость и значение могут измениться. Однако присваивание изменяемой переменной может изменить только её значение.

let mut s = "123";
s = s.len();

Эта программа вызовет ошибку: нельзя присвоить целочисленное значение строковой переменной.

Другие расширения