Обновить

Комментарии 66

Начну немного издалека - за много лет программирования я заметил, что у любой программы начиная с некоторого уровня сложности модель данных превращается в Document Object Model - нечто похожее на HTML DOM: с элементами, укладывающимися в дерево, с произвольными перекрестными ссылками между этими элементами и с неизменяемыми ресурсами, которые могут произвольно разделяться (шарится) между иерархиями объектов. Самый очевидный пример - строки.

Эта структура данных присутствует всюду - в GUI, в каждой модели документа, в каждом сложном иерархическом котроллере, да и само приложение, объединяющее все перечисленное, тоже может быть представленно таким же DOM-ом.

Я поставил себе задачу выяснить как современные языки программирования поддерживают эту модель. Какие современные (или классические) концепции обеспечивают безопасность, эффективность и удобство работы с DOM-образными структурами данных.
Для этого я софрмулировал сравнительный тест (или бенчмарк, хотя некоторым нравится это слово) Card DOM. По ссылке и описание задачи и критерии оценки.

Я уже написал этот тест на JS (и думаю это покывает все GC-языки, хотя я и могу ошибаться). Сейчас делаю то же самое на C++.

И следующим на очереди по идее должен быть Раст, но с ним все как-то не ладится. Для топологически корректного копирования DOM-узлов требуется вести Map(original->copy). Поэтому узлы графа нужно хранить в виде какого-то универсального указателя и потом приводить к известному конуретному типу или трейту. Я не нашел как это безопасно сделать в Расте. Может быть существует какой-то способ? Может быть топологически верная копия делается каким-то другим идиоматичным способом? Я не знаю, я не специалист в Расте. Поэтому я обращаюсь к вам и вашим читателям. Не могли бы выпоказать на примере Card DOM, как Раст справляется с DOM-подобными структурами данных. Это была бы прекрасная демонстрация языка, которая бы одновременно сыграла роль и рекламы и туториала.

у любой программы начиная с некоторого уровня сложности модель данных превращается в Document Object Model - нечто похожее на HTML DOM

Это потому что вы работаете с определенным типом программ - Enterprise Application Software (EAS). Соглашусь что такого софта в реальной жизни (за что платят деньги - что приносит деньги) - основная масса. Как бы самое главное - это бизнес-процессы, это наша жизнь, как бы всем нужно кушать, одеваться, ездить и т.д. - это это все обеспечивается теми самыми бизнес-процессами и соответствующим софтом.

Однако же есть и другие типы приложений, хотя многие за всю жизнь могут с ними не столкнуться ни разу - системные приложения. Так вот Rust заточен больше для системы, так по этому писать EAS вряд ли будет хорошей идеей.

Я пишу драйвер устройства для SoC. В нём используется древовидная структура функций, хранящая конфигурационные параметры. Узлы этого дерева могут ссылаться друг на друга через перекрестные ссылки. Запросы на изменение параметров, а также операции чтения и записи в блочные устройства организованы в очереди; они группируются в бакеты, где возможна перестановка операций при сохранении их взаимозависимостей. Бакеты и батчи также образуют иерархическую структуру. Взаимозависимости между элементами представляют собой перекрестные ссылки. Многочисленные неизменяемые сущности - идентификаторы запросов и транзакций, предопределённые типы операций - являются общими ресурсами, доступными разным частям системы. Вся эта архитектура естественным образом укладывается в DOM-подобную структуру данных.

Перекрёстные ссылки и Rust - взаимоисключающие понятия.

Чтобы сделал любую структуру данных сложнее массива, вам придётся использовать смарт поинтеры. Которые, сюрприз-сюрприз, заимствованы у С++. И это единственное заимствование, которое имеет практический смысл.

это вправду прям большая проблема раста, c это структурой всё не так очевидно, но конечно решаемо

нельзя просто так взять и иметь несколько изменяемых ссылок на один объект, язык этого не позволяет из за правл владения

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

еще можно вместо ссылок использовать индексы. создаешь Vec<Node>, а все связи между узлами выражаешь через обычные индексы этого вектора

