Ошибка "cannot borrow as mutable because it is also borrowed as immutable" в Rust: как исправить?
Почему возникает ошибка?
Ошибка cannot borrow as mutable because it is also borrowed as immutable — это проявление фундаментального правила Rust: безопасность заимствования. Оно гарантирует, что данные не изменятся, пока на них есть активные ссылки. В вашем случае:
let first = &text[0..1];— создаёт неизменяемую ссылку на всю строку (срез&strзаимствует владельцаString).text.push_str(" world");— требует изменяемой ссылки наtext.- Но
firstвсё ещё "жив" до конца области видимости, поэтому компилятор блокирует изменения.
Проблема в том, что Rust не может доказать, что push_str не перераспределит память, сделав first висячей ссылкой. Даже если вы не используете first после изменения — компилятор смотрит на область видимости ссылки, а не на реальное использование.
Решение 1: Переупорядочивание кода (самое простое)
Если вам не нужно использовать first после изменения строки, просто перенесите изменяемую операцию после последнего использования неизменяемой ссылки:
fn main() {
let mut text = String::from("hello");
let first = &text[0..1];
println!("First char: {}", first); // последнее использование first
text.push_str(" world"); // теперь изменяемое заимствование безопасно
println!("Full text: {}", text);
}
Здесь first "умирает" после println!, и компилятор разрешает push_str.
Решение 2: Клонирование данных (если нужна копия)
Если вам нужно сохранить подстроку для дальнейшего использования после изменения строки, создайте копию данных:
fn main() {
let mut text = String::from("hello");
let first = text[0..1].to_string(); // to_string() создаёт новую String
text.push_str(" world");
println!("First char: {}", first); // first — независимая копия
println!("Full text: {}", text);
}
Недостаток: дополнительное выделение памяти. Но это безопасно и просто.
Решение 3: Использование индексов вместо ссылок
Если вам нужен только первый символ, можно сохранить его индекс, а не ссылку:
fn main() {
let mut text = String::from("hello");
let first_index = 0; // сохраняем индекс
text.push_str(" world");
let first_char = text.chars().nth(first_index).unwrap();
println!("First char: {}", first_char);
}
Это работает, потому что индекс — это простой usize, который не заимствует строку.
Решение 4: Разделение на отдельные области видимости
Можно использовать блоки {} для ограничения времени жизни ссылок:
fn main() {
let mut text = String::from("hello");
let first = {
&text[0..1] // ссылка живёт только внутри этого блока
};
// здесь first уже недоступен
text.push_str(" world");
println!("Full text: {}", text);
}
Что выбрать?
- Если порядок важен и данные нужны после изменения — используйте
to_string()(клонирование). - Если можно изменить порядок — переупорядочьте код.
- Если нужны только индексы — работайте с ними.
Помните: Rust не даёт вам выстрелить себе в ногу. Эта ошибка — защита от неопределённого поведения, которое было бы возможно в C++ или Java.
<<Альтернативный подход: Cell/RefCell для внутренней изменяемости
Если вам нужно обойти правила заимствования в сложном сценарии (например, когда ссылки и изменения пересекаются в разных частях кода), можно использовать RefCell для проверки заимствований во время выполнения:
use std::cell::RefCell;
fn main() {
let text = RefCell::new(String::from("hello"));
let first = text.borrow(); // неизменяемое заимствование
println!("First char: {}", &first[0..1]);
text.borrow_mut().push_str(" world"); // изменяемое заимствование
// После drop(first) можно снова использовать
drop(first);
println!("Full text: {}", text.borrow());
}
Важно: RefCell проверяет правила заимствования в рантайме, а не на этапе компиляции. Если нарушить правила (например, попытаться взять borrow_mut пока есть активный borrow), программа запаникует. Используйте этот подход только когда статические проверки компилятора невозможно удовлетворить, например, в рекурсивных структурах или графах.
Для вашей простой задачи лучше использовать to_string() или переупорядочивание — это проще и безопаснее.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги