Как стать автором
Обновить

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

На предыдущей неделе union (https://github.com/joshtriplett/rfcs/blob/untagged_union/text/0000-union.md) фигурировали в this week in rust в final comment period.

Мне тоже — меньше ключевых слов, да и такое применение unsafe вполне логично.

А меня вот словосочетание unsafe enum сбивает с толку, не сразу очевидно, что речь идет о сишных union'ах.

Тоже аргумент… с другой стороны, это всё-таки "продвинутая часть языка", то есть к использованию FFI, как мне кажется, подходят уже с определёнными знаниями. Опять же, различий и так хватает. Скажем, repr(packed) против __attribute__((__packed__)) или прагм.

Да там много о чём хорошо написано, но ведь про (не)возможность задавать выравнивание на уровней отдельных полей я ничего упустил?..

Насколько я помню, нет. В RFC часто эту тему муссируют, но в ближайших релизах такого не видно.
В Rust они остаются и в релизе, впрочем, аналогичное поведение можно получить при помощи макроса debug_assert!.

Тут стоит заметить, что в D есть конструкция debug(name) вида:


debug( anyDebugName ) {
    // any code
}

Она вырезает код, если при компиляции не указан соответствующий флаг: --debug=anyDebugName


Аналогично, есть конструкция version(name), позволяющая варьировать даже релизный код в зависимости от платформы, архитектуры, компилятора или опять же вручную заданного условия.


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

Всё же сложение и соединение — разные операции. Хорошо это видно на примере векторов (массивов чисел), которые можно соединять, а можно поэлементно складывать. Но для векторов необходимо и 3 вида умножения (скалярное, поэлементное, векторное), которых зачастую нет.


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

Тут вы путаете объединение и тип-сумму. Суть объединения в том, что одни и те же данные могут представлять из себя разные типы одновременно. Например: масссив байт и число. А вот тип-сумма в D выражается через Algebraic:


Algebraic!(int, string) variant;

variant = 10;

variant.visit!(
    (string s) { writeln( "Hello " , s ); },
    (int i) { writeln( "Hello " , i ); }
);

В Rust существует только один тип строк, которые представляют последовательность UTF-8 байт

Это же крайне не эффективно. Даже длину строки не узнать, не пробежавшись по ней. Та же ситуация со срезами.


let cstring: BTreeMap<_, _> = collect![

Не думаю, что бинарное дерево тут будет эффективней простого массива. Есть ли возможность отобразить перечисление именно на массив?


В Rust это делается через использование структуры-кортежа (tuple struct, перевод)

На самом деле и в D это всего-лишь параметризированная структура:


struct Typedef(T, T init = T.init, string cookie=null)

В Rust используется несколько другой подход: сортировка, как и некоторые другие алгоритмы, реализована для «срезов» (slice), а те контейнеры, для которых это имеет смысл, умеют к ним приводиться.

Всё же, диапазоны в D — более мощная абстракция, чем срезы, которые в D тоже есть.

Тут стоит заметить, что в D есть конструкция debug(name) вида:

В Rust тоже:


if cfg!(debug) {
    ...
}

Всё же сложение и соединение — разные операции.

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


Тут вы путаете объединение и тип-сумму.

Ну не то чтобы путаю. Просто в С есть только "старый добрый" union, да и в С++ тоже, разве что добавляются более удобные обёртки поверх него. И используются они и для того и для другого.

А вот про Algebraic! в D действительно не знал, спасибо.


Это же крайне не эффективно. Даже длину строки не узнать, не пробежавшись по ней.

Длину (в байтах) узнать легко. Нужно ли легко/постоянно узнавать длину в символах — вопрос спорный.

Ну и тут дело в том, что строки в Rust — это немного больше, чем "тупой контейнер", как в том же С++. Тем не менее, если есть необходимость, никто не запрещает нам работать с ними как с контейнером u8/u16/u32, просто это будут уже не строки в терминах языка.


Есть ли возможность отобразить перечисление именно на массив?

Наверное, я не совсем понял, что делается в D в том фрагменте кода. Как именно будет выглядеть результирующий массив?


Всё же, диапазоны в D — более мощная абстракция, чем срезы, которые в D тоже есть.

В расте используются итераторы, которые, несмотря на название (которое у меня вызывает ассоциации, в первую очередь с С++), представляют собой (в общем случае) как раз указатель плюс размер.

Наверное, я не совсем понял, что делается в D в том фрагменте кода. Как именно будет выглядеть результирующий массив?

string[ 3 ] cstring = [ "red", "blue", "green" ];

Перечисления по умолчанию имеют тип int.


В расте используются итераторы, которые, несмотря на название (которое у меня вызывает ассоциации, в первую очередь с С++), представляют собой (в общем случае) как раз указатель плюс размер.

Это классические срезы. Диапазоны же могут быть ленивыми, бесконечными, не иметь произвольного доступа, входными, выходными и тп. То есть срез — это частный случай конечного диапазона с произвольным доступом.

Это классические срезы.

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


Перечисления по умолчанию имеют тип int.

То есть, нужен просто массив строковых представлений перечисления? Думал, что нам надо именно в обе стороны мапить значения. С массивом это ведь не будет работать в общем случае — ведь в enum можно произвольные значения задавать.

В таком случае, как в Rust легко и "из коробки" сделать именно такое не знаю. Зато можно сделать иначе:


#[derive(Debug)]
enum Colors {
    Red,
    Blue,
    Green,
}

println!("{:?}", Colors::Red);

let colors = [Colors::Red, Colors::Blue, Colors::Green];
println!("{:?}", colors);

А именно (автоматически) реализовать трейт Debug. Тогда сможем приводить значения перечисления к строке. Правда если захочется самим задать строковое представление, то придётся реализовывать трейт (Debug или Display) вручную.

На самом деле и в D это всего-лишь параметризированная структура:

Почитал подробнее… пожалуй, в этом подход языков опять различается. Скажем, в Rust для получения "обёрнутого" значения используется всё тоже сопоставление с образцом, а в D имеется ещё один хелпер-шаблон TypedefType. Опять же, tuple struct может использоваться не только для этого.

И правильно я понимаю, что по умолчанию Typedef для одинаковых типов будет использовать и одинаковый cookie, то есть типы будут одинаковы? а

TypedefType позволяет получить оригинальный тип, если вы его не знаете. Если знаете, то можете просто привести к нему. Зачем тут сопоставление с образом?


Правильно:


alias TypeInt1 = Typedef!int;
alias TypeInt2 = Typedef!int;

// The two Typedefs are the same type.
static assert(is(TypeInt1 == TypeInt2));

alias MoneyEuros = Typedef!(float, float.init, "euros");
alias MoneyDollars = Typedef!(float, float.init, "dollars");

// The two Typedefs are _not_ the same type.
static assert(!is(MoneyEuros == MoneyDollars));
Зачем тут сопоставление с образом?

В Rust это общий механизм. Так можно "вытаскивать значения" из структур и кортежей, в том числе и из tuple struct:


struct Handle(i32);

let h = Handle(10);
let Handle(v) = h; // v: i32

Просто так скастовать структуру в произвольный тип не получится.

А, фактически в D тоже используется сопоставление с образом, правда много более многословно:


template TypedefType(T)
{
    static if (is(T : Typedef!Arg, Arg))
        alias TypedefType = Arg;
    else
        alias TypedefType = T;
}
> Но для векторов необходимо и 3 вида умножения (скалярное, поэлементное, векторное)

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

Но, по крайне мере, это более честно и не маскирует проблемы.


Представление строки в виде последовательности wchar_t так же, в общем случае, не позволяет узнать длину строки, не пробежавшись по ней.


Мало того, что wchar_t-строка в Windows является UTF-16, так еще и не очень понятно, что нужно считать длиной строки (например: последовательность U+0438 (и), U+0306 (кратка) визуально выглядит как U+0439 (й)).

Эти проблемы решаются нормализацией и отсутствием суррогатных пар (ucs4).

Дорого по памяти (хотя на linux wchar_t и так 4 байта обычно). И будут торчать length_nfd и length_nfc, чтобы всем хорошо было.


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

НЛО прилетело и опубликовало эту надпись здесь

Вы видели сколько весит icu? 35 MiB, если что. Для некоторых и glibc с частичной поддержкой юникода — уже много. Так что далеко не в каждом окружении оно надо. Да и зачем ваять заново, если есть icu?

НЛО прилетело и опубликовало эту надпись здесь
ваять не надо, надо поддерживать на уровне языка

А реализация в библиотеке языка откуда должна взяться?


чтоб объект «строка юникода» был строкой, а не левым объектом, предоставленным либой с сырым буфером под капотом

А что такое строка юникода? Набор байт в одной из utf (8,16be,16le, 32be, 32le)? Набор codepoint'ов? Набор codepoint'ов в NFC? Может в NFD, NFKC или NFKD? Или набор глифов, если вам надо посчитать сколько позиций на экране занимает конкретная строка? Или заодно поддержку truetype надо втащить, чтобы можно было размер на экране посчитать и при использовании немоноширинного шрифта?


чтоб трансформация по мере надобности происходила, всякое такое

Что такое "трансформация по мере надобности"? Из одной кодировки в другую? Или из внутреннего представления в одной кодировки во внутреннее в другой?


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

И какие части, скажем, icu-charsets (или libicudata.so) можно выкинуть в iconv-like программе? Явно она использует только кодировку текущей локали.


У меня складывается впечатление, что вы просто не знакомы с этим доменом.

НЛО прилетело и опубликовало эту надпись здесь

Я бы не сказал, что лишаем: такие вещи можно выносить в макросы/inline функции/расширения компилятора. Rust в этом плане имеет больше возможностей, чем C: макросы поадекватнее, расширения компилятора есть (и, возможно, API для них даже когда‐нибудь стабилизируют). Другое дело, что выигрыш нескольких тактов обычно не стоит того, чтобы заморачиваться, да и написать макрос, который бы оптимизировался так же хорошо, как и встроенная возможность, сложнее.


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

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

Он вообще обычно идёт в комплекте с glibc. И является довольно тонкой обёрткой над оной.

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

Скорее всего, там есть shortcut для совпадающих входной и выходной кодировок. Очевидно, что такой shortcut пишут либо в самом приложении, либо в библиотеке реализующей перекодировку. Но и то, и другое работает с массивами байт (или аналогом — срезом/итератором/потоком и т. п.). К компилятору и линковщику это отношения не имеет.


Вообще в той части намёк был на то, что программа, а тем более компилятор с линковщиком на этапе сборки не могут знать, поддержка каких кодировок может потребоваться. Только разработчик может знать нужны ли ему полноценные unicode collation, toUpper/toLower вне ASCII-диапазона и т. п.


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

Нет, там она опасна. Файловые системы обычно оперируют строками байт для путей файлов. Иногда развлекаются с case insensitive, но это до добра доводит не всегда: CVE-2014-9390. Нормализация в этом месте даст возможность создать сторонними средствами файл, который невозможно будет открыть/удалить.


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

Определитесь всё же, вы говорите про поддержку юникода в компилятора/интерпретаторе или стандартной библиотеке? Т. к. для компиляторов и интерпретаторов поддержка юникода обычно сводится к "не препятствовать" и предоставлению convinient способов создания необходимых объектов на основе строковых литералов.

НЛО прилетело и опубликовало эту надпись здесь

Если язык строго типизированный, а a и b разных типов (а они должны быть разных типов, если одно UTF8, а другое UTF32), то получаем TypeError. Если a и b представлены как массивы байт, то на выходе, очевидно, получим массив байт, который частично UTF8, частично UTF32. Не слишком полезно, но это именно то, о чём вы просили.


Rust, который здесь обсуждают, строго типизирован. Разные типы под разные строчки можно организовать в любом языке, где пользовательские типы вообще есть; и сделать это на уровне отдельной библиотеки, так что замечание про поддержку на уровне системы типов языка я как‐то не понимаю. Единственная поддержка на уровне языка которая здесь пригодится — это пользовательские строковые константы (не знаю как в Rust, а в C++ такое есть; по наличию макроса println! который выглядит как будто делает разбор строчки на входе могу предположить, что, как минимум, возможно написать макрос utf32nfc!("string literal")).


Имя файла в *nix является ни чем иным как набором байт. Ни о какой нормализации не идёт и речи: как указали при создании, так оно и будет. Нормализация на уровне какой‐либо библиотеки просто выльется в недоступные файлы. В Windows нормализация производится системными функциями ввода‐вывода, так что опять не требует никакой поддержки ни со стороны языка, ни со стороны библиотеки.

Если язык строго типизированный, а a и b разных типов (а они должны быть разных типов, если одно UTF8, а другое UTF32), то получаем TypeError.

Не обязательно. Многие языки позволяют реализовывать операции с разными типами, Rust тут не исключение. Вот упрощённый пример из Rust by Example:


struct Foo;
struct Bar;

#[derive(Debug)]
struct FooBar;

impl ops::Add<Bar> for Foo {
    type Output = FooBar;

    fn add(self, _rhs: Bar) -> FooBar {
        FooBar
    }
}

fn main() {
    println!("Foo + Bar = {:?}", Foo + Bar);
}

Собственно, "сложение строк" как раз реализовано для разных типов: String и &str.

Вообще‐то «строгость типизации» для языков с перегрузкой операторов определяется
тем, что затащили в стандарт, а не тем, что вы можете написать: так Python
считается языком со строгой типизацией потому что стандартные типы не позволяют
сложить число со строкой. Так что ваша перегрузка не показатель. То, что можно
сложить String с &str (но не наоборот) показывает, что язык не абсолютно строгий
(таких вообще, наверное, нет), но, к примеру


fn main() {
    let s = 1u16;
    let s2 = 2u8;
    println!("{:?}", (s + s2));
}

не пройдёт. И, я уверен, если бы в стандартной
библиотеке были типы UTF8String и UTF32NFCString, то их нельзя было бы
складывать — именно потому, что непонятно, что должно быть на выходе — как
в примере выше непонятно, должно ли на выходе получиться uint8 или uint16.
Нестандартную библиотеку, которая позволяет такое можно смело клеймить как не
соответствующую духу языка.

Вообще‐то «строгость типизации» для языков с перегрузкой операторов определяется тем, что затащили в стандарт, а не тем, что вы можете написать
То, что можно сложить String с &str (но не наоборот) показывает, что язык не абсолютно строгий

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

А вот если в функцию ожидающую число можно передать строку, то это другое дело.

Но вообще я не хотел ввязываться в спор о строках и ещё меньше хочу спорить о типизации. Сказать хотел только то, что в Rust никто не запрещает операторам работать с разными типами и это уже делается, в том числе, "в стандарте".

Т.е. практически х3-х4 к потреблению памяти?
Выше по комментариям «высокосветские» беседы знатоков, дополню их комментарием абсолютно незнакомого с этими языками человека: D выглядит намного приятнее, чем Rust. Соблюдается принцип наименьшей неожиданности. Кратко, лаконично, все есть из коробки. Даже что-нибудь написать на нем захотелось, после Go даже как-то повеселее будет.
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории