Обновить
69
Владимир@Googolplex

Software engineer

25
Подписчики
Отправить сообщение
InterruptedException — такого понятия в нативных потоках нет в принципе, поэтому если оно вам нужно, то придётся делать вручную.
OutOfMemoryException — обработка этой ошибки — это прерогатива аллокатора. Дефолтный аллокатор (тот, что в libstd) сейчас паникует (или завершает программу, не помню точно — в любом случае, обработать его нельзя). В 99% случаев в прикладных программах это удовлетворительное поведение. Если вы пишите что-то близкое к железу, то там вы можете реализовать свои способы аллокации памяти (отключив libstd) и самостоятельно обрабатывать такие ошибки.
В зависимости от API, это делается с помощью RAII. Например, вот так:

fn do_work(connection: &mut Connection) -> Result<(), SqlError> {
    let mut txn = connection.begin();
    try!(txn.insert(...));
    try!(txn.update(...));
    ...
    txn.commit()
}


Здесь connection.begin() возвращает объект, у которого в деструкторе транзакция отменяется. Если этот объект выходит из области видимости, то транзакция отменяется. Макрос try!() как раз обеспечит ранний выход из области видимости. Метод commit() «поглощает» объект (принимает по значению и перемещает в себя), выполняя коммит транзакции. Поскольку txn уходит «во внутрь» метода, он не будет уничтожен прямо здесь, и роллбэка не произойдёт.

А как в Rust с управлением памятью? Есть GC?

Собственно, безопасное управление памятью без GC — это одна из ключевых фичей Rust. Если кратко, то управление памятью осуществляется за счёт умных указателей, гарантии которых невозможно нарушить из-за концепций владения и заимствования (например, вы не сможете получить ссылку на внутренность какого-нибудь умного указателя и затем уничтожить сам указатель — компилятор вам не даст). В частности, в стандартной библиотеке есть Rc, который является умным указателем с подсчётом ссылок (и его аналог, Arc, который использует атомарные операции — он медленее, но зато потокобезопасный).

Умного указателя с полноценным GC нету, фактически, только потому, что никто пока что не потрудился его сделать. Как выяснилось на практике, почти всегда без сборки мусора можно обойтись и совершенно не потерять в удобстве.
Большинство движков регулярных выражений, которые я знаю, не меняют исходную строку при замене — они создают новую на основе предыдущей. Не говоря уже о том, что в большинстве современных языков строки неизменяемые. Поэтому проблемы перестроения таблицы не существует.
Я не понимаю, к чему вы ведёте. Напомню, с чего началась дискуссия. Gorthauer87 написал:
ведь если есть символ b, а нужно его заменить на Б, то начнётся веселая чехарда: придется или в лучшем случае все символы за b сдвигать на разницу в длине или же вообще заново выделять память


Я ответил, что в общем случае замена code point'ов один к одному не работает, даже простейшие операции вроде toupper() приводят к изменению общего количества code point'ов.

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

Именно про это я и написал в комментарии, на который вы ответили — такая работа с текстом очень редка, в частности, потому, что в случае юникода «массив символов» в смысле «массив code point'ов» бесполезен практически для любого практического применения.

По поводу регулярок.

В общем случае нужно определиться, с чем работают регулярные выражения. Как правило, они работают над code point'ами (корректность такого подхода в общем случае можно оспорить, ну да ладно). В таком случае есть два варианта — либо переводить строки в UTF-32 внутри движка и работать с фиксированной длиной encoded character'а, либо нужно просто построить таблицу соответствия «номер encoded character'а -> смещение от начала строки в code unit'ах» и работать с ним, что тоже даст O(1) доступ по номеру code point'а в UTF-8.

Кстати, второй вариант отлично подходит для регулярок на графемных кластерах — просто будет номер кластера а не encoded character'а.
Вы понимаете, что ß — это один code point, а SS (его toupper-версия) — это два? Здесь в принципе невозможно получить соответствие 1:1 для code point'ов, вне зависимости от используемых типов.
Я время от времени думаю, почему в Rust не хотят сделать аннотаций «не выделяет память», чтобы как раз помечать функции

На самом деле, кода в Rust ещё использовалась концепция typestate, почти так оно и было. Потом от typestate отказались, потому что она приводит к проблемам с композицей (вот здесь есть ещё информация). Та же самая штука была с pure-функциями (они тоже были, и от них тоже отказались).
А уж индексация какая удобная!

Уже много раз было сказано — «посимвольная» индексация строк, что бы в данном контексте «символ» не обозначал, — это совершенно бессмысленное действие. В 99% случаев строки в программах никогда не индексируются. Там же, где они индексируются, в 99% случаев это должна быть «семантическая» индексация, на уровне графемных кластеров, а в этом случае вы в принципе не сможете обеспечить индексацию за постоянное время, потому что графемные кластеры могут быть произвольной длины, безотносительно используемых кодировок.

Для всех оставшихся случаев (когда вам зачем-то нужна быстрая индексация по code point'ам) — есть UTF-32.
Я не знаю, почему вы решили, что меня, что-то путает. Именно это я и имел в виду — абстрактный символ как понятие отделено от кодировки, а из-за того, что в общем случае преобразования символов не имеют вида «один к одному» (как, например, toupper(ß)=SS), завязываться на то, что в программе каждый символ закодирован фиксированным количеством байт — глупо и неправильно.
По Rust есть целая большая официальная книга, на главу которой про макросы выше привели ссылку. И Rust, кстати, всё-таки по синтаксису гораздо ближе к C, чем Nim или Nemerle.
лень, неорганизованность, и отсутствие паранойи.

Да, по-видимому, всё именно так :)
Ну в Maven Central без подписи загрузить библиотеку невозможно. Да, сами сборочные инструменты подписей не проверяют, но я более чем уверен что в Gradle или SBT использовать соответствующий плагин возможно (или написать самостоятельно, если его ещё нет). Вот, например.
Ну понятно, что воркэраунды есть всегда) Вероятно даже, что в тикле их сделать проще, чем во многих других языках.

