LINUX.ORG.RU

Вопрос по Rust (Option + Rc + RefCell)

 ,


1

2

Доброго времени суток,

Программируя небольшую игрулечку на piston+rapier2d, создал класс героя(Hero) :

pub struct Hero {
    pub rotation: f64,
    pub health: f32,
    pub texture: Texture,
    step_size: f64,
    // --- [ Physics ] --- //
    body_handle: Option<RigidBodyHandle>,
    rigid_body_set: Option<Rc<RefCell<RigidBodySet>>>,
    collider_set: Option<Rc<RefCell<ColliderSet>>>,
}


Три последние члена класса задаются через функцию trait-a PhysicalObject - init_with_physics(…).

Вопрос у меня в том правильно ли я всё делаю, меня смущает синтаксическая «огромность» при доступе к этим трём членам класса. В частности, когда использую Option<Rc<RefCell<…>>>

Например что бы получить rigid_body_set :

let rigid_set_rc = self.rigid_body_set.clone().unwrap();
let mut rigid_set = rigid_set_rc.borrow_mut();

В одну строчку это сделать не получилось - rust-analyzer ругается :

temporary value dropped while borrowed consider using a let >binding to create a longer lived value

Также как понимаю при unwrap(), объект внутри option перемещается, поэтому приходится clone() использовать. Правильно ли это? Или есть другие подходы представлять в struct неинициализированные данные, которые могут быть позже заданы.

let mut rigid_set = self.rigid_body_set.as_ref().unwrap().borrow_mut();
ilammy ★★★
()

Для решения подобных архитектурных задачек можно применить разные подходы.

Например, можно использовать подход Entity Component System. В этом случае я рекомендую посмотреть на https://arewegameyet.rs/ecosystem/ecs/. На практике ничего из этого я не использовал, но https://docs.rs/bevy_ecs/0.6.0/bevy_ecs/ и https://docs.rs/specs/0.17.0/specs/ выглядят многообещающе.

Если не хочется «тащить» эти крейты в свой проект, можно просто посмотреть как подобные вопросы решаются другими, более опытными разработчиками.


Еще один совет: зачастую можно избавится от использования Rc<RefCell<…>> скорректировав архитектуру. Но чтобы что-то посоветовать, нужна знать немного больше о той задаче, которую вы пытаетесь решить.

trex6 ★★★★★
()
Ответ на: комментарий от WatchCat

Даже если не забудешь - можно нарваться на нехилый рефакторинг из-за одного единственного unwrap где-то далеко внутри.

trex6 ★★★★★
()

Разве синтаксическая огромность доступа не решается введением метода-геттера, в котором ее можно упрятать и забыть?

Virtuos86 ★★★★★
()
self.rigid_body_set.map(|rigid_set_rc| {
	let mut rigid_set = rigid_set_rc.borrow_mut();
	//твой код
});
us976
()
Последнее исправление: us976 (всего исправлений: 1)

Для проходящих мимо - тут растеры обсуждают насколько «удобно» в раст выглядит аналог std::shared_ptr, и сколькими способами с ним можно работать.

anonymous
()
Ответ на: комментарий от trex6

Спасибо за ответы, видел ECS в amethyst и bevy, но отпугнули меня эти data-driven движки, для начала хотел что-то попроще, но сейчас получше себя чувствую в rust - так что посмотрю.

xionovermazes
() автор топика

если не нужно изменять значение, то выглядит довольно «удобно»

let num_rc = Rc::new(0);
if num_rc.as_ref() == &0 {
    println!("0");
} else {
    println!("{}", num_rc);
}
us976
()
Ответ на: комментарий от Virtuos86

при наличии в ЯП паттерн-матчинга «удобно» можно без кавычек писать :)

Примеры в этом треде говорят о совсем другом.

anonymous
()
Ответ на: комментарий от anonymous

Для проходящих мимо - тут растеры обсуждают насколько «удобно» в раст выглядит аналог std::shared_ptr, и сколькими способами с ним можно работать.

Там еще мьютекс, если делать точный аналог на C++ то убрать можно только RefCell.

anonymous
()
Ответ на: комментарий от trex6

Зато нет UB, как при разыменовывании std::shared_ptr.

На практике те же яйца. Что растокод вылетит с паникой, что плюсатый с сегфолтом.

anonymous
()
Ответ на: комментарий от anonymous

Что растокод вылетит с паникой

В реальных проектах unwrap-ами не пользуются.

Или вы другую панику имели в виду?

P.S. UB вовсе не тождественнен сегфолту. Это намного хуже.

trex6 ★★★★★
()
Последнее исправление: trex6 (всего исправлений: 3)
Ответ на: комментарий от trex6

В реальных проектах unwrap-ами не пользуются.

Просто погрепай реальные проекты. Плюс тут еще и borrow_mut, который тоже паникует.

P.S. UB вовсе не тождественнен сегфолту. Это намного хуже.

Опять же в теории для сферических языков в вакууме, на практике разыменование нулевого указателя или сегфолт или рестарт (что в принципе равнозначно).

anonymous
()
Ответ на: комментарий от anonymous

Просто погрепай реальные проекты.

Ок. В тех проектах, которые мне доводилось в прод выкатывать - не пользовались.

Грепать сложно - его часто в тестах применяют.

Плюс тут еще и borrow_mut, который тоже паникует.

В самую точку! RefCell - вообще штука так себе.

Опять же в теории для сферических языков в вакууме, на практике разыменование нулевого указателя или сегфолт или рестарт

В релизных сборках конпелятор может наоптимизировать. Была статья пару лет назад на хабре. Но что-то уже не гуглится.

trex6 ★★★★★
()

Самое забавное, что в доках по русту вот прямо твой случай так и описан.

