Для управления данными в куче в Rust применяется такая концепция как ownership (можно перевести как "владение", "принадлежность"). Она является одной из характерных возможностей и особенностей языка Rust, которая позволяет гарантировать безопасность памяти.
Ownership имеет следующие аспекты:
Каждое значение в Rust имеет "владельца" (owner) (например, в виде переменной или параметра функции).
У значения в один момент времени может быть только один владелец.
Когда владелец выходит за пределы области видимости/контекста, в котором он определен, его значение удаляется из памяти. Для этого Rust автоматически вызывает специальную функцию - drop(), которая очищает память.
Рассмотрим простейшую ситуацию:
fn main(){
let s1 = "hello".to_string(); // s1 - владелец строки "hello"
println!("s1: {}", s1);
let s2 = s1; // меняем владельца строки на s2
println!("s1: {}", s1); // !Ошибка - s1 теперь нельзя использовать
println!("s2: {}", s2);
}
Вначале определяем переменную s1, которая является владельцем строки "hello". Строка "hello" хранится в куче.
Потом меняем владельца, присваивая значение s1 переменной s2.
let s2 = s1;
Но у значения может быть только один владелец. Поэтому s1 перестает быть владельцем. Таким образом, владение строкой "hello" переходит от переменной s1 к переменной s2.
А переменная s1 теперь становится неинициализированной, и соответственно нам ее нельзя использовать. В итоге при компиляции этого кода мы получим
ошибку на строке
println!("s1: {}", s1); // !Ошибка - s1 теперь нельзя использовать
Хотя мы можем заново инициализировть переменную s1, присвоив ей какое-либо значение:
fn main(){
let mut s1 = "hello".to_string(); // s1 - владелец строки "hello"
println!("s1: {}", s1);
let s2 = s1; // меняем владельца строки на s2
s1 = "world".to_string(); // s1 - владелец строки "world"
println!("s1: {}", s1); // s1: world
println!("s2: {}", s2); // s2: hello
}
Передача значения параметру в функцию аналогична присвоению значения переменной:
fn main(){
let s1 = "hello".to_string(); // s1 - владелец строки "hello"
println!("s1: {}", s1);
display_message(s1);
println!("s1: {}", s1); // !Ошибка - переменная s1 неинициализирована
}
fn display_message(message: String){
println!("message: {}", message);
}
Здесь значение переменной s1 передается параметру message при вызове функции display_message():
display_message(s1);
Это приводит к тому, что опять же переменная s1 теряет владение над строкой "hello", которое переходит к параметру message.
В итоге опять же при компиляции мы получим ошибку на строке:
println!("s1: {}", s1); // !Ошибка - переменная s1 неинициализирована
Мы можем вручную вызвать функцию drop() и таким образом освободить память, не дожидаясь, пока владелец значения выйдет за пределы области видимости. Например:
fn main(){
let s1 = "hello".to_string(); // s1 - владелец строки "hello"
println!("s1: {s1}"); // s1: hello
drop(s1); // явным образом удаляем ссылку
// println!("s1: {s1}"); // ! Ошибка - s2 неинициализирована
}
В данном случае после вызова функции drop() переменная s1 будет неинициализирована, память связанного с ней значения в куче будет очищена.