создаешь Vec, а все связи между узлами выражаешь через обычные индексы этого вектора

То есть как бы говоришь: "Спасибо, Rust, но мы как-нибудь без тебя" ;)

Отличный матеиал.

Я лично не пишу на Rust, пока не пишу. Но, любопытство к нему в последнее время зашкаливает. Всё-таки я пишу на одном диалекте Rust, недавно появившийся язык шейдеров WGSL. Другие шейдеры пишутся на диалектах С, как GLSL HSLS. Я не понимал за чем надо было всё усложнить, ведь до сих пор тут интуитивный понятный язык GLSL, и тут сразу WGSL который переворачивает всё с ног на голову. И тут уже и интерес не к некоему диалекту Rust а у самому Rust. Всё что я читаю о Rust заставляет переосмысливать то что я пишу на C++. В C++ можно придерживаться дисциплины Rust, но если ошибёшься, компилятор это свободно сжует. Синтаксис Rust кажется несколько неудобным, но я готов с этим мириться.

попробуете сам Rust, многое встанет на свои места. с++ после него уже не тот, так что добро пожаловать ;)

Я обязательно попробую. Я уже начал пробовать.

Пробежать по двусвязному списку и в каждом узле прибавить единичку.
Элементарная задачка на любом языке, кроме Rust.

Все введения в Rust начинаются с рассказа о том, как прекрасная система заимствования помогает писать вам безопасный код, а компилятор, ваш лучший друг, найдёт все ошибки на этапе компиляции.

А про всякие сырые указатели и смарт указали расскажем как-нибудь потом, потому что если вы узнаете про них прямо сейчас, у вас возникнет недоумение, зачем вообще городили этот огород, если всё равно потом всё приходится делать через обёртки вокруг сырых указателей и UnsafeCell.

Порой мне кажется, что Safe Rust не обладает даже полнотой по Тьюрингу.

зачем вообще городили этот огород

Чтобы изолировать и тщательно оттестировать unsafe-код внутри этих самых смарт-указателей и прочих базовых библиотеках и чтобы миллиарды строк высокоуровневой бизнес-логики, написанной на 100% Safe Rust, не могли содержать ошибок в работе с памятью даже теоретически благодаря отсутствию в них unsafe-кода

всё приходится делать через обёртки

Как будто что-то плохое

  1. Пишем unsafe код.

  2. Оборачиваем его в runtime проверки.

  3. Пишем в первой главе руководства, что всё за нас делает компилятор без оверхеда.

  4. ???

  5. PROFIT!

Оборачиваем его в runtime проверки.

И здесь вы начали нести не соответствующую действительности чушь, так что можно не продолжать

Ах да. Там же какая-то магическая сила превращает unsafe в safe всемогущей властью компилятора.

Тем временем, RefCell:

pub fn borrow(&self) -> Ref<'_, T> {
    match self.try_borrow() {
        Ok(b) => b,
        Err(err) => panic_already_mutably_borrowed(err),
    }
}

Код try_borrow даже постить не буду ;)

Я так и думал, что вы узнали про RefCell и решили ошибочно экстраполировать его особенности на весь Rust

Я так и думал, что вы верите в магию, и считаете, что unsafe можно превратить в safe без runtime проверок. И что вы никогда не заглядывали в godbolt, чтобы посмотреть, что там на самом деле получается.

А мне и не надо заглядывать, всё уже сделано до меня — вот, например, Rust успешно выкидывает runtime-проверки границ массива с сохранением безопасности https://habr.com/ru/companies/otus/articles/718012/

Спасибо, что нашли ещё одно подтверждение моих слов ;)

Мы собираемся внести в код два изменения, которые помогут оптимизатору понять

Оптимизатор, если ему подсказать, уберёт ненужные проверки, которые изначально там всё-таки были. А если не подсказать, то не уберёт. Простите, что с плохими новостями, но деда Мороза не существует.

