Перейти к содержимому

Уроки · 46

Владение и ссылки

За несколько последних уроков for встречался в трёх разных видах: &points в уроке «Список кружков», &mut bullets в уроке «Турель стреляет очередью» и просто circles, вовсе без &, в уроке «Кружки исчезают». Каждый раз разница объяснялась на месте, по ходу дела. Теперь, когда все три уже знакомы, стоит собрать их вместе и назвать, что происходит на самом деле.

Любое значение в программе — например, список Vec — где-то лежит в памяти компьютера, пока программа работает. Список может быть огромным, на тысячи элементов, но ссылка на него — это не сам список, а что-то вроде бумажки с адресом: «он лежит вон там». Ссылка всегда занимает маленькое фиксированное место, независимо от того, насколько велико то, на что она указывает, — ведь в ней записан только адрес, а не содержимое целиком.

Символ & перед значением и означает «дай ссылку на это, а не само значение». В описаниях типов иногда пишут &T — это не что-то конкретное, а общая запись: T здесь просто заглушка вместо настоящего типа («T» от английского type), значит «ссылка на что угодно» — может быть &Vec<Bullet>, может быть &f32, какой тип подставишь, такая и ссылка.

for &(x, y) in &points {
draw_circle(x, y, 20.0, YELLOW);
}

&points — ссылка на список points, только для чтения: каждая точка читается по одной, чтобы нарисовать кружок, но ничего не меняется и не забирается в собственность. После цикла points остаётся ровно тем же списком, каким был, и им можно пользоваться дальше как ни в чём не бывало.

for bullet in &mut bullets {
bullet.x += bullet.direction_x * SPEED;
bullet.y += bullet.direction_y * SPEED;
draw_circle(bullet.x, bullet.y, 8.0, YELLOW);
}

&mut bullets — тоже ссылка, тоже не забирает список в собственность, но, в отличие от &bullets, разрешает менять то, на что она указывает: bullet.x и bullet.y можно записывать заново прямо внутри цикла. Сам список bullets при этом остаётся тем же самым списком — его длина не меняется, просто значения внутри перезаписываются.

for circle in circles {
// ...
kept_circles.push(circle);
}

Здесь & нет вовсе. circles не одалживается на время — список забирается в собственность целиком, а каждый его элемент по очереди становится собственностью переменной circle внутри цикла. Это называется владением: у каждого значения в Rust есть ровно один владелец, и когда владение передаётся дальше (как здесь, из circles в circle), прежний владелец больше не может им пользоваться — переменная circles после такого цикла попросту перестаёт существовать.

Вот почему после этого цикла нужно заново создавать circles:

circles = kept_circles;

Старого circles уже нет — есть только то новое значение, которое мы только что туда положили.

  • Значение в памяти всегда лежит у кого-то одного — это и называется владением. Владение можно передать дальше (переместить), и тогда прежний владелец перестаёт быть действителен.
  • Ссылка (&) — способ обратиться к значению, не забирая владение: временный «адрес», а не сама вещь. Ссылка занимает маленькое, фиксированное место, что бы она ни описывала.
  • &points — ссылка только для чтения: значение видно, но менять его через такую ссылку нельзя.
  • &mut bullets — ссылка для изменения: значение можно менять через неё, но владение всё равно остаётся у исходной переменной.
  • for circle in circles без & — не ссылка вовсе, а перемещение владения: список тратится целиком, элемент за элементом, и переменной circles после цикла больше нет.