Что представляет собой эквивалент shared_ptr в C++ в Rust?

Почему этот синтаксис не разрешен в 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 просто аннулирует первый объект и сделает его непригодным для использования ...

Что представляет собой эквивалент shared_ptr в C++ в Rust?

Мы должны сделать что-то вроде этого:

Что представляет собой эквивалент shared_ptr в C++ в Rust?

Мне нравится идея копирования по ссылке, но зачем автоматически аннулировать первую?

Должно быть возможно избежать двойного освобождения с помощью другого метода. Например, в C++ уже есть отличный инструмент, позволяющий использовать несколько бесплатных вызовов ... shared_ptr вызывает бесплатные только тогда, когда никакой другой указатель не указывает на объект - кажется, это очень похоже на то, что мы на самом деле делаем, с той разницей, что у shared_ptr есть счетчик.

Например, мы можем подсчитать количество ссылок на каждый объект во время компиляции и вызвать free только тогда, когда последняя ссылка выходит за пределы области видимости.

Но Rust - молодой язык; может у них не было времени реализовать что-то подобное? Планирует ли Rust разрешить вторую ссылку на объект, не аннулируя первую, или нам следует взять за привычку работать только со ссылкой на ссылку?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
0
3 796
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Либо 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 только в случае крайней необходимости, например, когда вы реализуете структуру в виде графа.

Спасибо за комментарий о clone () :)

tirz 14.04.2018 20:08

Да, это антипаттерн, но отличный пример того, что a по-прежнему можно использовать.

tirz 14.04.2018 20:16

Другие вопросы по теме