Лучше оптимизировать проверки, которые изначально есть, чем потом ловить Heartbleed'ы по всему миру из-за проверки, которой изначально не было

Вы продолжаете докапываться до каких-то несущественных мелочей, игнорируя суть Rust и фундаментальные проблемы, которые он с некоторым успехом решает

Я просто жду, когда вы возьмёте назад слова

И здесь вы начали нести не соответствующую действительности чушь

и извинитесь ;)

А там нечего забирать — одного контр-примера со способностью компилятора убирать runtime-проверки границ массива уже достаточно, чтобы опровергнуть оказавшееся ложным утверждение (или гипотезу, если угодно) «Оборачиваем его в runtime проверки»

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

Если оптимизатору иногда удаётся их выбросить - отлично, ребята в LLVM хорошо поработали. Что никак не отменяет того факта, что моё высказывание истинно.

Если перестать фокусироваться на несчастном RefCell, а взять хоть тот же условный Vec, проверки там — где надо, где они должны были бы быть и в аналогичном сишном коде тоже. Считать проблемой то, что необходимые runtime-проверки есть — глупо. Игнорировать то, что компилятор способен выкидывать runtime-проверки и обеспечивать отсутствие оверхеда, тем самым опровергая ваши утверждения, — дважды глупо

Только вот Vec - это стандартная структура данных, реализованная в большинстве языков, а RefCell - это чисто растовский хак, нужный только для того, чтобы обойти ограничения языка.
Потому что обычные растовские ссылки ("заимствования") практически бесполезны.

Ещё где-то внизу маленькими буквами пишем: "утечки памяти не считаются UB, разбирайтесь с ними сами, но на сайте продукта всё равно поставьте шилдик Made with Rust"

Собственно с чего бы утечкам памяти быть UB

Тут вопрос скорее в маркетинговой сфере.
Громкие заявления: "У нас крутая система владения и заимствования, которая позволяет управлять памятью без GC."
Реальность: "Утечки ищите и устраняйте сами."

Сделать утечку надо ещё сильно умудриться

Цепляться за один частный случай маловероятной на практике утечки, так же как и цепляться за один частный RefCell, старательно игнорируя многочисленные преимущества языка — как минимум глупо

Для опровержения любой гипотезы достаточно одного контр-примера.
И я его привел. Моя позиция подтверждена конкретным фактом.

Вы же продолжаете демагогию в худших традициях маркетинга и пропаганды.

Какой гипотезы? Контр-примера чему?

Факт возможности утечки никак не отменяет факта наличия системы владения и заимствования, успешно управляющей памятью и не допускающей в Safe Rust использования ещё не инициализированной или уже освобождённой памяти

не допускающей в Safe Rust использования ещё не инициализированной памяти

Hold my beer

use std::mem::MaybeUninit;

// Unsafe Rust
fn hack<T>(x: MaybeUninit<T>) -> T {
    unsafe { x.assume_init() }
}

// Perfectly safe Rust
fn main() {
    let x:MaybeUninit<i32> = MaybeUninit::uninit();
    let y = hack(x);
    println!("{y}");
}

Вы использовали неинициализированную память таки в unsafe-коде, нарушив safety-требование функции assume_init

А рандомное число из памяти я получил в safe коде.
Теперь представьте, что это было не в одном файле:

  • hack в каком-то крейте

  • main у ничего не подозревающего программиста, использующего этот крейт

1) Просто не используйте unsafe-крейты, которым вы не доверяете

2) Если вы всё же хотите или вынуждены их использовать, при получении рандомного числа из ниоткуда вы точно знаете, что виновником может быть только один из unsafe-крейтов и никто другой, что существенно сужает круг подозреваемых и ускоряет отладку — чем Rust и прекрасен в сравнении со всякими сишечками

Как отличить, кто из них safe, а кто unsafe?
Функция не помечена как unsafe.
Опции у cargo install, запрещающей устанавливать "небезопасные" крейты, тоже не вижу.

Из коробки не завезли, но есть cargo-geiger какой-нибудь