https://doc.rust-lang.org/book/ch15-05-interior-mutability.html

Но да, выглядит крайне всрато. Запили себе отдельный тип, комбинирующий Rc<RefCell<…>> и сделай ему нормальный API с проверкой ошибок, если такой крейт найти не сможешь.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 2)
Ответ на: комментарий от anonymous

Для проходящих мимо - тут растеры обсуждают насколько «удобно» в раст выглядит аналог std::shared_ptr, и сколькими способами с ним можно работать.

Только здесь нужно понимать, что эта растовская конструкция работает по скорости быстрее, чем плюсовый std::shared_ptr

anonymous
()

Для скорости можно еще заменить RefCell на UnsafeCell

anonymous
()
Ответ на: комментарий от anonymous

Только здесь нужно понимать, что эта растовская конструкция работает по скорости быстрее, чем плюсовый std::shared_ptr

4.2, отдельно Rc быстрее (потому-что не на атомиках), а то, что у ТС - однозначно медленнее.

anonymous
()
Ответ на: комментарий от anonymous

Не только из-за атомиков. Еще и перемещение работает быстрее.

Вообще, эмпирическое правило такое, что идиоматический растовский код работает обычно максимально быстро, а плюсовый нужно часто напильником допиливать. Увы!

anonymous
()
Ответ на: комментарий от WatchCat

Все верно, но это не отменяет факта что borrow_mut будет паниковать.

Сам RefCell очень полезен на этапе обучения.

Но в какой-то моменты ты меняешь свой подход к архитектуре и он становится ненужен практически везде.

Подозреваю, что и в async контексте он тоже не будет работать нормально.

trex6 ★★★★★
()
Ответ на: комментарий от trex6

Но в какой-то моменты ты меняешь свой подход к архитектуре и он становится ненужен практически везде.

А где про этот измененный подход можно почитать? Который устраняет необходимость обходить временами деревянный БЧ?

Virtuos86 ★★★★★
()
Ответ на: комментарий от Virtuos86

Не уверен, что кто-то это формализовал. Можем посмотреть на ваши примеры, где необходимо использовать RefCell и обсудить.

trex6 ★★★★★
()
Ответ на: комментарий от trex6

У меня нет примеров :(. Но RefSell ведь по определению тащат туда, где borrow-checker не дает использовать обычную изменяемую ссылку, разве нет?

Virtuos86 ★★★★★
()
Последнее исправление: Virtuos86 (всего исправлений: 1)
Ответ на: комментарий от trex6

А как сделать двусвязный список, без использования unsafe?

us976
()
Ответ на: комментарий от anonymous

Нахер ему тут скорость? Он уже все написал и оно тормозит? Он профилировал? Ты профилировал? Кто-то профилировал?

80% кода можно написать хоть на черте лысом, остальные 20% можно оптимизировать любым доступным способом, вплоть до вызова C или либы на асме.

Первая цель - написать хоть что-то, что работает как ожидалось. Для остального надо собирать метрики и делать выводы.

anonymous
()
Ответ на: комментарий от Virtuos86

тащат туда, где borrow-checker не дает использовать обычную изменяемую ссылку, разве нет? Бороться с borrow-checker лучше бороться на алгоритмическом уровне. Если borrow-checker что-то не нравится, то это не спроста.

Для меня RefCell выглядить как хак для осваивающих язык и еще не отошедших от старых привычек. Но наверняка найдутся хорошие примеры, где без него никуда.

trex6 ★★★★★
()

Во-первых, раз у тебя все три поля задаются через трейт PhysicalObject, то почему бы их не запихнуть в одну структуру, а ее уже в твоего Hero. Был бы

struct Physics {
    body_handle: Option<RigidBodyHandle>,
    rigid_body_set: Option<RigidBodySet>,
    collider_set: Option<ColliderSet>,
}

Hero {
    ...
    physics: Rc<RefCell<Physics>>
}

Во-вторых, я уверен, что и Rc<RefCell<...>> тебе на самом деле не нужен. Для чего ты вообще это используешь? Ты собираешь какой-то контейнер со всеми PhysicalObject в игре? Так ты можешь положить туда Hero целиком, если у тебя будет контейнер вида Vec<Box<dyn PhysicalObject>>. Как у тебя в принципе реализована работа с PhysicalObject?

Aswed ★★★★★
()
Ответ на: комментарий от Aswed

Вообщем да, есть Trait - PhysicalObject. Храню в контейнере объекты PhysicalObject + State.

Я вот подумал, в классе Уровня хранить Box< RigidBodySet >, Box< ColliderSet >. А в обьектах в уровня в этом контейнере, хранить ссылки на них Option< &’a Box< RigidBodySet > >.

В целом в архитектурах там где нужна ссылка на родителя или на член-класса родителя, как лучше сделать ? Может быть вообще можно без Box<…> обойтись : В классе уровня хранить RigidBodySet , А в объектах уровня &’a mut RigidBodySet ?

xionovermazes
() автор топика
Ответ на: комментарий от xionovermazes

Смотри, так как при удалении, добавлении объекта в твой Vec<GameObject> тебе все равно придется менять свой RigidBodySet и ColliderSet(у тебя же не должно быть неактивного Collider или Collider объекта, который удален), я бы предложил в последних двух сетах хранить индексы объектов в Vec<GameObject>. Таким образом объекты изменяются только из Vec<GameObject> привязанного к уровню, и у тебя отпадает нужда как в Rc так и в RefCell. Минус подхода - тебе надо будет перезаполнять массивы индексов при каждом обновлении Vec<GameObject>. Но это не значит, что такое решение будет работать медленнее(это надо замерять). Если обновление сетов индексов редкое и батчами, то вообще все круто будет

Aswed ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.