Что в Rust используется вместо сборщика мусора для управления памятью?
Владение (Ownership): основа управления памятью в Rust
В Rust нет сборщика мусора, потому что он использует систему владения (ownership) с набором правил, проверяемых на этапе компиляции. Это позволяет автоматически освобождать память без накладных расходов GC и без ручного управления, как в C. Ваш пример как раз демонстрирует ключевое правило: у каждого значения в Rust ровно один владелец.
Когда вы пишете let s2 = s1;, происходит не копирование данных, а перемещение (move) владения. Переменная s1 передаёт владение строкой s2 и становится недействительной. Компилятор запрещает использовать s1 после перемещения — это предотвращает ситуации с "висячими указателями" и двойным освобождением памяти, которые возможны в C++.
fn main() {
let s1 = String::from("Привет");
let s2 = s1; // s1 перемещается в s2
// println!("{}", s1); // Ошибка! s1 больше не владеет строкой
println!("{}", s2); // Работает: s2 — новый владелец
}
Как освобождается память?
Когда переменная-владелец выходит из области видимости, Rust автоматически вызывает деструктор (drop) и освобождает память. Это называется Resource Acquisition Is Initialization (RAII), как в C++. В Java и Python это делает GC, но здесь всё детерминировано и происходит сразу в конце блока.
fn main() {
{
let s = String::from("временная строка");
// Здесь s используется
} // s выходит из области видимости — память освобождена
// println!("{}", s); // Ошибка: s больше не существует
}
Что, если нужно копирование?
Для типов, которые хранятся на стеке (например, числа, булевы значения), работает копирование через трейт Copy. Для типов в куче (как String) нужно явно вызывать .clone():
fn main() {
let s1 = String::from("Привет");
let s2 = s1.clone(); // Глубокое копирование
println!("{} {}", s1, s2); // Теперь обе переменные валидны
}
Заимствование (Borrowing) и ссылки
Если нужно временно использовать данные без передачи владения, применяются ссылки (заимствование). Ссылки не становятся владельцами, поэтому исходная переменная остаётся доступной:
fn main() {
let s1 = String::from("Привет");
let len = calculate_length(&s1); // &s1 — ссылка, заимствование
println!("Длина '{}' = {}", s1, len); // s1 всё ещё доступна
}
fn calculate_length(s: &String) -> usize {
s.len()
} // s выходит из области видимости, но так как это ссылка, память не освобождается
Компилятор Rust проверяет правила заимствования:
- В любой момент может быть либо одна изменяемая ссылка, либо сколько угодно неизменяемых.
- Ссылки всегда должны быть валидными (нельзя вернуть ссылку на локальную переменную).
Итог
Rust заменяет сборщик мусора системой владения, заимствования и времён жизни (lifetimes). Все проверки выполняются на этапе компиляции, поэтому программа работает без накладных расходов на GC и без риска утечек памяти (если не использовать unsafe). Это даёт производительность, сравнимую с C/C++, но с гарантиями безопасности памяти.
<<Дополнение: времена жизни (lifetimes) и умные указатели
Первый ответ отлично объяснил основы. Хочу добавить, что полное управление памятью в Rust включает также времена жизни (lifetimes) и умные указатели для сложных сценариев.
Времена жизни — это аннотации, которые помогают компилятору проверить, что ссылки не переживают данные, на которые они указывают. Обычно они неявные, но в сложных функциях приходится указывать явно:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
Для случаев, когда нужно разделяемое владение (как в Java), Rust предоставляет умные указатели:
Rc<T>— подсчёт ссылок для одного потока.Arc<T>— атомарный подсчёт ссылок для многопоточности.RefCell<T>— внутренняя изменяемость с проверкой на этапе выполнения.
Эти конструкции имитируют поведение GC, но с явным контролем. Например, Rc освобождает память, когда счётчик ссылок падает до нуля:
use std::rc::Rc;
fn main() {
let s = Rc::new(String::from("Привет"));
let s2 = Rc::clone(&s); // Увеличивает счётчик
println!("{}", s); // Оба владельца валидны
} // Когда все Rc выходят из области видимости, память освобождается
Таким образом, Rust не нуждается в полноценном GC, потому что его система типов и правила владения покрывают 99% случаев управления памятью, а для оставшихся 1% есть умные указатели. Это даёт предсказуемую производительность и нулевые накладные расходы на сборку мусора.