LINUX.ORG.RU

Rust и обёртка над блокировкой

 ,


0

5

Допустим, у нас есть некая мутабельная структура данных (в данном случае это просто число, но это лишь для простоты примера), которую мы хотим шарить между потоками. При этом мы хотим сделать свою RAII обёртку над блокировкой этой сущности, чтобы предоставить пользователю определённый интерфейс её модификации (а также отслеживать разблокировку и т. п.).

Получается как-то так (лайфтаймы у обёртки от балды, потому что я не знаю как это правильно написать):

use std::sync::{Arc, Mutex, MutexGuard};

pub struct SharedData {
    data: Arc<Mutex<i32>>
}

impl SharedData {
    pub fn new(value: i32) -> Self {
        SharedData {
            data: Arc::new(Mutex::new(value))
        }
    }

    pub fn lock(&self) -> LockedSharedData<'_> {
        LockedSharedData::new(self.data.clone())
    }
}

pub struct LockedSharedData<'a> {
    _data: Arc<Mutex<i32>>,
    guard: MutexGuard<'a, i32>
}

impl<'a> LockedSharedData<'a> {
    fn new(data: Arc<Mutex<i32>>) -> Self {
        LockedSharedData {
            guard: data.lock().unwrap(),
            _data: data
        }
    }

    pub fn get(&self) -> i32 {
        *self.guard
    }

    pub fn inc(&mut self) {
        *self.guard += 1;
    }
}

Оно ожидаемо не компилируется - https://rust.godbolt.org/z/4rEe3fWxK :

error[E0515]: cannot return value referencing function parameter `data`
  --> <source>:26:9
   |
26 | /         LockedSharedData {
27 | |             guard: data.lock().unwrap(),
   | |                    ----------- `data` is borrowed here
28 | |             _data: data
29 | |         }
   | |_________^ returns a value referencing data owned by the current function

error[E0505]: cannot move out of `data` because it is borrowed
  --> <source>:28:20
   |
