Что означают символы амперсанда (&) и звездочки (*) в Rust?
Всем привет! Я только начал изучать Rust и никак не могу разобраться с двумя операторами: & (амперсанд) и * (звёздочка). В других языках, например в C++, я привык, что & — это взятие адреса, а * — разыменование указателя. Но в Rust всё как-то иначе.
Например, я написал простую программу, которая пытается изменить значение переменной через функцию:
fn main() {
let x = 42;
let y = &x;
println!("{}", y); // Работает, но что тут происходит?
let mut z = 10;
change_value(&z);
println!("{}", z);
}
fn change_value(val: &i32) {
*val = 100; // Ошибка! Почему?
}
Компилятор ругается на строку *val = 100, говорит что-то про то, что val — это неизменяемая ссылка. Но ведь я использую звёздочку, чтобы разыменовать! Почему это не работает?
Также я не понимаю, зачем иногда нужно писать & перед переменной при передаче в функцию, а иногда — нет. И когда нужно использовать * для доступа к значению?
Пожалуйста, объясните простыми словами, что такое ссылки и разыменование в Rust, и в чём отличие от указателей в C/C++.
<<Что такое ссылки (&) и разыменование (*) в Rust
В Rust & и * — это не про указатели в стиле C, а про ссылки и разыменование в контексте системы владения (ownership). Давайте разберёмся по порядку.
1. Амперсанд (&) — оператор взятия ссылки
Когда вы пишете &x, вы не копируете значение x, а создаёте ссылку на него. Ссылка — это как «закладка» в книге: она говорит компилятору «посмотри на эту ячейку памяти, но не забирай её себе». В Rust это называется заимствование (borrowing).
let x = 42;
let y = &x; // y — это ссылка на x
Теперь y имеет тип &i32 (ссылка на 32-битное целое). Она не владеет значением 42, а только указывает на него.
2. Звёздочка (*) — оператор разыменования
Чтобы получить доступ к значению, на которое указывает ссылка, используется *. Это называется разыменованием:
let x = 42;
let y = &x;
println!("{}", *y); // Выведет 42
Без звёздочки println!("{}", y) тоже сработает, но только потому, что макрос println! автоматически разыменовывает ссылки для форматирования. В обычных операциях (например, арифметика) нужно писать *y.
3. Почему ваш код не работает?
Вы написали:
fn change_value(val: &i32) {
*val = 100; // Ошибка!
}
Проблема в том, что val — это неизменяемая (immutable) ссылка (&i32). Через неё нельзя изменить данные. Чтобы изменять значение, нужно использовать изменяемую ссылку (&mut i32):
fn change_value(val: &mut i32) {
*val = 100; // Теперь работает!
}
fn main() {
let mut z = 10;
change_value(&mut z); // Передаём изменяемую ссылку
println!("{}", z); // Выведет 100
}
Обратите внимание: &mut z — это взятие изменяемой ссылки. Это одно из ключевых правил Rust: у вас может быть либо одна изменяемая ссылка, либо множество неизменяемых, но не одновременно.
4. Отличие от C/C++
- Безопасность: В Rust ссылки всегда валидны. Нет нулевых ссылок (null), нет висячих указателей (dangling pointers). Компилятор проверят это на этапе компиляции.
- Автоматическое разыменование: В некоторых контекстах (методы, сравнения) Rust автоматически применяет
*. В C/C++ этого нет. - Владение: В C
&даёт адрес, который можно хранить и использовать, пока жив оригинал. В Rust ссылка «живёт» только пока жив владелец, и компилятор это проверяет.
5. Примеры для закрепления
// Неизменяемая ссылка — только чтение
let s1 = String::from("hello");
let len = calculate_length(&s1); // Заимствуем s1
fn calculate_length(s: &String) -> usize {
s.len() // s — ссылка, но .len() работает благодаря автоматическому разыменованию
} // s выходит из области видимости, но ничего не происходит — мы не владели значением
// Изменяемая ссылка — запись
let mut s2 = String::from("hello");
change(&mut s2);
fn change(some_string: &mut String) {
some_string.push_str(", world"); // Изменяем через ссылку
}
Главное запомните: & — это «одолжить значение на время», а * — «дай мне то, на что ты ссылаешься». И никогда не пытайтесь изменить данные через &T — компилятор этого не позволит.
Дополнение: Автоматическое разыменование и оператор &mut
Первый ответ отлично объясняет базу. Я хочу добавить нюанс, который часто путает новичков — автоматическое разыменование (deref coercion).
В Rust компилятор часто сам подставляет * за вас, когда это очевидно. Например, при вызове методов:
let x = Box::new(42); // Box — это умный указатель, похожий на ссылку
println!("{}", x); // Работает! println сам разыменовал Box
println!("{}", *x); // То же самое, но явно
// Вызов методов:
let s = String::from("hello");
let r = &s; // r: &String
println!("{}", r.len()); // Работает! Rust автоматически разыменовал &String до String
Это поведение реализовано через трейт Deref. Компилятор видит, что у вас &String, а метод .len() есть у String, и автоматически применяет * столько раз, сколько нужно.
Когда нужно писать * явно?
- В арифметике:
let sum = *a + *b;— еслиaиb— ссылки. - При присваивании:
*ptr = new_value;— чтобы изменить значение через изменяемую ссылку. - При сравнении:
if *a == *b { ... }— если хотите сравнить значения, а не адреса.
Также помните: &mut даёт вам эксклюзивный доступ на запись. Если вы передали &mut x в функцию, то пока она не завершится, вы не сможете использовать x напрямую — это защита от гонок данных (data races).
Совет: всегда начинайте с неизменяемых ссылок (&T), и только если компилятор скажет, что нужно &mut, меняйте. Так вы привыкнете к идиомам Rust.
Ответов пока нет.
Войдите чтобы оставить ответ