По умолчанию ссылки, как и переменные, нельзя изменять. То есть по умолчанию мы не можем изменить значение, на которое ссылается ссылка. Например:
fn main(){
let message = "hello".to_string();
println!("До изменения: {}", message);
change_message(&message); // !Ошибка - значение по ссылки нельзя изменять по умолчанию
println!("После изменения: {}", message);
}
fn change_message(mes: &String){
mes.push('!');
}
Здесь для изменения значения по ссылке определена функция change_message(). У структуры String есть метод push(), который добавляет
к строке один символ. В данном случае мы пытаемся добавить к строке "hello" символ "!", то есть, чтобы в итоге получилась строка "hello!". Однако при компиляции
мы получим ошибку, поскольку по умолчанию значение по ссылке нельзя изменять:
Чтобы значение по ссылке все таки можно было изменять, необходимо использовать изменяемую ссылку (mutable reference), которая определяется с помощью оператора &mut:
fn main(){
let mut message = "hello".to_string();
println!("До изменения: {}", message);
change_message(&mut message);
println!("После изменения: {}", message);
}
fn change_message(mes: &mut String){
mes.push('!');
}
Здесь надо отметить три момента. Прежде всего, поскольку будет изменяться значение переменной, она должна быть определена с оператором mut:
let mut message = "hello".to_string();
Во-вторых, параметр-ссылка функции, через который надо изменять значение, определяется как изменяемая ссылка с &mut:
fn change_message(mes: &mut String){
В-третьих, при передаче ссылки в функцию значения перед ним также указывается оператор &mut:
change_message(&mut message);
В итоге, эта программа будет нормально компилироваться и одарит нас следующим консольным выводом:
До изменения: hello После изменения: hello!
Подобным образом можно определять переменные-изменяемые ссылки:
fn main(){
let mut s1 = "hello".to_string();
let s2 = &mut s1;
s2.push('!');
println!("{}", s1); // hello!
}
Следует отметить, что изменяемые ссылки имеют ограничение: в рамках одной области видимости может существовать только одна изменяемая ссылка, а никакиъх других ссылок (в том числе неизменяемых) одновременно с изменяемой ссылкой существовать не может. Например, следующий код не скомпилируется:
fn main(){
let mut s1 = "hello".to_string();
let s2 = &mut s1; // первая изменяемая ссылка
let s3 = &mut s1; // вторая изменяемая ссылка
s2.push('!');
println!("{}", s1); // hello!
}
В данном случае в одной области видимости - функции main определены две изменяемые ссылки - s2 и s3. Однако мы можем использовать вложенные области видимости, которые располагаются до определения изменяемых ссылок во внешних областях видимости:
fn main(){
let mut s1 = "hello".to_string();
// let s2 = &mut s1; // если определить здесь, будет ошибка
{
let s3 = &mut s1;
s3.push('?');
}
let s2 = &mut s1;
s2.push('!');
println!("{}", s1); // hello?!
}
Одна переменная-изменяемая ссылка - s3 определена в анонимном блоке, который заканчивает свою работу ДО определения второй изменяемой ссылки - s2. Соответственно никаких конфликтов между ссылками не возникнет.
Также возникнет ошибка и в следующей ситуации"
fn main(){
let mut s1 = "hello".to_string();
let s2 = &mut s1; // изменяемая ссылка
let s3 = &s1; // неизменяемая ссылка - ! Ошибка, так нельзя
s2.push('!');
println!("{}", s1);
println!("{}", s3);
}
Здесь в одной области видимости создаются и используются изменяемая и неизменяемая ссылки, соответственно на этапе компиляции мы получим ошибку
cannot borrow `s1` as immutable because it is also borrowed as mutable. Причем от этой ситуации следует отличать следующую:
fn main(){
let mut s1 = "hello".to_string();
let s3 = &s1; // неизменяемая ссылка
println!("{}", s3); // hello
// дальше неизменяемая ссылка s3 НЕ используется
let s2 = &mut s1; // изменяемая ссылка
s2.push('!');
println!("{}", s1); // hello!
}
Здесь также в одной области видимости создаются две ссылки на один и тот же объект, причем одан из ссылок - неизменяемая. Однако этот пример нормально скомпилируется. Почему? Потому что неизменяемая ссылка создается и используется ДО создания изменяемой ссылки. После создания изменяемой ссылки неизменяемая ссылка НЕ используется. Соответственно здесь не возникнет конфликта между ссылками. Аналогичная ситуация будет и в следующем случае:
fn main(){
let mut s1 = "hello".to_string();
let s2 = &mut s1; // изменяемая ссылка
s2.push('!');
println!("{}", s1); // hello!
// дальше изменяемая ссылка s2 НЕ используется
let s3 = &s1; // неизменяемая ссылка
println!("{}", s3); // hello!
}
Здесь наоборот сначала создается и используется изменяемая ссылка, а затем неизменяемая. НО опять же их создание и использование не смешивается, поэтому проблем не возникнет.