Вот это гарантии безопасности!

Всяко лучше чем у сишечек

У сишечек хотя бы никто не врёт про якобы безопасность, поэтому всегда нужно быть начеку. Раст же, не предоставляя никаких гарантий кроме кучи препятствий при написании элементарного кода, громко заявляет о своей безопасности, усыпляя бдительность.

Куча CVE наглядно демонстрирует как там все начеку)

Риторические приёмы и уровень фанатизма всё больше напоминает "в линуксе нет вирусов" ;)

Ага, «в расте есть RefCell и утечки ужас-ужас», «в unsafe-коде можно ошибиться ужас-ужас», «в расте куча runtime-проверок», «борроу-чекер мешает писать код» и прочая классика растосрачей, каким пунктом там cve-rs в методичке?)

Гипотеза: на Rust без использования unsafe (явного или завёрнутого) и без использования runtime проверок невозможно решить задачу из школьного учебника:

Реализуйте функцию, которая проходит по двусвязному списку целых чисел и увеличивает каждое число на 1.

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

Мне никогда не приходилось использовать двусвязные списки в своей практике, так что я не вижу смысла решать задачи, не имеющие практического смысла, нормальный рациональный программист возьмёт Vec и не будет выпендриваться

Обе гипотезы подтвердились: Rust на это не способен, и вы так себе разработчик ;)

Хорошо, а деревья приходилось использовать? Просто скажите, что деревья тоже никому не нужны, и мы закончим этот разговор.

Я правильно понимаю, что вы отрицаете возможность построения safe API поверх unsafe кода?

Без runtime проверок? Отрицаю.

Перечитал тред выше. Я с вами согласен в части:
- без unsafe написать двусвязный список можно, но будут дополнительные runtime проверки из-за реализации через слабые ссылки.

Но что мешает написать (или взять готовую из std) оптимальную реализацию через unsafe?

Без runtime проверок? Отрицаю.

Вы возможно не поняли мой вопрос. Допустим есть оптимальная реализация (как на С) через unsafe без дополнительных runtime проверок.
Вы отрицаете что у нее можно сделать безопасное API?

Сам по себе Weak не решает проблему

fn borrow_mut(&mut self) -> &mut T

Для получения ссылки на мутабельные данные нужна мутабельная ссылка на Weak. Чтобы получить мутабельную ссылку на Weak, нужна мутабельная ссылка на ту структуру, которая содержит Weak (нод списка в нашем случае). А так как список двусвязный, нужны две мутабельные ссылки, а это запрещено. Поэтому без RefCell и внутренней мутабельности не обойтись.

Вы отрицаете что у нее можно сделать безопасное API?

Если небезопасное так легко превращается в безопасное, то зачем вообще тогда нужен Rust? Просто пишем на Си и делаем безопасное API.

P.S. Поиронизирую.
"Безопасное API" в переводе с растовского означает "API, которое запрещает вам его использовать для реальных задач. Если хотите чего-то добиться, работайте с ним через unsafe на свой страх и риск".

Поэтому без RefCell и внутренней мутабельности не обойтись.

Согласен, нужен RefCell, а это еще проверки.

Если небезопасное так легко превращается в безопасное, то зачем вообще тогда нужен Rust? Просто пишем на Си и делаем безопасное API.

А вот тут вы каким-то кривлянием пытаетесь проигнорировать ключевой вопрос:

Вы отрицаете возможность построения safe API поверх unsafe кода?

Который кстати относится не только к Rust, но и к C# например (в нем есть ключевое слово unsafe).

Я хочу услышать простой ответ, да или нет.

Можно. Я выше даже написал рецепт.

  1. Пишем unsafe код.

  2. Оборачиваем его в runtime проверки.

  3. Пишем в первой главе руководства, что всё за нас делает компилятор без оверхеда.

  4. ???

  5. PROFIT!

  1. Пишем unsafe код в стиле С

  2. Не оборачиваем его в runtime проверки, т.к. они не нужны

  3. Выставляем наружу safe API

  4. PROFIT!

