LINUX.ORG.RU

Чисто технические причины НЕ любить Rust [holywar mode activated]

 , ,


3

9

Я тут на досуге посидел с ручкой-бумажкой и выписал штук 10 чисто технических причин, почему Rust, как язык, совершенно ни на что не годится и не нужно тратить время на его изучение. Чисто технических - в смысле, не включающих в себя пункты про отсутствие нормальной IDE, малый размер сообщества, и тому подобные. Не знаю, насколько это кому-то интересно, но предлагаю провести эксперимент. Напишите в комментариях свою версию этих пунктов, а потом сравним. Есть серьезные подозрения, что в Rust намного больше недостатков чисто с точки зрения дизайна языка, чем мне сейчас приходит на ум. Но как с любой другой новой технологией, евангелисты сосредоточены исключительно на преимуществах, закрывая глаза на недостатки.


Ответ на: комментарий от loz

Не будет ненужных запятых и точек с запятой, как минимум.

Лишние они или нет - предмет множества споров. Вон некоторым вообще крайне милы begin/end - мол скобки менее читаемы. Всё-таки для среднего разработчика сишный синтаксис ближе.

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

исключения не очевидны... Для джавы это точно так же справедливо

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

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

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

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

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

It is true that one cannot assume that a destructor will run, and hence that forget is not itself unsafe (rather, it is unsafe to write a dtor that must run).

Из комментариев к этой фигне. Это правда что ли? В смысле, официальная позиция? Нельзя полагаться на логику в деструкторе? А как же RAII и всё такое?

DarkEld3r ★★★★★
()

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

Другое дело хрен меня заставишь с Qt или Python куда-то уйти...

