Search
Write a publication
Pull to refresh

Comments 16

Одно неконтролируемое чтение данных без полной проверки каждого символа на диапазон unicode (а это просадка производительности) - и такой "оптимальный" код сломается. По сути, это не оптимизация, а вектор атаки. Rust классная технология, но её авторам бы поменьше религиозного фанатизма и побольше бы погружения в реальную практику системного программирования.

Что-то мне подсказывает, что читать без unsafe кусок памяти в структуру/енам у вас не выйдет, это не си. А unsafe подразумевает, что вы знаете, что вы делаете.

Допускаю такое. Но встречный вопрос: проверяется ли в самом деле диапазон значений при чтении char из файла или потока стандартной библиотекой? А как при этом решается вопрос с обратной совместимостью при расширении стандарта unicode, что происходит регулярно?

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

Я не знаю способа прочитать один символ именно как символ. Возможно он существует, но я не в курсе. Я знаю только про str::from _utf8, который все это проверяет и про str::from_utf8_unchecked который не проверяет и он, соответственно, unsafe.
А так юникод вообще вещь странная и я не очень понимаю, что считать символом.
Одну и ту же Ë можно записать и как один символ и как два (диакритический знак и Е). В общем если читать вручную посимвольно, то это верный способ по итогу рано или поздно себе ногу отстрелить.

При чтении из UTF-8 проблемы может и нет, но ведь существует UTF-32, которым тип Char и представлен. И что делать с обновлением стандарта - всё равно не ясно :) Отдельный прикол в том, что даже UTF-32 поддерживает составные символы, ну и про диакритические знаки вы верно заметили.

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

Де-факто стандарт в современном мире это UTF-8. Если про UTF-16 я еще могу сказать, что он там где-то в кишках винды до сих пор популярен, то про использование UTF-32 я вообще ничего не знаю, но возможно потому что у меня область немного другая и это конкретно я не сталкиваюсь.

В теории наверное что-то есть для работы как с UTF-16, так и с UTF-32, но я практически уверен, что там будет либо с проверками, либо unsafe, либо оба варианта рядом.

И в любом случае это не имеет отношения к теме поста, потому что сериализация/десериализация прямого представления в памяти делается только через unsafe. Что подразумевает, что человек в теме происходящего разбирается достаточно хорошо, чтобы сделать это корректно.

существует UTF-32, которым тип Char и представлен.

Где? Если вы имели в виду Rust, то там char - это не UTF-32.

Но встречный вопрос: проверяется ли в самом деле диапазон значений при чтении char из файла или потока стандартной библиотекой?

Не может не проверятся. Char хранит номер кодовой точки, а в файлах записаны дынные в какой-то кодировке (например, utf-8). Это не одно и тоже и библиотека должна сделать перевод.

А как при этом решается вопрос с обратной совместимостью при расширении стандарта unicode, что происходит регулярно?

1114112 (0х110000) — это все кодовые точки, которые когда либо будут в стандарте. Их было столько что 5-ой версии 15 лет назад, что в текущей, 16-ой. Количество точек соответствующих каким-то символам на текущий момент — 150к. Каждый год по паре тысяч добавляют. То есть, при сохранении текущих темпов, пространства хватит лет на 100.

Впрочем, я бы выбрал что-то ближе к концу (0xF0000000), чтобы через 50 лет не переписывать.

Согласен, что странно было выбрать нишу впритык. Но существующие Rust программы это не сломает: все программы собираются всегда статически, если появится стандарт, расширяющий диапазон и что-то использующий там, то ваша программа все равно не сможет это корректно прочитать еще до того, как вы к enum-у перейдете.

Наверно, любое некрайнее (в свободном диапазоне) значение дискриминанта не очень удобно для компилятора, ведь кроме enum Option c одним None, можно объявить сколько угодно полей без данных. Например: None, None2, ... None845 и так далее.

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

Можно начать нумеровать от первого числа вне диапазона типа (в примере от 0x110000) и вплоть до сколько влезет в 4 байта. Если не влезет, то придется живой дискриминант ввести.

Наверное, можно нумеровать и в обратнуую сторону: от 0xFFFFFFFF до 0x110000 ). Не знаю, есть ли хорошая причина выбрать прямой или обратный вариант. Может, тип хранящий enum тоже может поменяться (то есть точка обратного отсчета 0xFFFFFFFF поменяется. А может они просто выбрали один из двух подходящих и им оказался прямой.

P.S. Удивительно, но в типе char есть дырка! :) то есть там может быть не 1_114_111 вариантов, а 1_112_064 (на 2047 меньше). И компилятор ее понимает и принимает во внимание. Моя жизнь не будет прежней :)

Строки в Rust всегда корректные UTF8 последовательности в байтовом представлении именно потому что даныые пролностью проверяются https://github.com/rust-lang/rust/blob/master/library/core/src/str/validations.rs#L126

По сути, это не оптимизация, а вектор атаки

Покажите proof of concept

А как при этом решается вопрос с обратной совместимостью при расширении стандарта unicode, что происходит регулярно?

Подозреваю вы тут что-то путаете или не понимаете как юникод работает.

Диапазоны значений (0 to D7FF16 and E00016 to 10FFFF16) никогда не менялись и не будут меняться как раз для обратной совместимости с UTF-16

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

А как скомпилированный код из стандартной библиотеки узнает об изменении стандарта юникода? Юникод - это не просто стандарт и кодировка, а еще и очень много кода, реализующего вышеперечисленное.

При чтении из UTF-8 проблемы может и нет, но ведь существует UTF-32, которым тип Char и представлен.

Зачем вы галлюцинируете вместо того, чтобы просто открыть доки: https://doc.rust-lang.org/std/primitive.char.html

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

Так и вижу эту картину: конец месяца, подбой итогов, пиво, пицца…

И тут кто-то говорит: ну, коллеги, поскольку буквально всё остальное у нас уже безукоризненно, давайте, наконец, откусим последнюю занозу, отделяющую проект от финального релиза под кодовым названием «The Best Ever». Давайте сэкономим четыре байта. Эти четыре байта — последнее, что стои́т между нами и идеалом.

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

И недостающие 4 байта тоже.

Вышеописанная особенность Rust это скорее даже не оптимизация, а суровая необходимость. В Rust достаточно часто используется Option<&T> для представления опциональных ссылок, вместо какого-то отдельного типа. Так чтобы он весил не больше указателя, Option использует тот факт, что ссылки не могут быть нулевыми и поэтому хранится он в памяти как сырой указатель с интерпретацией нулевого значения как None и остальных значений как Some(&T).

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

Sign up to leave a comment.

Articles