Реализацию двусвязного списка я без проблем напишу через unsafe, и ассемблер будет одинаковый с таким же кодом на С. Никаких дополнительных проверок!

Вы долго еще будете изворачиваться и игнорировать ключевой вопрос:

Вы отрицаете возможность построения safe API поверх unsafe кода?

Я вам никогда не смогу ответить на этот вопрос, потому что я ответил уже три раза, и все три раза вы проигнорировали мои ответы. Не собираюсь продолжать играть в "а ты купи слона", пока вы не предоставите код, подтверждающие ваши утверждения.

Конечно же код вы не предоставите, сославшись на какие-нибудь отговорки, хотя реальная причина проста - Rust на это не способен.

P.S.
Просто на всякий случай: вот такое - это не safe API.
Это сокрытие проблем от компилятора и разработчиков.

fn that_is_safe_i_guarantee(...):
   // SAFETY: постарайтесь не передавать аргументы, 
   // которые могут всё сломать
   unsafe { ... }

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

Но я понимаю, что в нем сложно разобраться, по-этому написал сильно упрощенную версию:

use core::ptr::null_mut;

struct LinkedListNode<T> {
    data: T,
    next: *mut LinkedListNode<T>,
    prev: *mut LinkedListNode<T>,
}

pub struct LinkedList<T> {
    head: *mut LinkedListNode<T>,
    tail: *mut LinkedListNode<T>,
}

impl<T> LinkedList<T> {
    pub fn new() -> Self {
        Self {
            head: null_mut(),
            tail: null_mut(),
        }
    }
    
    pub fn push_front(&mut self, data: T) {
        let head = self.head;
        let new_node = Box::new(LinkedListNode {
            data,
            next: head,
            prev: null_mut(),
        });
        self.head = Box::leak(new_node);
        
        if !head.is_null() {
            unsafe {
                (&mut *head).prev = self.head;
            }
        }
    }
    
    pub fn remove_at(&mut self, index: usize) -> Option<T> {
        let mut node = self.head;
        for i in 0..index {
            if !node.is_null() {
                unsafe { node = (&mut *node).next; }
            } else {
                return None;
            }
        }
        
        let node = unsafe { Box::from_raw(node) };
        
        if node.prev.is_null() {
            self.head = node.next;
        } else {
            unsafe { 
                (&mut *node.prev).next = node.next;
                (&mut *node.next).prev = node.prev;
            }
        }
    
        return Some(node.data);
    }
}

Код напрямую перенесен из С в Rust, и я специально использовал только указатели и Box, чтобы можно было легко сравнить с кодом на С.

Этих двух методов достаточно, чтобы понять главную идею:
Если алгоритм написан правильно, то абсолютно без разницы, сколько внутри unsafe. В С и С++ точно так же, только ключевого слова unsafe нету.

Если алгоритм написан правильно, то абсолютно без разницы, сколько внутри unsafe.

Так я о том же говорил: зачем нужны эти пляски с бубном, если можно просто взять и хорошо написать на С?

И ваш код через сырые указатели только подтверждает, что borrow checker нафиг не нужен, только мешает.

Кстати, алгоритм у вас неправильный - tail никогда не обновляется ;)
И, думаю, компилятор к этому никаких претензий не имеет ;)

Да и сделать +1 ко всем элементам списка не выйдет - значение иммутабельное:

struct LinkedListNode<T> {
    data: T,
    ...
}

Ладно, про +1 ошибся ) Отредактировать уже нельзя, пусть мой позор вечно тут хранится.

В алгоритмах не нужен.
А вот в коде, который использует алгоритмы, borrow checker даёт существенные гарантии. Ничего похожего в С и С++ и близко нету.

Принцип такой: алгоритмы пишутся через unsafe опытными программистами, проверяются тестами, санитайзерами, фаззингом и т.д.

А обычный код без unsafe может писать любой джун, и у него никогда не вылезет UB, use after free, double free, разъименование null и т.д. Можно в main.rs еще добавить #[forbid(unsafe_code)] для гарантии.

