Почему этот синтаксис не разрешен в Rust:
fn main() {
let a = String::from("ping");
let b = a;
println!("{{{}, {}}}", a, b);
}
Когда я попытался скомпилировать этот код, то получил:
error[E0382]: use of moved value: `a` --> src/main.rs:5:28 | 3 | let b = a; | - value moved here 4 | 5 | println!("{{{}, {}}}", a, b); | ^ value used here after move | = note: move occurs because `a` has type `std::string::String`, which does not implement the `Copy` trait
Фактически, мы можем просто сделать ссылку - что не то же самое во время выполнения:
fn main() {
let a = String::from("ping");
let b = &a;
println!("{{{}, {}}}", a, b);
}
И это работает:
{ping, ping}
Согласно книга ржавчины, это позволяет избежать двойных бесплатных ошибок, потому что переменные Rust копируются по ссылке, а не по значению. Rust просто аннулирует первый объект и сделает его непригодным для использования ...
Мы должны сделать что-то вроде этого:
Мне нравится идея копирования по ссылке, но зачем автоматически аннулировать первую?
Должно быть возможно избежать двойного освобождения с помощью другого метода. Например, в C++ уже есть отличный инструмент, позволяющий использовать несколько бесплатных вызовов ... shared_ptr вызывает бесплатные только тогда, когда никакой другой указатель не указывает на объект - кажется, это очень похоже на то, что мы на самом деле делаем, с той разницей, что у shared_ptr есть счетчик.
Например, мы можем подсчитать количество ссылок на каждый объект во время компиляции и вызвать free только тогда, когда последняя ссылка выходит за пределы области видимости.
Но Rust - молодой язык; может у них не было времени реализовать что-то подобное? Планирует ли Rust разрешить вторую ссылку на объект, не аннулируя первую, или нам следует взять за привычку работать только со ссылкой на ссылку?





Либо Rc, либо Arc является заменой для shared_ptr. Что вы выберете, зависит от того, какой уровень безопасности потоков вам нужен для общих данных; Rc предназначен для случаев без потоков, а Arc - для случаев, когда вам нужны потоки:
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("ping"));
let b = a.clone();
println!("{{{}, {}}}", a, b);
}
Как и shared_ptr, нет копирует сам String. Он увеличивает счетчик ссылок только во время выполнения, когда вызывается clone, и уменьшает счетчик, когда каждая копия выходит за пределы области видимости.
В отличие от shared_ptr, Rc и Arc имеют лучшую семантику потоков. shared_ptr является полупоточно-ориентированным. Счетчик ссылок shared_ptr сам по себе является потокобезопасным, но общие данные не «волшебным образом» сделаны потокобезопасными.
Если вы используете shared_ptr в многопоточной программе, вам все равно придется поработать, чтобы убедиться, что это безопасно. В непоточной программе вы платите за некоторую поточную безопасность, которая вам не нужна.
Если вы хотите разрешить изменение общего значения, вам также необходимо переключиться на проверку заимствования во время выполнения. Это обеспечивается такими типами, как Cell, RefCell, Mutex и т. д. RefCell подходит для String и Rc:
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let a = Rc::new(RefCell::new(String::from("ping")));
let b = a.clone();
println!("{{{}, {}}}", a.borrow(), b.borrow());
a.borrow_mut().push_str("pong");
println!("{{{}, {}}}", a.borrow(), b.borrow());
}
we can count the number of references to each object during the compilation time and call free only when the last reference goes out of the scope.
Это почти то же самое, что Rust делает со ссылками. Он не использует счетчик фактически, но позволяет вам использовать только ссылки на значение, в то время как это значение гарантированно останется по тому же адресу памяти.
shared_ptr C++ делает это во время компиляции нет. shared_ptr, Rc и Arc - это конструкции среды выполнения, которые поддерживают счетчик.
Is it possible to make a reference to the object without invalidate the first reference?
Это именно то, что Rust делает со ссылками, и что вы уже сделали:
fn main() {
let a = String::from("ping");
let b = &a;
println!("{{{}, {}}}", a, b);
}
Более того, компилятор остановит вас от использования b, как только a станет недействительным.
because Rust's variables are copied by reference instead of by value
Это неправда. Когда вы присваиваете значение, право собственности на значение передается новой переменной. Семантически адрес памяти переменной изменился, и, таким образом, чтение этого адреса может привести к небезопасности памяти.
should we take the habit to only work with a reference
Да, использование ссылок, когда возможно, является наиболее идиоматическим выбором. Они не требуют дополнительных затрат времени выполнения, и компилятор сообщит вам об ошибках, а не обнаружит их во время выполнения.
Конечно, бывают случаи, когда пригодятся Rc или Arc. Часто они нужны для циклических структур данных. Вам не следует расстраиваться из-за их использования, если вы не можете получить простые рекомендации для работы.
with a reference of a reference?
Это небольшой недостаток, так как дополнительное косвенное обращение нежелательно. Если вам действительно нужно, вы можете уменьшить его. Если вам не нужно изменять строку, вы можете вместо этого переключиться на Rc<str>:
use std::rc::Rc;
fn main() {
let a: Rc<str> = Rc::from("ping");
let b = a.clone();
println!("{{{}, {}}}", a, b);
}
Если вам нужно сохранить возможность иногда изменять String, вы также можете явно преобразовать &Rc<T> в &T:
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("ping"));
let b = a.clone();
let a_s: &str = &*a;
let b_s: &str = &*b;
println!("{{{}, {}}}", a_s, b_s);
}
Смотрите также:
Maybe we can simply count the number of references to each object during the compile time and call free only when the last reference goes out of the scope.
Вы на правильном пути! Это то, для чего нужен Rc. Это тип интеллектуального указателя, очень похожий на std::shared_ptr в C++. Он освобождает память только после того, как последний экземпляр указателя выходит за пределы области видимости:
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("ping"));
// clone() here does not copy the string; it creates another pointer
// and increments the reference count
let b = a.clone();
println!("{{{}, {}}}", *a, *b);
}
Поскольку вы получаете только неизменяемый доступ к содержимому Rc (в конце концов, он является общим, а общая изменчивость запрещена в Rust), вам нужна внутренняя изменчивость, чтобы иметь возможность изменять его содержимое, реализованное через Cell или RefCell:
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let a = Rc::new(RefCell::new(String::from("Hello")));
let b = a.clone();
a.borrow_mut() += ", World!";
println!("{}", *b); // Prints "Hello, World!"
}
Но в большинстве случаев вам вообще не нужно использовать Rc (или его поточно-ориентированный брат Arc). Модель владения Rust в основном позволяет избежать накладных расходов на подсчет ссылок, объявляя экземпляр String в одном месте и используя ссылки на него повсюду, как вы это делали во втором фрагменте. Попробуйте сосредоточиться на этом и используйте Rc только в случае крайней необходимости, например, когда вы реализуете структуру в виде графа.
Да, это антипаттерн, но отличный пример того, что a по-прежнему можно использовать.
Спасибо за комментарий о clone () :)