Comments 18
Можно подробнее про последний пример и "нормализацию"?
Что я вижу — она создаёт проблему по быстродействию. Как только вам понадобится список занятий для ученика — придётся перебирать общий список enrollments. В БД для того, чтобы делать это быстро — есть ключи и другие ухищрения, да и требования по быстродействию обычно не столь суровы, как к структурам данных в памяти, но всё равно порой разработчики сознательно идут на денормализацию базы.
Во первых, enrollments можно попробовать как-нибудь иначе организовать. В том числе, с ключами.
Во вторых, в расте всегда можно опуститься на уровень "сырых указателей" если они требуются. Да, гарантии придётся обеспечивать самостоятельно, но можно "небезопасные" внутренности завернуть в безопасный интерфейс.
Насчёт «во вторых» — ну да, вроде Rc, который упомянут в статье — вроде более-менее классический weak pointer получается (один из традиционных путей решения аналогичной задачи на C++). Но почему синтаксис такой страшный? Даже страшнее, чем в C++. Нельзя ли это переписать по человечески? (нутром чую, что можно)
Опять же, автор называет это решение «сложночитаемым» (я его понимаю) и рекомендует второе, дорогое — как «более правильное».
казалось бы, Rust нужен, чтобы бесплатно или задёшево давать гарантии «ссылочной целостности» и т.п.
Язык не всемогущ. (:
Сейчас правила "заимствования" в языке довольно просты, пусть и не всегда достаточно гибки. При этом данная часть всё равно считается сложной для освоения. Честно говоря, плохо представляю как это можно было бы улучшить, чтобы и изучение не усложнить и код кучей дополнительных аннотаций не загромождать.
Опять же, мне кажется, что возникшие неудобства вполне окупаются дополнительными гарантиями. Понимаю, что могут быть и другие мнения — чего уж там, некоторым и динамика по душе.
Нельзя ли это переписать по человечески? (нутром чую, что можно)
Речь о вещах типа Vec<Rc<RefCell<Class>>>
? Ну можно тайпдефы использовать. А вообще мне такая матрёшка даже нравится: оно гибче, чем заводить отдельные классы на каждую комбинацию возможностей.
в 3 случае мы имеем 2 взаимосвязанных delete в принципе тут мог решить проблему алгоритм разрешения циклических ссылок аналогичный алгоритму сборки муссора
для этого delete пришлось бы объединить в атомик (внутри атомика компилятор допускает висящие ссылки, но не допускает их использования) думаю к этому придут
в 1 случае компилятору ничто не мешает увидеть, что область использования переменной jill_ref_mut заканчивается после строки jill_ref_mut.celebrate_birthday(); тут компилятор тоже может без проблем разобраться однозначно сам.
аналогично случай 2.
Как вариант, индекс или id вполне могут выполнять роль «ссылки». Всё зависит от задачи, а задачи могут со временем меняться.
Ссылку можно сделать, если известно, что объект «ученик» будет жить меньше, чем объект «школа».
Односторонние ссылки в этом случае проблемы не составляют.
У индекса/id есть преимущество — он не прибивает объект, на который ссылается, гвоздями к определённому месту в памяти.
К сожалению в статье не упомянут другой недостаток Rc/Arc — при неправильном употреблении они могут привести к утечке памяти.
Но это сразу станет немного «дороже» — часть проверок перенесётся в рантайм, в программе придётся обрабатывать случаи, когда «что-то пошло не так».
У всего своя цена, серебряных пуль нет.
Поэтому в первую очередь ставится вопрос: зачем эта обратная ссылка на школу? Нужна ли она? Можно ли обойтись без неё? Какая стоит задача?
В конце концов, если уж очень нужно, никто же не мешает сделать структуру со школами и учениками на сырых указателях «как в старом добром Си» и работать с нею на свой страх и риск, огребая все щедро рассыпанные на пути грабли. Ссылки и времена жизни сделали ведь не для того, чтобы портить программисту жизнь, — это мощный инструмент автоматического контроля со статическими гарантиями.
Чуть выше я предложил использовать индексы или id — это не то же самое, что список пар «ученик-школа», упомянутый в статье. Тут уместнее предположить две владеющих структуры списком школ и списком учеников, а школы и ученики могут, если нужно, просто хранить номера или ключи в этой структуре.
Недостатки ссылок вполне перечислены в статье — ссылка может быть либо одна изменяемая, либо много — на неизменяемый объект. Меньше ссылок, связывающих состояние — меньше ограничений. Преимущество ссылки — более быстрое получение объекта при необходимости и статические гарантии, что объект существует.
Получение школы или ученика из таблицы по индексу — достаточно дешёвая операция, чтобы рассмотреть её как альтернативу хранению ссылки, при этом, если количество объектов небольшое, то можно сэкономить на размере хранимого значения и что-то выиграть с кешами процессора. Плюс к этому, номер в списке вполне можно применить к разным таблицам, хранящим разную информацию об объекте. Но у массивов и индексов в них есть и существенный недостаток, они плохо применимы к динамическим данным, когда списки активно меняются и элементы массово удаляются — здесь уже нужно решать вопросы с фрагментацией списков или обновлением индексов, что дорого. С другой стороны с указателями — это ещё сложнее, а со ссылками об этом можно забыть. Минус статические гарантии, плюс проверки и риск ошибок.
Как компромис в такой ситуации можно использовать {Hash,BTree}Map с идентификаторами, учитывая, что получить из него что-то по ключу — это уже более дорогая операция, чем обычный индекс. Так же без гарантий и с лишними проверками и рисками.
Rust, как системный язык, даёт разные инструменты для тонкого контроля над тем, как работает программа. При этом инструменты далеко не всегда взаимозаменяемые, каждый со своими требованиями, ограничениями, ценой, хотя может показаться «здесь куча разных способов сделать одно и то же». Поэтому ещё раз повторюсь: в первую очередь всё определяет задача, а уже дальше нужно думать, какой инструмент для её решения использовать. Делать что-то «для удобства», «так привычно» или «на всякий случай» — плохая мотивация.
Первый пример прекрасно компилируется. Что я делаю не так?
fn main() {
let mut jill = Person::new("Jill", 19);
let jill_ref_mut = &mut jill;
jill_ref_mut.celebrate_birthday();
println!("{}", jill.name()); // невозможно заимствовать jill как неизменяемое
// потому что это уже заимствовано как
// изменяемое
}
(rustc 1.31.1)
Борьба с проверкой заимствования