Кстати, алгоритм у вас неправильный - tail никогда не обновляется ;) И, думаю, компилятор к этому никаких претензий не имеет ;)

Тут поспешил) Компилятор, кстати, выдает warning.

И вам нужно принудить всех разработчиков в вашем проекте этим пользоваться. Это реально.

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

Я же имел ввиду что умеет компилятор, включая его ключи компиляции. В GCC и Clang этого нету. Ну может Профили Безопасности улучшат ситуацию, все же их встраивают в компилятор.

Я вам отвечал на утверждение, что "Ничего похожего в С и С++ и близко нету". Есть и даже лучше, чем предлагается в Rust.

А уж пользоваться этим (или чем-то аналогичным на С++) или же принудить всех разработчиков переходить на Rust, пусть решают сам разработчики.

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

По этому он хуже Rust на ближайшие годы, пока (если) не получит достаточного распространения.

А уж пользоваться этим (или чем-то аналогичным на С++) или же принудить всех разработчиков переходить на Rust, пусть решают сам разработчики.

Лично я рекомендую переходить на Rust только в низкоуровневом коде (микроконтроллеры, драйвера, исходники ОС), и только для нового кода.

Если же писать под ОС, то лучше взять язык со сборщиком мусора, кроме редких случаев выжимания каждого % из процессора.

P.S. Не могли бы вы раскрыть, чем он лучше чем Borrow Checker?

пока не будет его поддержки в существенной части библиотек.

Ему ненужна дополнительная поддержка, так как он оперирует уже существующими классами и возможностями начиная с C++20.

По этому он хуже Rust на ближайшие годы, пока (если) не получит достаточного распространения.

Если бы Rust был действительно выполнял свои маркетинговые лозунги, то неужели вы думаете, что подавляющее большинство (включая меня) не перешло бы на его использование?

Вот только в действительности содержимое не соответствует фантику и меняя язык программирования с С++ на Rust программист не получает обещанную безопасность (не все алгоритмы возможно реализовать на Rust без использования unsafe блоков, а утечки памяти так и вовсе перестали считать за ошибки), но зато отгребает ворох проблем с совместимостью.

Если же писать под ОС, то лучше взять язык со сборщиком мусора, кроме редких случаев выжимания каждого % из процессора.

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

P.S. Не могли бы вы раскрыть, чем он лучше чем Borrow Checker?

Пожалуйста. В концепции Borrow Checker невозможно множественное владение, игнорируются утечки памяти и с его помощью нельзя реализовать возможность блокировки объекта на редактирование в нескольких потоках одновременно.

Тогда как в предложенном варианте тоже реализуется контроль безопасной работы с памятью, но без перечисленных выше недостатков Borrow Checker. И самое главное, обеспечивается полная обратная совместимость с уже существующим С++ кодом.

Вы всё время пытаетесь сравнить голый язык Rust с сборкой из С++ + плагин компилятора + библиотека. Вам самому не кажется это странным?

Давайте сравнивать честно, либо только языки, либо со всеми возможными библиотеками, которые для Rust закрывают все ваши претензии.

Теперь по пунктам:

Ему ненужна дополнительная поддержка, так как он оперирует уже существующими классами и возможностями начиная с C++20.

Чтобы проверять код зависимостей, разве не нужно как минимум в их сборку добавить вызов плагина компилятора? А в заголовочники добавить include "memsafe.h".

Если бы Rust был действительно выполнял свои маркетинговые лозунги, то неужели вы думаете, что подавляющее большинство (включая меня) не перешло бы на его использование?

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

А маркетинговые лозунги почти никогда не соответствуют реальности, для любого языка. Давайте учитывать то, что написано в официальной документации.

Вот только в действительности содержимое не соответствует фантику и меняя язык программирования с С++ на Rust программист не получает обещанную безопасность (не все алгоритмы возможно реализовать на Rust без использования unsafe блоков, а утечки памяти так и вовсе перестали считать за ошибки), но зато отгребает ворох проблем с совместимостью.