I-Love-Microsoft ★★★★★
()
Ответ на: комментарий от tailgunner
(pub fn check_read_write (T R W)
  (:value &T :bytes_hexx &[u8] :read R :write W)
  (where ((T (+ PartialEq fmt:Debug))
          (R (-> (Fn ((&mut &[u8])) (s11n:Result T))))
          (W (Fn (&mut (Vec u8)) &T))))

Примерно так, если я правильно распарсил.

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

Я не понял, о чем говорил Нико. Возможно, о том, что возможность спровоцировать утечку (и невызов drop) с помощью Rc известна и не считается unsound.

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

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

Библиотеки для того и нужны, чтобы кочевать из проекта в проект, не?

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

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

Смотря о каком преимуществе ты говоришь. Зеленые ниточки при правильном использовании превращают твою лапшу из callback'ов в нормальный читабельный код с близкой к нулевой потере производительности.

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

Единственный их минус — оверхед в виде стека. И вот за эти несчастные 16-32 килобайта на соединение ты получаешь:

  • человекочитаемый линейный код
  • гибкость кода повышается на порядки (при появлении новой точки прерывания потока выполнения больше не нужно раздирать код на callback'и и передавать между ними состояние)
  • вменяемые бактрейсы, а не портянки из callback'ов, у которых не видно ни начала, ни конца

К сожалению, с пошаговой отладкой в дебаггере green threads дружат не лучше callback-чной лапши, но кому это нужно в 21-м веке?

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

Но в общем то ручная реализация всё равно будет быстрей.

На сколько быстрее? На 0.001%? Переключение между контекстом функции-итератора и вызывающего его кода — сохранение вершины стека + сохранение/восстановление регистров, которые должны быть неизменны по соглашениям ABI — это очень-очень быстро.

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

Во-первых, это уже не бесплатно, особенно если учитывать кэш. Во-вторых, на нескольких ядрах тебе нужно организовывать mpmc очередь или work stealing, на которой будут висеть потоки ОС, а твои блуждающие зеленые потоки будут каждый раз заполнять кэш заново. Добавь к этому радости NUMA, и поймешь, почему в бенчмарках выше по треду Go практический не масштабируется:

Meanwhile, Go's JSON serialization performance on Peak [40 ядер] is only 1.6x that of i7 [8 ядер]. Even more interesting: Go's database performance is slightly lower on Peak than i7. (Yes, the Go tests use all CPU cores.)

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

Он вообще просил на лиспе, а не на s-exp'ах.

Что такое лисп? Нет такого языка.

слив зощитан

Четыре скобочки взорвали тебе голову? Тогда не открывай реальный код.

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

А что, в Go уже есть зелёные нити? Недавно каждая горутина была обычной нитью.

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

Ну вобще плюс-минус одинаково да, но по-моему бред, что нельзя например писать

<T R W>

вместо

<T, R, W>
loz ★★★★★
()
Ответ на: комментарий от tailgunner

То что я привел выше так же лисп из обширного семейства, и называется RustLispByLoz.

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

Ну вобще плюс-минус одинаково да

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

лисп из обширного семейства, и называется RustLispByLoz

Я понял. Но моя ирония заключалась в том, что этого языка не существует.

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

А почему тогда тот же go на мультипроцессорной конфигурации страшно тормозит

Понятия не имею.

Попробуй ради интереса (с -fno-inline, иначе gcc нечестно поступит с iter2)

#undef _FORTIFY_SOURCE
#include <setjmp.h>
#include <ucontext.h>
#include <signal.h>
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>

volatile int forever = 1;

static void stop(int sig)
{
    forever = 0;
}

static void iter(jmp_buf caller, jmp_buf self, unsigned long *count)
{
    /* bootstrap */
    if (_setjmp(self) == 0)
        _longjmp(caller, 1);
    /* main loop */
    for (;;) {
        if (_setjmp(self) == 0) {
            ++*count;
            _longjmp(caller, 1);
        }
    }
}

void iter2(unsigned long *count)
{
    ++*count;
}

int main()
{
    jmp_buf A, B;
    ucontext_t uc;
    unsigned long count = 0;

    if (_setjmp(A) == 0) {
        getcontext(&uc);
        uc.uc_stack.ss_sp = mmap(NULL, 32768, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        uc.uc_stack.ss_size = 32768;
        uc.uc_link = NULL;
        makecontext(&uc, (void(*)())iter, 3, A, B, &count);
        setcontext(&uc);
    }

    signal(SIGALRM, stop);
    alarm(1);
    while (forever) {
        if (_setjmp(A) == 0)
            _longjmp(B, 1);
    }
    printf("%lu iterations\n", count);
    alarm(1);
    for (forever = 1, count = 0; forever; )
        iter2(&count);
    printf("%lu iterations\n", count);
    return 0;
}

У меня разница составила целый порядок (~38 миллионов итераций через «зеленые jmp_buf'ы» против ~445 миллионов через вызов функции). В итоге получаем ~76 миллионов зеленых свичей в секунду — в 6 раз меньше, чем вызовов iter2. Если сравнить с косвенным виртуальной функции, думаю, разница будет еще меньше.

Соответственно, этот jmp_buf ты можешь пересылать между тредами, и все будет в шоколаде. Скорее всего, go тормозит как раз в этом месте, используя pipe/eventfd для общения между процессами вместо futex/cond_t.

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

Во-первых, это уже не бесплатно, особенно если учитывать кэш

Полегче на поворотах. Кеш решает, когда ты в цикле инкрементируешь счетчик, а когда у тебя легион callback'ов, которые к тому же производят syscall'ы, кеш ведет себя ничуть не лучше, чем при наличии легиона зеленых нитей.

Во-вторых, на нескольких ядрах тебе нужно организовывать mpmc очередь или work stealing

Откуда вы такие беретесь? Такое ощущение, что народ в 2015 наконец прочитал статью о c10k problem и побежал писать сервера с выделенным тредом под accept. Даже в самом запущеном случае числодробильни с головой хватит single producer (network IO) / multiple consumer (worker threads), а в жизни, где 99.9% времени процесс висит в ожидании IO все еще прозаичнее: кто соединение заaccept'ил, тот его и танцует до самого FIN'а. И отлично работает, надо сказать.

Добавь к этому радости NUMA

Это ты сейчас вообще к чему приплел? Во всех бенчмарках треда был ЕДИНСТВЕННЫЙ процессор. Да и вообще, callback-и ты используешь или fiber'ы, принципиально они практически не различаются: меняется пара регистров, меняется вершина стека.

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

Даже в самом запущеном случае числодробильни с головой хватит single producer (network IO) / multiple consumer (worker threads)

Ты не понял, я имел ввиду очередь гринтредов для исполнения на реальных корах, когда их больше одной. Тебе же надо как-то M:N реализовывать? Тут-то проблемы и начинаются.

меняется пара регистров, меняется вершина стека

Гринтред может всплыть на другой физической коре, либо из-за load balancing, либо из-за work stealing. Тогда ему надо заново заполнять кэши, а обращение к памяти, которую он выделил в на предыдущем кванте на другой коре, становятся в пару раз медленнее, спасибо NUMA. Гринтред может не мигрировать между физическими корами, тогда у тебя получается неэфективная загрузка CPU.

Во всех бенчмарках треда был ЕДИНСТВЕННЫЙ процессор

Ты читать не умеешь? http://www.techempower.com/blog/2014/05/01/framework-benchmarks-round-9/ :

Dell R720xd dual-Xeon E5 v2 + 10 GbE
Of course, other variables are at play, such as significantly more HT processor cores--40 versus 8 for our i7 tests--and a different system architecture, NUMA.
Similarly, we expect that some application platforms are better suited for NUMA than others. For example, platforms that use process-level concurrency scaled to a large number of processor cores quite well. For JSON serialization, node.js performance in the Peak environment is about 3.3x that of i7. Meanwhile, Go's JSON serialization performance on Peak is only 1.6x that of i7. Even more interesting: Go's database performance is slightly lower on Peak than i7. (Yes, the Go tests use all CPU cores. Putting aside scaling as CPU cores increase, Go is quicker in the JSON serialization test than node.js in both environments.)

anonymous
()

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

Ты предлагаешь догадаться?

Есть серьезные подозрения

Если есть так и напиши их

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

Тебе же надо как-то M:N реализовывать?

Уточни задачу. Про сетевые сервисы все решается элементарно на уровне ОС: сколько в этом треде заaccept'или соединений, столько fiber'ов и породили. work stealing — это искать приключений там, где их нет, ибо движущая сила зеленки — это миниизация контекст свичей + более простой (кооперативный) планировщик в userspace.

а обращение к памяти, которую он выделил в на предыдущем кванте на другой коре, становятся в пару раз медленнее, спасибо NUMA

Как уже говорилось выше, забуть про work stealing. Во-вторых, pthread_set_affinity_np. В-третьих, если исключить два первых пункта, чем выигрывают классические callback'и при таком раскладе?

Такое ощущение, что ты перечитал статей и пишешь об абстрактном computer science.

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

Мы с тобой точно про одно говорим? Ты заацептил 1к соединений, породил 1к файберов, но у тебя только 32 физические коры. Тебе нужно иметь в юзерсеспейсе многопоточный планировщик, который будет равномерно раскидывать файберы из очереди на коры для исполнения, пресловутый M:N . Тут-то у тебя и появляется mpmc, work stealing, блуждающие между корами файберы (ты не можешь установить файберу аффинити), NUMA, кэши и прочее. Понятно, что это реализуется рантаймом ЯП, и спрятано от программиста за удобными абстракциями «бесплатных» и «легких» зеленых потоков, но платить за это приходится очевидными проблемами с масштабированием. Поэтому в Rust нет зеленых потоков.

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

Тут-то у тебя и появляется mpmc, work stealing, блуждающие между корами файберы (ты не можешь установить файберу аффинити), NUMA, кэши и прочее

а можно подробнее про все эти базворд-проблемы?

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

человекочитаемый линейный код

гибкость кода повышается на порядки (при появлении новой точки прерывания потока выполнения больше не нужно раздирать код на callback'и и передавать между ними состояние)

вменяемые бактрейсы, а не портянки из callback'ов, у которых не видно ни начала, ни конца

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

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

eao197 ★★★★★
()

Их «дженерики» реализованы как шаблоны C++, не к ночи будь помянут: если дженерик используется с десятью разными типами, компилятор генерит десять экземпляров кода. Отсюда следствия:

1) Никакой полиморфной рекурсии — упираемся в бесконечное развёртывание шаблона;

2) AST дженерика запихивается прямо в скомпилированный объектный файл — чтобы можно было использовать с другим типом. Применительно к C++ — это как если бы .o содержал в себе полную копию .h.

Да, список хотелось бы посмотреть.

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

2) AST дженерика запихивается прямо в скомпилированный объектный файл — чтобы можно было использовать с другим типом. Применительно к C++ — это как если бы .o содержал в себе полную копию .h.

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

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

А то ведь банальная попытка печати в stderr может сломать всю красоту нафиг.

Что сломать?

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

Я понятия не имею, о чём мечтают плюсовики. Они могут мечтать о чём угодно. Только от обязанности компилировать весь буст в каждом файле это не избавляет никак; единственное, что пропадает — это синтаксический разбор.

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

Если лично ты не понимаешь термин, это еще не значит, что термин - баззворд. Вот тут, например: http://www.cs.columbia.edu/~aho/cs6998/reports/12-12-11_DeshpandeSponslerWeis... , сами гугловцы используя эти «баззворды» описывают проблемы с рантйамом Go, и как их можно попробовать исправить, добавив еще «баззвордов».

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

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

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

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

И почему это плохо?

Применительно к C++ — это как если бы .o содержал в себе полную копию .h.

Передергиваение. Не говоря о таком забавном факте: https://twitter.com/pcwalton/status/571734807907991552

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

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

Кстати, одна из причин, по которым из Rust убрали green threads - трудность взаимодействия с Си-библиотеками.

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

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

Какие еще могут быть варианты, если нам нужно иметь возможность инстанцировать дженерики типами по значению, а не по ссылке, минимизируя оверхэд? .NET делает практический то же самое, только он jit-ит специализации в рантайме. Java так не делает, но там и полноценных дженериков нет, только сахар вокруг type erasure.

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

C чего ты вообще взял, что без них можно обойтись? В варианте без where - который удобнее для простых условий - запятые нужны:

pub fn check_read_write<
    T: PartialEq + fmt::Debug,
    R: Fn(&mut &[u8]) -> s11n::Result<T>,
    W: Fn(&mut Vec<u8>, &T)
>(value: &T, bytes_hex: &[u8], read: R, write: W)

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

И почему это плохо?

Раздельная компиляция? Не, не слышали.

Передергиваение.

В чём же? Разницы между исходником и AST практически нет.

Не говоря о таком забавном факте

Который тупо бессмысленен, потому что «C++ name mangling rules» не существуют. Речь там идёт всего лишь об одной из реализаций.

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