24 |   impl<'a> LockedSharedData<'a> {
   |        -- lifetime `'a` defined here
25 |       fn new(data: Arc<Mutex<i32>>) -> Self {
26 | /         LockedSharedData {
27 | |             guard: data.lock().unwrap(),
   | |                    ----------- borrow of `data` occurs here
28 | |             _data: data
   | |                    ^^^^ move out of `data` occurs here
29 | |         }
   | |_________- returning this value requires that `data` is borrowed for `'a`

error: aborting due to 2 previous errors

Some errors have detailed explanations: E0505, E0515.
For more information about an error, try `rustc --explain E0505`.
Compiler returned: 1

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

★★★★★

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

Ты не можешь выйти из режима «критика»? Твои ведь слова

Тут проблема не в реализации, а язык плохой by design.

Я показал, что проверять параметры шаблонов можно в самом начале и валиться с коротким и понятным сообщением, а не когда инстанцирование зашло в глубокие кишки и там какой-то трабл с километровой простынёй на выходе при упоминании всей цепи до самого начала. Т.е. проблема именно в реализации. Всё что необходимо - сесть и допилить libstdc++ ограничив входные параметры концептами. Броситься всем для написания своих собственных костылей-оберток я не предлагал (но делать это для интерфейса своих собственных либ - ок). Т.е. твой тезис «язык плохой by design» - рассыпался.

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

Ты либо пытаешься обмануть меня либо самого себя.

Пытаешься обмануть кого-то здесь только ты.

Ошибки borrow checker-а в примере наверху.

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

Там буквально 2 пункта. Где и почему.

Нет - это мусор. Там нет ответов на эти вопросы. Там есть локальная чушь уровня «не смог». А почему оно не смогло сделать то, что должно - меня не интересует.

Ещё раз. Оно не указывается проблемы в коде/логике - оно указывает проблемы своего огрызка, который не смог. Это абсолютно бесполезная информация.

Теперь посмотрим как выглядит типичаня ошибка шблонизатора с++

Почему каждый агигатор кидаешься типичными пропагандистскими штампами?

шблонизатора

Никакой ошибки «шблонизатора» там нет. Опять наврал.

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

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

На что он ссылается? Он ссылается на «строчки», но это чушь. Потому как эти строчки - это трейс. Это не ошибка. Т.е. эти строчки специально туда добавлены.

Далее мы увидим как данный пропагандист в каждом своём лозунге сводит белое к чёрному.

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

Так же, данный пропагандист подменяет понятия. Он берёт сложный пример и мусорный. Я даже не буду касаться того, что те ошибки что он выдаёт за ошибки - вообще не ошибки. Это локальные дыры в его примитивном бч/прочих костылях.

Очевидно, что в примитивном примере(особенно подложном. Вспоминаем - раст не умеет в ошибки. Только в захардкоденные диагностики, чтобы впаривать их пропаганде и обманывать неофитов) ошибка будет проще, но это не означает что одно лучше другого. Чтобы понять разницу - нужно брать одинаковые примеры.

Тут ещё проблема в том, что система типов раста - это мусор. И в принципе невозможно сравнивать это убожество и цпп. Там все примеры заранее сложнее.

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

Приведу реальный пример, а не тот пропагандистский мусор. Для понимания того что такое трейс и почему мусор - это мусор.

use std::ops::Add;

struct Point2d<T> { a: T, b: T}

impl<T: Add<Output=T>> Add for Point2d<T> {
    type Output = Self;
    fn add(self, other: Self) -> Self::Output {
        Self {
            a: self.a + other.a,
            b: self.b + other.b,
        }
    }
}

fn main() {
    let x = Point2d { a: 0, b: 0 } + Point2d { a: 42, b: 42 };
    let y = Point2d { a: (), b: () } + Point2d { a: (), b: () };
}

Возьмём тот же кейс. Где есть какой-то юз внутри. Смотрим на ошибку этого днища:


error[E0369]: cannot add `Point2d<()>` to `Point2d<()>`
  --> <source>:17:38
   |
17 |     let y = Point2d { a: (), b: () } + Point2d { a: (), b: () };
   |             ------------------------ ^ ------------------------ Point2d<()>
   |             |
   |             Point2d<()>

Это реальные ошибки, которые будут на расте. Не враньё, не фейковые диагностики. Эта ошибка является типичным примером мусора, о чём я и говорил выше.

Она говорит «не смогла», Но не говорит почему. А вопрос «почему?» - это то основной вопрос.

Теперь возьмём цпп:


template<typename T> struct Point2d {
  T a, b;
  Point2d operator+(Point2d other) { return {a + other.a, b + other.b}; };
};

struct empty {};

int main() {
    Point2d{0, 0} + Point2d{42, 42};
    Point2d{empty{}, empty{}} + Point2d{empty{}, empty{}};
}

И его ошибку:

<source>: In instantiation of 'Point2d<T> Point2d<T>::operator+(Point2d<T>) [with T = empty]':
<source>:10:57:   required from here
<source>:3:48: error: no match for 'operator+' (operand types are 'empty' and 'empty')
    3 |   Point2d operator+(Point2d other) { return {a + other.a, b + other.b}; };
      |                                              ~~^~~~~~~~~
<source>:3:61: error: no match for 'operator+' (operand types are 'empty' and 'empty')
    3 |   Point2d operator+(Point2d other) { return {a + other.a, b + other.b}; };
      |                                                           ~~^~~~~~~~~
<source>:3:70: error: could not convert '{<expression error>, <expression error>}' from '<brace-enclosed initializer list>' to 'Point2d<empty>'
    3 |   Point2d operator+(Point2d other) { return {a + other.a, b + other.b}; };
      |                                                                      ^
      |                                                                      |
      | 

Мы так же видим мусор уровня «Не смог», но он никому не интересен. Поэтому в качестве ошибки мы получаем причину, а так же полный контекст - как мы её достигли. Точно так же как и в стек-трейсе.

Именно поэтому в цпп ошибки такие, какие есть. Из этого так же следует то, что любой код на расте в сравнении с кодом на цпп - это мусор. Это обусловлено возможностями.

Когда у тебя компилятор выдаёт мусор вместо ошибок, когда твоя система типов - мусор немощный. Ты в принципе не может писать сложный код. Поэтому сравнивать раст и цпп - не имеет смысла. Мы не найдём в среднем равного кода - раст-лапша всегда будет в разы мусорней.

Допустим, в цпп есть полиморфизм ссылок, а в раст-огрызке нет. В цпп есть поддержка полиморфного хешера, а в расте нет. Т.е. хешер в расте - это просто захардкоренная сигнатура, которая принимает что-то как может огрызок и возвращает тоже. В С++ это не так.

При том в цпп никто не запрещает делать такой же мусор, но никто не делает.

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

Ошибка в коде-то в том, что vector нехешируемый

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

А из объяснения компилятора это вообще неочевидно.

Тебе.

Не сообщает в чем именно проблема.

Пытается врать. Там явно сообщается. Сообщается, что оно пытается создать hash в контексте дефолтной инициализации. Но не может и всё остальное валится.

Кстати, удивительно то как меняются показания в течении повествования. Он сообщает «не сообщает» на то, что выдаст раст. Выше я пруф показал.

Смысл как раз таки в том, что он показывает первую ошибку(которую и выдаст раст и которую он называет «не сообщает»), но далее показывает ошибку, которая привела к ошибке. И так далее.

Можно посмотреть ошибку в libc++ здесь В шланге есть всякие атрибутики, которые они не особо юзают. Ну и вложенность лапши меньше.

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

Не сообщает в чем именно проблема. Если не знаешь что искать, то без гуглежа не разберешься.

Сообщает.

Да и вообще, если тебе надо выхлоп компилятора прогонять через sed, что бы в нем разобраться, то очевидно выхлоп компилятора говно.

Нет, опять эти нелепые лозунги пропагандистские. Если зелёный адепт не может прочитать стектрейс в 50 строчек и ему его отпиливают до 10 - следует ли из этого то, что все стектрейсы нужно пилить до 10? А трейсер, который выдаёт больше - говно?

Нет, просто запутался в показаниях. Больше информации всегда лучше, чем меньше. Это определяет то, что реализация не говно - именно по количеству информации.

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

Ну да, остальные языки примитивны в сравнении с цпп. И никуда ничего прогонять ненужно. Про прогон тебе сказали в контексте неофита.

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

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

Нет, ты просто пропагандист, который впаривает своё незнание темы другим. Ну и ангажирован, как обычно.

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

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

Опять какие-то пропагандистские фантазии. Это называется логика, тайплевел логика. То, что у тебя в примитивном огрызке её нет - это не значит, что в ней есть какие-то проблемы.

А причины выбора неочевидны

Тебе, но кого это волнует?

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

Это и есть полиморфизм, это и есть тайплевел.

Одновременно с этим в плюсах нет строгой типизации

Враньё.

и в любой момент может быть неявное приведение типов.

Это фича.

Поэтому вообще не очевидно какая функция/конструтор привела к ошибке компиляции.

Тебе неочевидно. В любом случае здесь пропагандист пытается подменить понятия.

Есть его огрызок на котором невозможно написать что-то, кроме мусора. Далее он берёт цпп и показывает там не-мусор и сравнивает с мусором.

Т.е. сравнивает то, что нельзя сравнивать. Никто не запрещает писать на цпп как на огрызке. Только так никто не делает.

Поэтому все компиляторы выдают такую простыню.

Не поэтому.

Потому что без нее не разобраться.

Пример выше я показал. Пропагандист опять врёт.

Тут проблема не в реализации, а язык плохой by design.

Выше был пример- пропагандист опять врёт.

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

Отдельно разберу пропагандистский штамп вида:

Одновременно с этим в плюсах нет строгой типизации

Здесь можно сразу таких определять в ретрансляторы тупой пропаганды. Уровень невежества там максимальный.

Что такое строгая типизация? Ну в отрыве от того, что это мусорный пропагандистский штамп не имеющий смысла?

Это синоним «немощная типизация». Т.е. пропаганда «мусор» заменила на «строгая». Просто осознай масштабы происходящего.

Строгая типизация - это самый примитивным вид типизации, когда типы сравниваются по strcmp. Подобным свойством обладает любая типизация.

Но далее люди, которые не хотят пользоваться этим мусором - специально усложняют систему типов, добавляя в неё преобразования типов. Даже данная секта обмазывается into/collect, чтобы подражать этим системам типов. Хотя пропагандисты все орали «такого быть не должно».

И так это работает. Вот вроде «мусор сбацанный на лабе» - звучит не круто. А вот если его назвать «строгая», то как звучит?

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

А строгая типизация существует тогда, когда адепты не осилили добавить в язык эти преобразования типов. А всё остальное - сказки.

Но преподносится это так, будто бы строгая типизация это какая-то «лучшая типизация», будто бы они что-то делали, чтобы этого достичь. Нет - там делать ничего ненужно. Это в базе есть.

Это то самое «белое в чёрное». Мы берём «нет преобразований типов» - подменяем понятие, называя это «строгим». И вот вроде «строгая» - это что-то, что может быть, а может нет. Это уже звучит как существование чего-то. Через этот новояз и работают пропагандисты.

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

Тут проблема не в копиляторах, а в том, что в C++ шаблоны построены по принципу «утиной типизации»

Никакой утиной типизации не существует. Это так же пропагандистский штамп. Когда мы говорим про «утиную» типизацию в контексте цпп - мы не говорим про утиную. Мы говорим про свойство утиной.

Ты просто повторил что-то не понимая смысла.

т.е. когда я пишу T, то компилятор не может сразу понять,

Чушь, никакого отношение это свойство к «утиной типизации» не имеет. Даже если убрать оттуда невежество.

удовлетворяет ли U всем требованиям, которые выдвигает T.

Никаких требований нет. Это так же пропагандистский штамп не применимый здесь.

Это такой же подлог как и со «строгой типизацией». «требования» - это стирание типов с заменой их общие типы/аннотаций подтипами.

Компилятору приходится выполнить инстанциирование всех использованных из T методов и уже в процессе инстанциирования проверять все ли нормально (по сути, сперва генерируется новый «исходник», который затем транслируется, хотя это очень и очень грубо).

Опять же, типичное непонимание и ретрансляция пропагандистской чуши.

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

Ладно, я объясню почему T<U> работают так, как работают. Подобное поведение обусловлено полиморфизмом.

Это значит, что T<U> - является полноценным типовым преобразованием. Т.е. структурно T<a> и T<b> могут быть не равны. И в реальности не равны.

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

«утиная» типизация тут вообще не причём. В расте она так же есть. Много где есть. В тех же академических огрызках типа хаскеля тоже можно писать без мусорных аннотаций/костылей.

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

Если же мы уберём из цпп «утиную типизацию» - ничего не изменится.

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

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

Враньё. Очередная ретрансляция каких-то пропагандистских штампов. Никакой «проблемы» нет. Никакую проблему концепты не решают.

Я даже скажу откуда это поверье взялось. Есть люди, которые пишут код. У них есть проблема с ошибками - это всякие enable_if и прочие костыли для создания предикатов. Хотя предикаты нативно есть в цпп, но мы не имеем к ним доступа.

Как следствие все эти enable_if и прочие костыли генерировали в трейс много лишнего мусора из своих кишков. И концепты как механизм описания предикатов решают эту проблему.

Аналогично и по костылям для сериализации sfinae, которые ранее делались через жопу. Хотя язык в этом имеем нативно. Это так же добавили.

А далее есть раст-пропаганда и прочий мусор. Она берёт это и натягивает на свою методичку. После это впаривается подобным персонажам - они несут в массы эту чушь.

Концепты работают как упрощение в ограничении полиморфизма. Нет смысла об этом говорить в контексте раста и прочих огрызков, потому как там нет полиморфизма и нечего ограничивать.

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

Но его не используют по тому, что он пробивает sfinae.

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

Всё что необходимо - сесть и допилить libstdc++

Я приму этот аргумент когда будет альтернативная хорошая libstdc++. В обозримом будущем этого вроде не планируется.

Т.е. твой тезис «язык плохой by design» - рассыпался.

Так нет же. Такой говнокод встречается не только в libstd. Он возможен в любом проекте, потому что компилятор позволяет его писать. Потому что такой говнокод по стандарту валиден. В этом и проблема.

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

Хотя в случае с СТД либой действительно было бы неплохо сделать какой-то флаг, который будет активировать проверки static_asserta’ми в самом начале процесса специализации библиотечных сущностей без детализации до царя гороха.

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

В целом здесь человек, судя по всему, понимает что static_assert нельзя применять. Только это будет ломать логику. Но если мы говорим об раст-пропаганде - там адепты код пишут уровня «мусор» и им можно вырубить.

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

Не. Вот тут как раз ты путаешь. В С++ статическая типизация, но нестрогая. А вот в python как раз таки строгая, хоть и динамическая. Если в питоне ты в функцию котороая работает только с int передаешь float(например range),

Вот, типичная пропагандистская чушь. float/int не имеют отношения к цпп. Это сишные типы с сишной же семантикой.

Про «строгую» «типизацию» я уже сообщил выше. Там уже ясен уровень.

Приведение типов - это фича. А то так да - питон писался на коленке и никакая нормальная система типов там была невозможна. Но в любом случае это чушь, потому как там всё это есть. А про int/float - Это просто перепащивают мусор.

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

Комментировать эту чушь даже смысла не имеет. Она не имеет смысла. Ну float в сике преобразуется в инт - дальше что? В чём проблема? Пропагандист нам не скажет.

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

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

У тебя проблемы с восприятием реальности. Хрюндель слился уже на первых попытках и перестал мне отвечать т.к. понял, что не вывозит. А забанить не получилось.

А то, что он там заспамливает пацанов - дак они просто не особо привычные и добрые.

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

Можно сделать структуру с MutexGuard, но ее лайфтайм должен быть зависим от того Mutex, который заимствуется при вызове lock.

Сейчас такой зависимости нет, и то что существует Arc не важно совершенно, потому что с точки зрения компилятора никакой связи между этим Arc и блокируемым мутексом не существует.

Поле _data таким образом не нужно, поскольку существование памяти будет гарантировано заимстсованием MutexGuard.

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

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

Если нужен гранулярный доступ, то нужна структура данных, которая это поддерживает.

Либо RwLock<HashMap<Mutex<T>>>, которая засчет interior mutability мутексов будет блокироваться целиком только при вставке/удалении, но не при апдейте.

Либо брать вменяемую lock-free реализацию, что примерно всегда лучше.

mersinvald ★★★★★
()
Последнее исправление: mersinvald (всего исправлений: 3)
19 января 2023 г.
Ответ на: комментарий от StoryTeller

Точную модель не скажу, все чеки давно где-то потерялись, но по принципу устройства вот этот очень похож.

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

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

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