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

Все уже слышали о языке программирования Rust, и его популярность в отрасли растет. Универсальность языка означает, что он используется для всего: от блокчейна до веб-серверов, встроенных систем и разработки игр. Недавно он даже нашел свое место в ядре Linux. Вы можете спросить: «Что такого особенного в Rust и почему это важно?».

Короче говоря, Rust делает несколько вещей очень хорошо. Это быстро, эффективно и, самое главное, безопасно для памяти. На самом деле, по замыслу он устраняет целые категории ошибок, возникающих из-за того, что бремя распределения памяти возлагается на разработчика. Такие языки, как C++, делают именно это, требуя от инженеров вручную выделять и освобождать память. Ошибки здесь могут привести к таким вещам, как гонки данных, двойное освобождение, нулевые указатели и доступ к списку за пределами его границ индекса. Это приводит к ошибкам и неопределенному поведению, что может привести к сбою программ или дать злоумышленникам возможность проникнуть в вашу систему.

Такие языки, как Javascript, Python, Go и другие, используют сборщик мусора для управления памятью. Это снимает нагрузку с разработчика и автоматизирует внутреннее управление памятью, освобождая неиспользуемую память через определенные промежутки времени. Несмотря на то, что эта свобода хороша, за нее приходится платить производительностью. Запуск сборщика мусора потребляет ресурсы, часто в независимом потоке. Языковой дизайн должен определять, как и когда запускается сборщик мусора, который может варьироваться от языка к языку, но он никогда не бывает бесплатным. В критически важных для производительности системах или системах с ограниченным объемом памяти это нежелательно и влияет на производительность. Чтобы увидеть реальный пример именно этого, прочитайте статью эта о том, почему Discord перешел с Go на Rust из соображений производительности.

Переменные

В Rust есть два способа определить переменную: изменяемый и неизменяемый. Уточнение здесь, чтобы будущие примеры имели смысл:

fn main() {
  let a = 3;      // Immutable, cannot be mutated
  let mut a = 3;  // Mutable, can be mutated
}

Владение

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

  • У каждого значения в Rust есть владелец.
  • Одновременно может быть только один владелец
  • Когда владелец выходит за пределы области действия, значение будет удалено.

Каждый раз, когда переменная выходит за пределы области видимости, Rust под капотом вызывает функцию Drop(), которая освобождает память, назначенную этой переменной. Например, давайте посмотрим на эту функцию:

fn main() {
  let foo = "string literal"; // immutable variable foo is defined
  //....

} // foo goes out of scope and is dropped from memory

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

fn main() {
  let a = 4; // mutable variable a is defined
  
  // Scoped block initiates
  {
    let b = 7;
    let c = a + b;
  } // Scope terminates, both b & c are dropped from memory

  println!("num a is {}", a); // Valid, a is in scope
  println!("num b is {}", b); // Compile error - b does not exist in this scope
}

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

Вот тут-то и появляется другая половина этого уравнения.

Заимствование

Заимствование позволяет использовать переменные, которыми вы не владеете, через ссылку. Давайте попробуем вызвать вторую функцию со значением:

fn main() {
  let string_value = String::from("string value");
  
  // Call function, passing value
  printer(string_value); // Ownership of string_value is moved to the printer function

  println!("string is {}", string_value); // Compile error - string_value not in scope
}

fn printer(s: String) {
    println!("{}", s);
} // s is dropped from memory when scope ends

Это не компилируется, потому что наше string_value перемещается и право собственности передается функции принтера. После запуска принтера string_value удаляется из памяти. Вместо этого попробуем позаимствовать ссылку на него:

fn main() {
  let string_value = String::from("string value");
  
  // Call function passing a refernce
  printer(&string_value); // Ownership of string_value is not moved

  println!("string is {}", string_value); // Valid - string_value in scope
} // string_value is dropped from memory when main() terminates

// The type of s is now &String, meaning it borrows a reference to a String
fn printer(s: &String) {
    println!("{}", s);
} // s is not dropped from memory when scope ends because printer doesn't own it

Разницу заметить довольно легко. Функция принтера теперь заимствует ссылку на строку, а не принимает значение (которое передаст право собственности). Те же правила применяются к изменяемой ссылке:

fn main() {
  let mut string_value = String::from("string value");
  
  // Call function passing a mutable refernce
  printer(&mut string_value); // Ownership of string_value is not moved

  println!("string is {}", string_value); // Valid - string_value in scope
}

// The argument type of printer is now &mut String, meaning it borrows a mutable reference to a String
fn printer(s: &mut String) {
    // De-reference to modify value - Allowed because reference is mutable
    *s = String::from("new string value);
    println!("{}", s);
}

Есть несколько жестких требований при использовании ссылок в Rust:

  • У вас может быть любое количество неизменяемых ссылок на значение.

OR

  • У вас может быть одна изменяемая ссылка на значение.

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

fn main() {
    let mut a = String::from("string here");

    let b = &mut a; // borrows mutable reference
    let c = &mut a; // compile error - borrows second mutable reference

    println!("Two refs are {} and {}", b, c);
}

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

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

Отказ от ответственности: это не на 100 % правда. Совместное владение существует в Rust при определенных обстоятельствах, но это тема будущей статьи.

Сводка

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

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

Теперь вы можете понять, почему такие вещи, как нулевые указатели (ссылки, переживающие своего «владельца»), гонки данных (чтение значения, которое изменяется в другом месте или наоборот) и двойные освобождения (эквивалент двойного вызова метода Drop()). подряд в той же области) исправлены дизайном Rust. Все аккуратно доставлено без сборщика мусора, что обеспечивает оптимальную производительность.

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