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 вызов и явно выполняете функцию неконтролируемого чтения, беря на себя ответственность за результат выполнения, так что нет, авторы раста прекрасно понимают практику системного программирования.

UFO landed and left these words here

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

UFO landed and left these words here

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

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

Sign up to leave a comment.

Articles