В общем, я удовлетворил своё любопытство по поводу тикля, спасибо большое за содержательную дискуссию!
Этот вывод — ошибочный в плане работы с юникодом. В UTF-8 U+10904 представляется четырьмя байтами F0, 90, A4, 84, а EFBFBD — это UTF-8-представление символа U+FFFD (вот этого).

Собственно, это явно написано по той ссылке, которую вы привели:
The reference implementation just replaces any character in the range \U010000 — \U10ffff with \ufffd, but as soon as Tcl has support for characters outside the BMP this range is reserved for exactly that.


Таким образом, тикль официально не поддерживает не-BMP-code point'ы, по крайней мере, на данный момент, как раз из-за своего, скажем так, legacy-внутреннего представления строк. Кроме того, насколько я понимаю, поддержки суррогатных пар тоже нет, поэтому с не-BMP-символами работать вообще нельзя ни в каком виде.

Собственно, вот этот TIP внушает надежду на то, что это всё-таки когда-нибудь будет реализовано.
У меня консоль тоже в UTF-8, но puts оба раза вывел не тот символ, который я записал (вот этот: unicode-table.com/ru/10904).
10904 — это уже шестнадцатиричное число (собственно, я взял символ извне BMP, а 2a90 — внутри BMP). Ваш пример делает не совсем то, что я спросил :)
Да, вы правы, прошу прощения. Я сначала тоже написал «до 4», но потом посмотрел на таблицу в вики (с примерами для разных длин) и исправил на 6, видимо, зря.
Окей, насчёт ASCII я ошибся (меня смутила фраза «but only handles a restricted range of characters» в документации). Видимо, там имеется в виду именно произвольная последовательность байт.

Внутреннее же представление в utf-8 (которое есть default) никоим образом не ущербно. Обыкновенный utf-8 как оно есть везде.

Документация с вами не согласна:
modified UTF-8 (which uses 1 to 3 bytes per character)

тогда как в настоящем UTF-8 на один code point может уйти до 6 байт. Поэтому во внутреннем представлении в любом случае понадобятся суррогатные пары или их аналог. Я попытался посмотреть, как в тикле будет во внутреннем представлении выглядеть символ U+10904, однако у меня не получилось:

proc bin2hex {bin} {
  binary scan $bin H* hex
  return $hex
} 

set txt [здесь должен быть явный символ U+10904, но хабр, похоже, не умеет в юникод :(]
puts "Внутреннее представление: ($txt), ([bin2hex $txt]), длина: [string length $txt]"
set bin [encoding convertto utf-8 $txt]
puts "В UTF-8: ($bin), ([bin2hex $txt]), длина: [string length $txt]"


И txt, и bin, судя по выводу bin2hex, содержат одно и то же, но puts выводит их по-разному (и оба раза криво), и string length для обеих равна 4 (что означает, что это не строки во внутреннем представлении — если бы это было так, то их длина была бы 1, потому что это 1 code point).

И прошу прощения, вероятно, я действительно не понял цель вашего примера. Однако, на мой взгляд, отсутствие строгой типизации (или, по крайней мере, строгих операций для работы с разными типами строк, как в Python 3), может сильно запутать логику происходящего, что, собственно, и произошло здесь в моём случае.
Кстати, а в каких языках будет много кодпойнтов на юнит?

Это некорректный вопрос, на code unit не может быть много code point'ов. Наоборот — может быть. Я больше имел в виду тексты с диакритикой.

Да, для подавляющего большинства операций хватит UTF-8 строк. А всякие адаптивные вещи — это настолько экзотика, что я даже не знаю, где это может пригодиться.

Можно, вероятно, будет придумать такую строку, которая будет всё портить.

Да, примерно это я и имел в виду. Любую строку можно разбить на графемные кластеры, взять длину максимального из них за длину code unit'а, и перекодировать строку так, чтобы каждый кластер занимал фиксированное (не больше максимального) число байт. Но, как мне кажется, это настолько специфический случай, что я даже не могу представить, где это может понадобиться.

В целом же у нас с вами, как я вижу, консенсус)
Почти всё то, что вы описали — это не кодировка, это способ работы с символьными данными. Кодировка — это байтовое представление символьных данных, и какую-то из них всё равно придётся выбрать (либо даже несколько и преобразовывать их по каким-то критериям на лету, как это делает, по-видимому, Tcl, хотя нужность этого весьма сомнительна).

можно иметь априорное знание о графемных кластерах

Нельзя. Графемные кластеры могут быть произвольными. Ограничивать их длину малым количеством code point'ов нельзя — многие валидные тексты в неанглийских языках отсекутся. Большая граница сделает размер code unit'а очень большим (десятки байт на code unit?).

можно хранить указатели или индексы

Это не часть кодировки.

В-третьих, можно перестраивать представление «на лету»

Да, можно, но этим вы не добьётесь того, что любому символу (в смысле графемному кластеру) соответствует фиксированное количество байт.

Просто всё дело в том, что в юникоде нет взаимооднозначного соответствия между абстрактными символами и code point'ами. Более того, нет даже однозначного количества code point'ов, которые требуются для представления абстрактных символов. Любые попытки работать с юникодным текстом в любой кодировке так, как будто каждый символ занимает фиксированное число байт, обречены на провал.

Информация

В рейтинге
Не участвует
Откуда
Santa Clara, California, США
Дата рождения
Зарегистрирован
Активность