Вот именно это мы и обсуждали весь тред выше, потрудитесь перечитать.
Без разницы сколько в алгоритме unsafe, если он написан без ошибок, и проверен соответствующими инструментами.
И есть существенная разница в использовании его Safe API, за которым будет следить компилятор, а не человек.

Пожалуйста. В концепции Borrow Checker невозможно множественное владение

Множественное владение реализуется библиотеками. Эти библиотеки пишутся один раз, и используются множество раз. Как и в С++. Не вижу в этом какой-то проблемы.

игнорируются утечки памяти

Ну надо же, счетчики ссылок делают возможным утечку памяти. Это вы прямо открыли Америку.
В Rust исправляется библиотеками, как и в С++.

и с его помощью нельзя реализовать возможность блокировки объекта на редактирование в нескольких потоках одновременно.

Для этого в языке есть Sync и Send, а в std есть примитивы синхронизации, которые через Sync и Send дают соответствующие гарантии компилятору. Если их недостаточно, то есть куча библиотек на все случаи жизни (1, 2 и т.д.).

Вы всё время пытаетесь сравнить голый язык Rust с сборкой из С++ + плагин компилятора + библиотека. Вам самому не кажется это странным?
Давайте сравнивать честно, либо только языки, либо со всеми возможными библиотеками...
Множественное владение реализуется библиотеками. Эти библиотеки пишутся один раз, и используются множество раз ...

Я так и не понял, что сравниваем, язык или язык + библиотеки?

Без разницы сколько в алгоритме unsafe, если он написан без ошибок, и проверен соответствующими инструментами.

Если программа на C++ написана без ошибок, тогда зачем её переписывать? :-)

Ну надо же, счетчики ссылок делают возможным утечку памяти. ...

Вы не поняли. Проблема не в утечке памяти, а в том, что Rust в угоду собственному маркетингу перестает считать эту ситуацию ошибкой.

Множественное владение реализуется библиотеками. Эти библиотеки пишутся один раз, и используются множество раз. Как и в С++. Не вижу в этом какой-то проблемы.

Для этого в языке есть Sync и Send, а в std есть примитивы синхронизации, которые через Sync и Send дают соответствующие гарантии компилятору. Если их недостаточно, то есть куча библиотек на все случаи жизни ....

А еще вы забыли написать, что при их использовании также возможны ошибки, точно так же как и в С++

В изначальном треде (до вас) сравнивали только язык. Если уж вы ссылаетесь на плагин компилятора + библиотеку, то теперь язык + библиотеки.

Если программа на C++ написана без ошибок, тогда зачем её переписывать? :-)

А где я писал, что её надо переписывать?

Вы не поняли. Проблема не в утечке памяти, а в том, что Rust в угоду собственному маркетингу перестает считать эту ситуацию ошибкой.

Нет, эта статья именно о реализации счетчиков ссылок в std. Утечку памяти через них не возможно проверить на этапе компиляции, о чем честно и пишут. Не вижу тут отличий от голого С++, кроме того, что в документации к С++ эта проблема не упомянута xD. Другие виды утечек памяти в Rust отслеживаются повсеместно засчет RAII.

Если же брать библиотеки, то можно взять любой GC с аренами, и выделять через него счетчики ссылок (и только их). На сколько я понял, в вашей реализации на С++ сильные циклические ссылки запрещены на этапе компиляции. Это ок на этапе освобождения памяти, но налагает накладные расходы в остальное время, которые будут сопоставимы с GC с аренами.

А еще вы забыли написать, что при их использовании также возможны ошибки, точно так же как и в С++

При использовании нет, компилятор не пропустит. Если конечно авторы примитива синхронизации не допустили ошибку.

Вот при разработке примитива синхронизации нужны все те же средства, что и в С или С++: статические анализаторы, санитайзеры, тесты, фаззинг и т.д.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Информация

Сайт
beget.com
Дата регистрации
Дата основания
Численность
201–500 человек
Местоположение
Россия