C++ быстрее и безопаснее Rust, Yandex сделала замеры

    Спойлер: C++ не быстрее и не медленнее и вообще смысл не в этом. Эта статья является продолжением славных традиций развенчания мифов крупных российских компаний о языке Rust. Предыдущая была "Go быстрее Rust, Mail.Ru Group сделала замеры".


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


    В 2019 году я был на конференции C++ CoreHard, слушал доклад Антона antoshkka Полухина о незаменимом C++. По словам Антона, Rust еще молодой, не очень быстрый и вообще не такой безопасный.

    Антон Полухин является представителем России в ISO на международных заседаниях рабочей группы по стандартизации C++, автором нескольких принятых предложений к стандарту языка C++. Антон действительно крутой и авторитетный человек в вопросах по C++. Но доклад содержит несколько серьёзных фактических ошибок в отношении Rust. Давайте их разберём.


    Речь идет об этом докладе с 13:00 по 22:35.



    Оглавление




    Миф №1. Арифметика в Rust ничуть не безопасней C++.


    Для примера сравнения ассемблерного выхлопа Антон взял функцию возведения в квадрат(link:godbolt):


    Rust C++
    pub fn square(num: i32) -> i32 {
        return num * num
    }

    auto square(std::int32_t num) {
        return num * num;
    }

    example::square:
      mov eax, edi
      imul eax, edi
      ret

    square(int): # @square(int)
      mov eax, edi
      imul eax, edi
      ret


    Цитата (13:35):


    Получаем одинаковый ассемблерный выхлоп. Отлично! У нас есть базовая линия. Пока что C++ и Rust выдает одно и то же.

    В самом деле, ассемблерный листинг арифметического умножения в обоих случаях выглядит одинаковым, но это только до поры до времени. Дело в том, что с точки зрения семантики языков, код делает разные вещи. Этот код определяет функции возведения числа в квадрат, но в случае Rust область определения [-2147483648, 2147483647], а в случае C++ это [-46340, 46340]. Как такое может быть? Магия?


    Магические константы -46340 и 46340 — это максимальные по модулю аргументы, квадрат которых умещается в std::int32_t. Все что выше будет давать неопределенное поведение из-за signed overflow. Если не верите мне, послушайте PVS-Studio. И если вы достаточно удачливы, чтобы работать в командах, которые настроили себе CI с проверкой кода на определенное поведение, вы получите такое сообщение:


    runtime error: signed integer overflow: 46341 * 46341 cannot be represented in type 'int'
    runtime error: signed integer overflow: -46341 * -46341 cannot be represented in type 'int'

    В Rust такая ситуация с неопределенным поведением в арифметике невозможна в принципе.


    Давайте послушаем, что об этом думает Антон (13:58):


    Неопределенное поведение заключается в том что у нас тут знаковое число, и компилятор C++ считает что переполнения знаковых чисел не должно происходить в программе. Это неопределенное поведение. За счет этого компилятор C++ делает множество хитрых оптимизаций. В компиляторе Rust'а это задокументированное поведение, но от этого вам легче не станет. Ассемблерный код у вас получается тот же самый. В Rust'е это задокументированное поведение, и при умножении двух больших положительных чисел вы получите отрицательное число, что скорее всего не то, что вы ожидали. При этом за счет того, что они документируют это поведение, Rust теряет возможность делать многие оптимизации. Они у них прям где-то на сайте написаны.

    Я бы почитал, какие оптимизации не умеет Rust, особенно с учётом того, что в основе Rust лежит LLVM — тот же самый бэкенд, что и у Clang. Соответственно, Rust «бесплатно» получил и разделяет с C++ большую часть независящих от языка трансформаций кода и оптимизаций. И хотя в представленном примере мы и получили одинаковый ассемблер, на самом деле, это случайность. Хитрые оптимизации и наличие неопределённого поведения при переполнении знакового в языке C++ могут приводить к веселью и порой порождают такие статьи. Рассмотрим эту статью подробнее.


    Дан код функции, вычисляющей полиномиальный хеш от строки с переполнением int'a:


    unsigned MAX_INT = 2147483647;
    
    int hash_code(std::string x) {
        int h = 13;
        for (unsigned i = 0; i < 3; i++) {
            h += h * 27752 + x[i];
        }
        if (h < 0) h += MAX_INT;
        return h;
    }

    На некоторых строках, в частности, на строке «bye», и только на сервере (что интересно, на своем компьютере все было в порядке) функция возвращала отрицательное число. Но как же так, ведь в случае, если число отрицательное, к нему прибавится MAX_INT и оно должно стать положительным.

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


    Аналогичный код на Rust будет вести себя корректно(link:playground):


    fn hash_code(x: String) -> i32 {
        let mut h = 13i32;
        for i in 0..3 {
            h += h * 27752 + x.as_bytes()[i] as i32;
        }
        if h < 0 {
            h += i32::max_value();
        }
        return h;
    }
    
    fn main() {
        let h = hash_code("bye".to_string());
        println!("hash: {}", h);
    }

    Выполнение этого кода отличается в Debug и Release по понятным причинам, а для унификации поведения можно воспользоваться семейством функций: wrapping*, saturating*, overflowing* и checked*.


    Как видите, документированное поведение и отсутствие неопределённого поведения при переполнении знакового действительно делают жизнь легче.


    Вычисление квадрата числа — это отличный пример того, как можно выстрелить себе в ногу с помощью C++ в трех строчках кода. Зато быстро и с оптимизациями. Если от обращения к неинициализированной памяти ещё можно откреститься вдумчивым взглядом, то проблема с арифметикой в том, что беда может прийти совершенно внезапно и на «голом» арифметическом коде, где ломаться на первый взгляд нечему.



    Миф №2. Плюсы Rust только в анализе времени жизни объектов.


    В качестве примера приводится следующий код(link:godbolt):


    Rust C++
    pub fn foo(max: i32,
               num: i32) -> i32 {
        return max * num
    }
    
    pub fn bar(max: i32,
               num: i32) -> i32 {
        return bar(max, num) *
               bar(max, num)
    }

    auto foo(std::int32_t max,
             std::int32_t num) {
        return max * num;
    }
    
    std::int32_t bar(std::int32_t max,
                     std::int32_t num) {
        return bar(max, num) *
               bar(max, num);
    }

    example::foo:
      mov eax, edi
      imul eax, esi
      ret
    example::bar:
      ret

    foo(int, int): # @foo(int, int)
      mov eax, edi
      imul eax, esi
      ret
    bar(int, int): # @bar(int, int)
      ret


    Антон (15:15):


    Компилятор Rust'а и компилятор C++ скомпилировали оба этих приложения, и функция bar ничего не делает. При этом оба компилятора выдали сообщения-предупреждения, что возможно здесь что-то не то. К чему я все это говорю… Когда вы слышите, что Rust супер замечательный безопасный язык, то его безопасность заключается только в анализе времени жизни объектов, UB — либо документированное поведение, которое вы не очень ожидаете, по-прежнему в нем есть. Компилятор по-прежнему компилирует код, который явно делает какую-то чушь. И-и-и так уж получается.

    Здесь мы наблюдаем бесконечную рекурсию. Опять-таки код компилируется в одинаковый ассемблерный выхлоп, то есть NOP для функции bar как в C++, так и в Rust. Но это баг LLVM.


    Если вывести LLVM IR кода с бесконечной рекурсией, то мы увидим(link:godbolt):


    code
    #[no_mangle]
    pub fn bar(max: i32, num: i32) -> i32 {
        return bar(max, num) * bar(max, num)
    }

    asm
    bar:
        ret

    IR
    define i32 @bar(i32 %max, i32 %num) unnamed_addr #0 !dbg !5 {
    start:
      ret i32 undef, !dbg !8
    }


    ret i32 undef — и есть ошибка, сгенерированная LLVM.


    В самом LLVM бага живет с 2006 года. И это важный вопрос, ведь необходимо иметь возможность пометить бесконечные цикл или рекурсию так, чтобы LLVM не мог оптимизировать это в ноль. К счастью, есть прогресс. В LLVM 6 добавили интринсик llvm.sideeffect, а в 2019 году в rustc был добавлен флаг -Z insert-sideeffect, который добавляет llvm.sideeffect в бесконечные циклы и рекурсии. И бесконечная рекурсия становится действительно бесконечной(link:godbolt). Надеюсь, что в скором времени этот флаг перейдет и в stable rustc по умолчанию.


    В C++ бесконечная рекурсия и цикл без побочных эффектов считаются неопределённым поведением, так что от этой баги LLVM страдают только Rust и C.


    Итак, после того, как мы разобрались с ошибкой LLVM, давайте перейдем к главному заявлению: "его безопасность заключается только в анализе времени жизни объектов". Это заявление ложно, так как безопасное подмножество Rust защищает от ошибок, связанных с многопоточностью, гонками данных и выстрелами по памяти.



    Миф №3. Вызовы функций в Rust бездумно трогают память.


    Антон (16:00):


    Посмотрим более сложные функции. Что с ними делает Rust. Поправили нашу функцию bar и теперь она вызывает функцию foo. Мы видим, что Rust сгенерировал две лишних инструкции: одна инструкция сохраняет что-то в стек, другая инструкция в конце вытаскивает со стека. В C++ этого нету. Rust два раза потрогал память. Как-то уже не очень.

    Вот этот пример(link:godbolt):


    Rust C++
    fn foo(max: i32,
           num: i32) -> i32 {
        return max * num
    }
    
    pub fn bar(max: i32,
               num: i32) -> i32 {
        return foo(max, num) *
               foo(max, num)
    }

    auto foo(std::int32_t max,
             std::int32_t num) {
        return max * num;
    }
    
    std::int32_t bar(std::int32_t max,
                     std::int32_t num) {
        return foo(max, num) *
               foo(max, num);
    }

    example::bar:
        push    rax
        mov     eax, edi
        imul    eax, esi
        jo      .LBB0_1
        imul    eax, eax
        jo      .LBB0_5
        pop     rcx
        ret
    .LBB0_1:
        lea     rdi, [rip + str.0]
        lea     rdx, [rip + .L...]
        jmp     .LBB0_2
    .LBB0_5:
        lea     rdi, [rip + str.0]
        lea     rdx, [rip + .L...]
    .LBB0_2:
        mov     esi, 33
        call    qword ptr [rip +..]
        ud2

    bar(int, int): # @bar(int, int)
        mov     eax, edi
        imul    eax, esi
        jo      .LBB1_3
        imul    eax, eax
        jo      .LBB1_3
        ret
    .LBB1_3:
        ud2


    Вывод ассемблера для Rust длинный, но мы разберемся в причинах такой разницы. В этом примере Антон использует флаги -ftrapv для C++ и -C overflow-checks=on для Rust, чтобы включить проверку на переполнение знаковых. При переполнении C++ прыгает на инструкцию ud2, которая приводит к "Illegal instruction (core dumped)", а Rust прыгает на вызов функции core::panicking::panic, подготовка к которой занимает половину ассемблерного кода. В случае переполнения core::panicking::panic дает нам красивое объяснение падения:


    $ ./signed_overflow 
    thread 'main' panicked at 'attempt to multiply with overflow', signed_overflow.rs:6:12
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

    Так откуда взялись эти "лишние" инструкции, которые трогают память? Соглашение о вызове функции x86-64 требует, чтобы стек был выравнен до 16 байт, инструкция call кладёт 8-байтовый адрес возврата на стек, что ломает выравнивание. Чтобы это исправить, компиляторы кладут всякие инструкции типа push rax. И так делает не только Rust, но и C++(link:godbolt):


    Rust C++
    extern "C" { fn foo(); }
    
    pub fn bar(max: i32,
               num: i32) -> i32 {
        let z = max * num;
        unsafe { foo(); }
        return z
    }

    extern "C" void foo();
    
    std::int32_t bar(std::int32_t max,
                     std::int32_t num) {
        auto z = max * num;
        foo();
        return z;
    }

    example::bar:
        push    rbx
        mov     ebx, edi
        imul    ebx, esi
        jo      .LBB0_2
        call    qword ptr [rip + foo..]
        mov     eax, ebx
        pop     rbx
        ret

    bar(int, int): # @bar(int, int)
        push    rbx
        mov     ebx, edi
        imul    ebx, esi
        jo      .LBB0_1
        call    foo
        mov     eax, ebx
        pop     rbx
        ret


    И C++, и Rust сгенерировали одинаковый выхлоп ассемблера, оба добавили push rbx для выравнивания стека. Q.E.D.


    Самое интересное заключается в том, что именно C++ нуждается в деоптимизации кода путём добавления аргумента -ftrapv, чтобы ловить неопределенное поведение при переполнении знаковых. Выше я уже показал, что Rust будет вести себя корректно даже без флага -C overflow-checks=on, так что можете сравнить сами(link:godbolt) стоимость корректного кода на C++, либо почитайте статью на эту тему. К тому же -ftrapv в gcc сломан с 2008 года.



    Миф №4. Rust медленнее C++.


    Антон (18:10):


    Чуть медленнее Rust плюсов...

    На протяжении всего доклада Антон выбирает примеры, написанные на Rust'е, которые компилируются в чуть больший ассемблер. Не только примеры выше, которые "трогают" память, но и пример на 17:30(link:godbolt):


    Rust C++
    pub struct Stats {
        x: u32,
        y: u32,
        z: u32,
    }
    
    pub fn sum(a: &Stats,
               b: &Stats) -> Stats {
        return Stats {
            x: a.x + b.x,
            y: a.y + b.y,
            z: a.z + b.z
        };
    }

    struct Stats {
        std::uint32_t x,
                      y,
                      z;
    };
    
    auto sum(const Stats& a,
             const Stats& b) {
        return Stats {
            a.x + b.x,
            a.y + b.y,
            a.z + b.z
        };
    }

    example::sum:
      mov ecx, dword ptr [rdx]
      mov r8d, dword ptr [rdx + 4]
      add ecx, dword ptr [rsi]
      add r8d, dword ptr [rsi + 4]
      mov edx, dword ptr [rdx + 8]
      add edx, dword ptr [rsi + 8]
      mov rax, rdi
      mov dword ptr [rdi], ecx
      mov dword ptr [rdi + 4], r8d
      mov dword ptr [rdi + 8], edx
      ret

    sum(Stats const&, Stats const&):
      mov eax, dword ptr [rsi]
      add eax, dword ptr [rdi]
      mov ecx, dword ptr [rsi + 4]
      add ecx, dword ptr [rdi + 4]
      mov edx, dword ptr [rsi + 8]
      add edx, dword ptr [rdi + 8]
      shl rcx, 32
      or rax, rcx
      ret


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


    В 2019 на конференции CppCon был интересный доклад There Are No Zero-cost Abstractions от Chandler Carruth. Вот он там на 17:30 сильно страдает из-за того, что std::unique_ptr стоит дороже сырых указателей (link:godbolt). И чтобы хоть как-то приблизиться к ассемблерному выхлопу кода на сырых указателях ему приходится добавлять noexcept, rvalue ссылки, и использовать std::move. А на Rust всё будет работать без дополнительных усилий. Давайте сравним два кода и ассемблер. В примере на Rust мне пришлось дополнительно извратиться с extern "Rust" и unsafe, чтобы компилятор не заинлайнил вызовы (link:godbolt):


    Rust C++
    extern "Rust" {
        fn bar(k: &i32);
        fn baz(_: Box<i32>);
    }
    
    pub fn foo(mut ptr: Box<i32>) {
        if *ptr > 42 {
            unsafe { bar(&*ptr) };
            *ptr = 42;
        }
        unsafe { baz(ptr) }
    }

    void bar(int* ptr)
        noexcept;
    
    // Takes ownership.
    void baz(std::unique_ptr<int>&& ptr)
        noexcept;
    
    void foo(std::unique_ptr<int>&& ptr) {
        if (*ptr > 42) {
          bar(ptr.get());
          *ptr = 42;
        }
        baz(std::move(ptr));
    }

    example::foo:
        push    rbx
        mov     rbx, rdi
        cmp     dword ptr [rdi], 43
        jl      .LBB0_2
        mov     rdi, rbx
        call    qword ptr [rip + bar..]
        mov     dword ptr [rbx], 42
    .LBB0_2:
        mov     rdi, rbx
        pop     rbx
        jmp     qword ptr [rip + baz..]
    

    foo(std::unique_ptr<...>&&):
        pushq   %rbx
        movq    %rdi, %rbx
        movq    (%rdi), %rdi
        cmpl    $43, (%rdi)
        jl      .LBB0_2
        callq   bar(int*)
        movq    (%rbx), %rax
        movl    $42, (%rax)
    .LBB0_2:
        movq    %rbx, %rdi
        popq    %rbx
        jmp     baz(...)
    


    При меньших трудозатратах Rust генерирует меньше ассемблера. И не нужны подсказки компилятору в виде noexcept, rvalue ссылок и std::move. В сравнениях языков нужны нормальные бенчмарки. Нельзя вытащить понравившийся пример, и утвержать, что один язык медленнее другого.


    В декабре 2019 Rust превосходил по производительности C++ согласно результатам Benchmarks Game. С тех пор C++ немного укрепил свои позиции. Но на таких синтетических бенчмарках языки будут раз за разом обходить друг друга. Я бы не отказался посмотреть нормальные бенчмарки.



    Миф №5. C → С++ — noop, C → Rust — PAIN!!!!!!!


    Антон (18:30):


    Мы берем большое десктопное плюсовое приложение, пытаемся его переписать на Rust и понимаем, что наше большое плюсовое приложение использует сторонние библиотеки. А очень много сторонних библиотек, написанных на си, имеют сишные заголовочные файлы. Из С++ эти заголовочные файлы мы можем брать и использовать, по возможности оборачивая все в более безопасные конструкции. В Rust'е нам придется переписать эти заголовочные файлы либо сгенерировать какой-то программой из сишных заголовочных файлов.

    Вот тут Антон смешал в одну кучу объявление сишных функций и их последующее использование.


    Действительно, объявление сишных функций в Rust требует либо их ручного объявления, либо автоматической генерации, потому что это разные языки программирования. Подробнее можно прочитать в моей статье про бота для Starcraft, либо посмотреть на пример генерации этих оберток.


    К счастью, у языка Rust есть пакетный менеджер cargo, который позволяет один раз сгенерировать объявления и поделиться ими со всем миром. Как вы понимаете, люди делятся не только сырыми объявлениями, но и безопасными и идиоматичными обёртками. На 2020 год в реестре пакетов crates.io находится около 40 000 крейтов.


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


    # Cargo.toml
    [dependencies]
    flate2 = "1.0"

    Всю работу по компиляции и линковке с учетом версий зависимостей cargo выполнит автоматически. Пример с flate2 примечателен тем, что в начале своего существования этот крейт использовал сишную библиотеку miniz, написанную на C, но со временем сообщество переписало сишный код на Rust. И flate2 стал работать быстрее.



    Миф №6. unsafe отключает все проверки Rust.


    Антон (19:14):


    Внутри блока unsafe отключаются все проверки Rust'а, он там ничего не проверяет, и целиком полагается на то, что вы в этом месте написали все правильно.

    Данный пункт является продолжением темы про интеграцию сишных библиотек в Rust'овый код.


    Увы, мнение об отключении всех проверок в unsafe — это типичное заблуждение, потому что в документации к языку Rust сказано, что unsafe позволяет:


    1. Разыменовывать сырой указатель;
    2. Вызывать и объявлять unsafe функции;
    3. Читать или измененять статическую изменяемую переменную;
    4. Реализовывать и объявлять unsafe типаж;
    5. Получать доступ к полям union.

    Ни о каких отключениях всех проверок Rust здесь и речи не идет. Если у вас ошибка с lifetime-ами, то просто добавление unsafe не поможет коду скомпилироваться. Внутри этого блока компилятор продолжает проверять код на соответствие системы типов, отслеживать время жизни переменных, корректность на потокобезопасность и многое-многое другое. Подробнее можно прочитать в статье You can’t "turn off the borrow checker" in Rust.


    К unsafe не стоит относиться как "я делаю, что хочу". Это указание компилятору, что вы берете на себя ответственность за вполне конкретный набор инвариантов, которые компилятор самостоятельно проверить не может. Например, разыменование сырого указателя. Это мы с вами знаем, что сишный malloc возвращает NULL или указатель на аллоцированный кусок неинициализированной памяти, а компилятор Rust об этой семантике ничего не знает. Поэтому для работы с сырым указателем, который вернул, к примеру, malloc, вы должны сказать компилятору: "я знаю, что делаю; я проверил, там не нулл, память правильно выравнена для этого типа данных". Вы берете на себя ответственность за этот указатель в блоке unsafe.



    Миф №7. Rust не поможет с сишными библиотеками.


    Антон (19:25):


    Из десяти ошибок за последний месяц которые я встречал и которые возникали в C++ программах три были вызваны тем, что с сишным методом неправильно работают, где-то забыли освободить память где-то не тот аргумент передали, где-то не проверили на null и передали нулевой указатель. Огромное количество проблем именно в использовании сишного кода. И в этом месте Rust вам никак не поможет. Получается как-то не очень хорошо. Вроде бы у Rust с безопасностью намного лучше, но только мы начинаем использовать сторонние библиотеки, нужно иметь такую же бдительность как в C++.

    По статистике Microsoft, 70% уязвимостей связаны с нарушениями безопасности доступа к памяти и с другими классами ошибок, которые Rust предотвращает ещё на этапе компиляции. Это ошибки, которые физически невозможно совершить в безопасном подмножестве Rust.


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


    И, казалось бы, можно поймать себя на мысли, что если в Rust и в C++ надо следить за корректностью вызовов сишных функций, то Rust ничуть не выигрывает. Но особенностью Rust является возможность разграничения кода на безопасный и потенциально опасный с последующей инкапсуляцией последнего. А если на текущем уровне гарантировать корректность семантики не удаётся, то unsafe надо делегировать вызывающему коду.


    На практике делегация unsafe наверх выглядит вот так:


    // Warning: Calling this method with an out-of-bounds index is undefined behavior.
    unsafe fn unchecked_get_elem_by_index(elems: &[u8], index: usize) -> u8 {
        *elems.get_unchecked(index)
    }

    slice::get_unchecked — это стандартная unsafe функция, которая получает элемент по индексу без проверок индекса на выход за границы. Так как в нашей функции get_elem_by_index мы тоже не проверяем индекс, а передаем его как есть, то наша функция потенциально опасна. И любое обращение к такой функции требует явного указания unsafe(link:playground):


    // Warning: Calling this method with an out-of-bounds index is undefined behavior.
    unsafe fn unchecked_get_elem_by_index(elems: &[u8], index: usize) -> u8 {
        *elems.get_unchecked(index)
    }
    
    fn main() {
        let elems = &[42];
        let elem = unsafe { unchecked_get_elem_by_index(elems, 0) };
        dbg!(elem);
    }

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


    Тем не менее, с помощью этой unsafe функции мы можем построить безопасную версию(link:playground):


    // Warning: Calling this method with an out-of-bounds index is undefined behavior.
    unsafe fn unchecked_get_elem_by_index(elems: &[u8], index: usize) -> u8 {
        *elems.get_unchecked(index)
    }
    
    fn get_elem_by_index(elems: &[u8], index: usize) -> Option<u8> {
        if index < elems.len() {
            let elem = unsafe { unchecked_get_elem_by_index(elems, index) };
            Some(elem)
        } else {
            None
        }
    }
    
    fn main() {
        let elems = &[42];
        let elem = get_elem_by_index(elems, 0);
        dbg!(&elem);
    }

    И эта безопасная версия никогда не выстрелит по памяти, какие бы аргументы вы туда не передали. Если что, я не призываю вас писать подобный код на Rust (есть функция slice::get), я показываю, как можно перейти из unsafe подмножества Rust в безопасное подмножество с сохранением гарантий безопасности. На месте нашей unchecked_get_elem_by_index могла быть аналогичная функция, написанная на C.


    Благодаря межъязыковой LTO вызов сишной функции может быть абсолютно бесплатен:


    C
    #include <stdint.h>
    
    uint32_t mul(uint32_t a, uint32_t b) {
        return a * b;
    }

    Rust
    #[link(name="mul", kind="static")]
    extern {
        fn mul(a: u32, b: u32) -> u32;
    }
    
    #[inline(never)]
    #[no_mangle]
    fn multiply(a: u32, b: u32) -> u32 {
        unsafe { mul(a, b) }
    }
    
    fn main() {
        println!("42*42 = {}!", multiply(42,42));
        println!("87*31 = {}!", multiply(87,31));
    }

    asm
    000000000002eda0 <multiply>:
       2eda0:   89 f8                   mov    %edi,%eax
       2eda2:   0f af c6                imul   %esi,%eax
       2eda5:   c3                      retq


    Я выложил проект с флагами компилятора на гитхаб. Результирующий выхлоп ассемблера аналогичен коду, написанному на чистом C(link:godbolt), но имеет гарантии кода, написанного на Rust.



    Миф №8. Безопасность Rust не доказана.


    Антон (20:38):


    Есть у нас замечательный язык программирования X. Этот язык программирования математически верифицируемый язык программирования. Если вдруг ваше приложение собралось и оно написано этом языке программирования X, то значит математически доказано, что в этом приложении нет ошибок. Звучит круто. Очень. Есть проблема. Мы используем сишные библиотеки, и когда мы их используем из такого языка программирования X, то разумеется все математические доказательства немного отваливаются.

    В 2018 году доказали, что система типов Rust, механизмы заимствования, владения, времён жизни и многопоточности корректны. Так же было доказано, что если мы используем семантически правильный код из библиотек внутри unsafe и смешаем это с синтаксически правильным safe кодом, мы получим семантически правильный код, который не позволяет стрелять по памяти или делать гонки данных.


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


    В качестве практического применения своей модели авторы доказали корректность некоторых примитивов стандартной библиотеки, включая Mutex, RwLock, thread::spawn. А они используют сишные функции. Таким образом, в Rust невозможно случайно расшарить переменную между потоков без примитивов синхронизации; а, используя Mutex из стандартной библиотеки, доступ к переменной всегда будет корректен, несмотря на то, что их реализация опирается на сишные функции. Круто? Круто.



    Заключение


    Объективно обсуждать относительные преимущества того или иного языка сложно, особенно если вам сильно нравится один язык и не нравится другой. Весьма часто новый апологет очередного "новоявленного языка-убийцы C++" делает громкие заявления, не разобравшись толком с C++, за что ожидаемо получает по рукам.


    Однако от признанных экспертов я ожидаю взвешенного освещения ситуации, которое, как минимум, не содержит грубых фактических ошибок.


    Большое спасибо Дмитрию Кашицыну и Алексею Кладову за ревью статьи.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      +18
      Однако от признаных экспертов я ожидаю взвешенного освещения ситуации, которое, как минимум, не содержит грубых фактических ошибок

      И напрасно.
      Эксперты — они потому и эксперты, что узкая специализация.
      Чем больше времени потрачено на что-то одно, тем меньше его на всё остальное.
      Чем больше набито шишек, тем больше предубеждение, что «там» всё еще хуже и нежелание набивать их еще раз. Всяк кулик своё болото хвалит.

      В качестве канонічного примера можно почитать спичи Линуса о C++.
        +22
        Есть такая штука, называется эффект Даннинга Крюгера. И смысл у него в том, что чем больше человек знает, тем более осторожны его суждения в своей и особенно в смежных областях. Хвалить свое болото и быть некомпетентным в чужом — это несколько разные вещи. Линус топит плюсы не потому что они плохи сами по себе, а потому что их пытаются регулярно пихать в ядро, где, по его месту, им не место.

        Я так понял, что Роман выступает не «за» или «против» а за профессионализм и за научный подход.
          +12

          Аргументы Линуса вполне осмысленны, потому что многие плюсовые абстракции не zero cost все же, но это все вполне решаемо адекватным стайл гайдом. Другое дело, что он явно не хочет с этим заморачиваться. Впрочем, не помню, чтобы он хейтил раст в этом же контексте.

            +14
            Другое дело, что он явно не хочет с этим заморачиваться.
            Не то, что «не хочет». Он «не может».

            Подавляющий по объёму (но, конечно, не по важности) объём кода Linux написан людьми, которые имеют слабое представление о C и C++ в прицнипе — они вообще железячники, из просто драйвера нужны, чтобы железяку продать.

            Когда они карго-култят драйвер дёргая куски C кода — результат получается слегка вменяемым и его можно, итерационно, довести до чего-то разумного.

            Когда они чего-то напишут с полным игнорированием Style Guide на C++ — это можно будет только выкинуть и переписать с нуля. Кто будет это делать?
              0

              Очень сильное заявление, особенно, что люди имеют слабое представление о Си. Пример, приведёте из кода?

                +1

                Memory leak в драйвере megaraid. Могли быть устранены в зародыше — при внимательном и вдумчивом написании кода. Либо прогоне kmemleak на рабочей системе. Пришлось патчить самому

                  0

                  Отлично, баги есть, есть откровенный говнокод. Но вы тоже будете обобщать, что среди разработчиков Linux люди не понимают Си.


                  P/S. Я не согласен с утверждением: «людьм, которые имеют слабое представление о Си»Я даже не спорю о их представлениях о С++ или rust или python.

                    0

                    Погодите. Вы просили пример. Я привел. Если бы разработчики этого конкретного драйвера были чуточку "умнее среднего" — проблемы не было. Но она есть. Никаких обобщений я не делал. Вы меня с кем-то перепутали

                      +1

                      Нет, возможно я вас ввёл в заблуждение, но я просил пример не понимания Си. Не баг, а именно не понимание Си.

                        0

                        баг — это следствие (развожу руками):


                        Если бы разработчики этого конкретного драйвера были чуточку "умнее среднего" — проблемы не было

                        и они писали бы корректный код (не синтаксически, а логически)

                  +1
                  Посмотрите на реакцию на любой vendor-драйвер в LKML. Из последнего — дискуссию про exFAT от Samsung можете посмотреть.
                    0

                    Т.е вы мне предлагаете посмотреть дискуссию, где обсуждаются конкретные проблемы драйвера exfat. Но при этом утверждаете, что люди не понимают в Си?

                      +6
                      Потому что когда по неизвестно какому разу обсуждается что нельзя проверять на переполнение с помощью проверки x + 100 < x — то это оно и есть.
                        0
                        почему? Для беззнаковых чисел именно так и можно проверять (если тип не будет расширен до int с unsigned char)
                          0
                          И это тоже нехорошо: получается, что беззнаковая арифметика хуже оптимизируется, чем знаковая, по причине тараканов 40-летней давности.
                            0
                            Ну вот если бы там речь шла только про беззнаковые числа — вопросов бы не было.
                +7
                И смысл у него в том, что чем больше человек знает, тем более осторожны его суждения в своей и особенно в смежных областях
                Нет, всё вообще не так. Оригинальное исследование не утверждает того, чего ему приписывают.

                Начнём с того, что мужика звали не «Даннинг-Крюгер». Это два разных исследователя, их фамилии пишутся через тире с пробелами: «эффект Даннинга — Крюгера».

                Оригинальное исследование 1999 года заключалось в следующем: студентам раздали опросники на разные темы. Также студенты оценили, насколько хорошо они ответили.

                Студентов разбили на квартили по успешности ответов. Получилось что-то такое:



                То есть всё полностью наоборот: чем лучше себя человек оценивал, тем лучше он себя проявил. При этом в среднем первая квартиль пусть и отвечала хуже второй, но оценила себя ниже, вторая — хуже третьей, но и оценила себя ниже третьей, и так далее.

                Да, первая, квартиль отвечала куда хуже, чем ожидала сама. Но это не даёт право в Интернете затыкать рты всем, демонстрирующим уверенность в чём-либо: «У вас Даннинг-Крюгер, азаза».

                Само исследование невнятное: какие-то статистические выкладки с регрессией к среднему. Выше был дан график для опросника по юмору. График для опросника по логике выглядит более непонятно:



                Каких-то больших различий нет: 5—10 процентов. При этом хорошо себя оценили как те, кто плохо ответил, так и лучшие. Получается, что если человек в себе уверен, то это либо профан, либо эксперт?

                Я тут разве что могу заключить, что все люди себя оценивают выше среднего. Вот это интересно.

                В 2006 году исследование повторяли. Результаты совсем невнятные. Люди себя оценивают хуже, если тест был сложный?



                Нет никаких эффектов. Если вы с кем-то не согласны, то нужно опровергать чужую точку зрения.
              +8

              Rust убьёт C++ и, возможно, C в перспективе 10 лет, это уже просматривается сейчас. Даже если Rust проигрывает в каких-то микробенчмарках, это ничего не значит. Миру слишком давно нужен такой язык как Rust, с богатыми. типами, с ручной, но безопасной памятью, с системой пакетов/библиотек, близкий к железу. Единственное, что может помешать Rust, это появление аналогичного по характеристикам языка, с поддержкой от крупного вендора, с более простой семантикой управления памятью.

                +28

                А зачем кому-то переписывать мегатонны существующего кода? Вот в один день бизнес примет решение: мы писали свой софт 10 лет на c++, а давайте потратим ещё 10 лет на переписывание на другой язык, чтобы через 10 лет получить тот же функционал, что у нас есть сегодня, но собирающийся из другого языка.
                Давайте будем немного реалистами.
                Язык хороший. В своей ниже отличный. Но переписывать на него legacy — кому это надо и чем это в деньгах выгодно?

                  –5
                  По-моему, можно «убить» C++ просто начиная новые проекты на Rust, а старые библиотеки оборачивая в безопасный код. Ну и переписывая в них самые проблемные места, возможно
                    +9
                    — Да взять всё и поделить!
                    — Так я и думал…
                    © «Собачье сердце»
                    +1
                    Чтобы избежать будущих CVE репортов. Если legacy кодом никто не пользуется, то можно и не переписывать. Если же это популярный проект, который периодически все еще создает проблемы с багами и безопасностью, то вполне можно. Теже ядра операционок и драйвера — переписать их на Rust очень даже надо.
                      +13
                      Зачем переписывать уже давно работающий код? Кто за это будет платить? Зачем переписывать на новый ЯП и при этом при переписи возможно посадить новую багу (да, это будет не бага памяти, а перепутанный < с >). Уже где-то была ссылка на то, что при переписывании куска Firefox на Rust они допустили ту же багу, что когда-то N лет назад была пофикшена в старом коде. Это ни разу не камень в огород Rust (он то здесь точно не виноват). Это всё проблемы переписывания кода с нуля.

                      Хотите действительно безопасности? Тогда пропагандируйте Idris/Agda/Coq и DeepSpec. Но все мы живём в реальном мире и понимаем, что это на данном этапе развития невозможно :)
                        +13
                        Платить за это будет компания, которой надоело CVE репорты фиксить в своих продуктах. Возьмем какой-нить интел или броадком. В их драйверах тонны уязвимостей находят и большинство так или иначе связаны с ошибками работы с памятью (из последних вон классический выход за границы при парсинге пакетов). Имеет смысл переписать это на языке, который предоставит гарантии защиты от таких ошибок? Я думаю имеет. Уж тем более имеет для потребителей, которые получат продукт выше качеством.

                        Вообще любой продукт, от которого зависит пол мира, очень даже имеет смысл переписать. OpenSSL приводят часто как хороший пример. Был бы он на расте мы, как минимум, избежали бы пачки уязвимостей, которые поставили эти пол мира на уши. Имеет смысл? Я думаю да.

                        И это не пропаганда раста, на котором я даже не пишу. Это просто здравый смысл. Нужно использовать лучший инструмент там, где это важно. Какой-нить игровой движок переписывать на раст? Там это не важно. Драйвер сетевой карты? Очень важно.
                          +8
                          А я считаю, что процесс должен быть итеративен, а не «взять и переписать на совершенно чуждую технологию». А итеративное переписывание в случае Rust получается, только если на либы разбивать.

                          Я бы предпочёл переписывание на С++, на котором также ошибки подобного уровня не допускаются. Не хочешь переполнений — используй арифметику без UB на переполнениях. Не хочешь отрыва конечностей при работе с памятью — без проблем, просто не работай с сырой памятью. Не получается? Оберни в безопасные с как можно более низким оверхедом обёртки.

                          И да, Rust достаточных гарантий не предоставляет. Например, многопоточка. Посмотрите, что происходит при двойном локе мьютекса (происходит unspecified behaviour. А что это такое на самом деле — вы инфы, кроме предположений на гитхабе, не найдёте).
                            +9
                            Не хочешь переполнений — используй арифметику без UB на переполнениях.
                            И где вы в C++ нашли «арифметику без UB на переполнениях»? Или вы при -trapv? Так он во-первых не работает в GCC уже больше 10 лет, а во-вторых при попытке включить его для уже существующего кода в Clang тот начинает «сыпаться». Потому что оказывается что там реально происходят переполнения, просто результат работа никем не используется и потому «всем пофиг».

                            Каждую строчку перепроверить и убедиться что там всё хорошо? Так в этом случае уже и на Rust можно перевести всё.

                            Посмотрите, что происходит при двойном локе мьютекса
                            А что для таких случаев предоставляет C++?
                              +4
                              И где вы в C++ нашли «арифметику без UB на переполнениях»? Или вы при -trapv?

                              Не, я про обычные вещи уровня safe_int
                              А что для таких случаев предоставляет C++?

                              Ровно то же самое, что и Rust. Чёрт пойми что.
                                +1
                                Ровно то же самое, что и Rust. Чёрт пойми что.

                                Не совсем. Гарантируется, что функция не вернёт после второго лока:


                                However, this function will not return on the second call (it might panic or deadlock, for example).

                                Тут скорее platform specific behaviour, которое для большинства популярных платформ означает deadlock, что не нарушает гарантий Раста (т.е. memory safety и отсутствие data races).

                                  +4
                                  Меня не это смущает. А то, что они ссылаются на unspecified behaviour, описания которого нет. Вот и все мои претензии. Описание, которое есть на гитхабе, ещё не является финальным.
                                    0
                                    UB — по определению не может быть описан. Он на то и unspecified.
                                      +5

                                      Unspecified behaviour ≠ undefined behaviour.

                                        +1
                                        Сорян, unspecified behavior — по определению может быть не описан. Как же бесит в С++ вот эта необходимость всегда быть начеку.
                                        0
                                        Но что такое unspecified — должно быть описано. Зачем-то же С++ в Стандарте описывает, что это такое.
                                +12

                                А можно небольшую просьбу от человека, знакомого с C, Rust, но плюсы знающего исключительно на уровне "Си с классами"?


                                В дискуссиях "C++ vs Rust" регулярно используется довод, что в C++ можно делать всё не менее безопасно, чем на Rust, и наверняка хорошо известно, что именно для этого требуется. Скорее всего, и компиляторы / линтеры это должны уметь.


                                Не могли бы вы (в значении "эксперты по языку C++", не обязательно лично Вы) показать пример конфигурации открытого компилятора и/или открытых линтеров, обеспечивающих адекватную степень безопасности? Чтобы была возможность начать проект на C++, включить их в этой конфигурации и быть уверенным, что они дадут мне по рукам, когда я случайно использую malloc вместо make_unique / напишу код с ошибкой инвалидации итератора / сделаю какую-нибудь ещё глупость, про которую я просто не в курсе. Потому что за себя я уверен, что даже если я прочитаю от корки до корки C++ Core Guidelines, как минимум поначалу такие ошибки всё равно будут.


                                Вот, например, clang-tidy --checks=cppcoreguidelines-*,modernize-* — это адекватно, избыточно или недостаточно?

                                  +4
                                  Этого всегда будет недостаточно. Такие проверки в принципе неразрешимы, придётся банить потенциально корректный код (подход Раста как раз). А те, что разрешимы требуют много ресурсов, что делает их нерентабельными (замедлять компиляцию 99.999% корректного кода ради того чтоб найти ошибку в 0.001% мало кто хочет).
                                    +5

                                    Так в том-то и дело, что меня вполне устроит в качестве ответа линтер, который банит потенциально корректный код. Если он скажет "написанный так код потенциально опасен, так нельзя, пользуйтесь абстракцией такой-то" — оно и к лучшему. Если мне действительно будет нужно — найду в документации, как разрешить данное конкретное нарушение правил линтера (а заодно прочитаю, чем именно оно опасно, на что именно нужно проверить код вручную и о чём нужно предупредить комментарием следующего разработчика).


                                    Не устраивает меня ситуация, в которой "разумеется, C++ позволяет всё это безопасно написать, но чтобы быть уверенным хотя бы на 90% в безопасности написанного, обязательно необходим ревьюер с опытом в современном C++ от N лет". Новые проекты в опенсорсе нередко начинаются с одного человека (или маленькой команды), и я не понимаю, как сейчас начать проект на современном C++ без хождения по всем стандартным граблям с нуля.

                                      +9
                                      Из переписки (прошу прощения за лексику):
                                      [Forwarded from Denis]
                                      блин, нашёл тут на работе в своём старом коде
                                      std::vector<_> vec = ...;
                                      auto it = vec.begin(), end = vec.end();
                                      while (it != end) {
                                          if (/* some conditions on `it` */) {
                                              ++it;
                                              continue;
                                          } else {
                                              // ...
                                              it = vec.erase(it);
                                          }
                                      }
                                      

                                      [Forwarded from Denis]
                                      это просто п**дец, как я мог так ошибиться

                                      [Forwarded from Denis]
                                      переписал в иммутабильном стиле, планирую грепнуть и всё нафиг переписать так

                                      [Forwarded from Stanislav Popov]
                                      а в чем проблема?

                                      [Forwarded from Denis]
                                      vec.erase(it) инвалидирует все итераторы, которые распологались «после» it, в том числе и vec.end()

                                      [Forwarded from Denis]
                                      а я его закэшировал какого-то хрена

                                      [Forwarded from Denis]
                                      еб*чие плюсы, и ни один сраный линтер из тех, что мы юзаем, не заметил х**ни :(

                                      [Forwarded from Denis]
                                      у меня щас горит на самом деле, потому что ни clang'овский scan-build, ни cppcheck не находят говна

                                      [Forwarded from Denis]
                                      придётся видимо грепать проект по .erase(

                                      [Forwarded from Denis]
                                      плюсы — дно

                                      [Forwarded from Denis]
                                      даже когда вроде вот знаешь всё х**ню и умеешь их готовить

                                      [Forwarded from Denis]
                                      на секунду отвлёкся/забылся — проиграл
                                        +10
                                        Собственно эта дискуссия показывает — почему Rust-сообщество так резко реагирует на «экспорт небезопаности».

                                        Когда у тебя в проекте куча unsafe — это тревожный признак, но это можно терпеть. Когда ты оборачиваешь unsafe в функцию, которой нужно «правильно» пользоваться — ты возвращаешься в ситуацию «на секунду отвлёкся/забылся — проиграл».

                                        И тогда зачем весь этот «цирк с конями»? Один такой язык у нас уже есть…
                                          +2
                                          Так в расте то при этом будет явно виден небезопастный код, а в с/с++ как я уже приводил пример в трех безобидных строчках UB можно схватить.
                                            +2
                                            Посмотрите на историю с Actix. Там функция, не помечанная unsafe, даёт доступ к внутренностям без проверок. Вот после этого можно и в rust «в трех безобидных строчках UB можно схватить».

                                            Причём автор объяснял что всё в порядке, просто не нужно лезть в потроха, функция не публичная, так что всё Ok.
                                              +2
                                              А, ну если мы тут случайно в лужу наступили, то можно ссать в штаны, все равно уже мокрое? Такая логика?
                                                +7

                                                Именно после этого — можно утверждать, что в Rust — всё на порядок проще с этим. Потому что небезразличный человек, буквально со стороны, — пробежался по ансейфам и нашёл этот баг.
                                                Люди совершают ошибки. Это не случайность, это — норма.
                                                Rust — язык созданный для упрощения борьбы с ошибками и в этом вся его прелесть. Хотя компилятор по прежнему не может думать за человека, но довольно таки большую часть работы он с человека снимает. Остаётся выполнить свою часть.

                                              +7

                                              Фишка в том, что в расте такой код "за забором". И я как-то считал, в СТД (в котором ужас-ужас сколько ансейфа) его значение порядка 0.1% от общей кодовой базы (считал очень грубо, по строчкам). Это значит, что у вас не миллион строк кода где можно "на секунду отвлечь/забыться", а тысяча. А теперь допустим что программист в среднем "забывает/отвлекается" раз на тысячу строк кода. В итоге в одной программе будет тысяча багов из-за этого, а в другой — один.




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

                                                0
                                                Фишка в том, что в расте такой код «за забором».
                                                Когда он за забором, то всё в порядке. А когда у вас вот такое вот:
                                                pub(crate) fn get_mut(&mut self) -> &mut T {
                                                 unsafe { &mut *self.inner.as_ref().get() }
                                                }
                                                


                                                Это реальный код, из реального проекта.

                                                Такими методами я вам могу в любом проекте достичь ультрабезопасности: будет у меня всего две unsafe функции. peek и poke. Каждая в одну строку. И всё. Всё остальное — «офигеть как безопасно».
                                                  +6

                                                  Если на этом проекте был хотя бы 1 ревьювер этот код бы не прошел ревью.


                                                  Такими методами я вам могу в любом проекте достичь ультрабезопасности

                                                  Такими темпами я могу вам в любом коде любую хрень сделать: bool тип в сишарпе которые не true и не false, монаду в хаскелле которая не монада, и всё остальное. Если вы написали unsafe и нагородили ерунды, то это вы виноваты, а не язык.


                                                  И да, то что в огромном проекте как актикс смогли за полчаса найти все места которые могут вызывать УБ это и есть польза от этого unsafe.

                                                    +1

                                                    Кстати, два момента


                                                    1. с этой функцией нет проблем, потому что из &mut self можно возвращать &mut на дочерние элементы
                                                    2. если её нарочно сломать (например, вместо &mut self использовать &self), то мири скажет, что код невалидный и починили бы вы свои ансейфы, дорогие товрищи:

                                                    image

                                                      0

                                                      Почему тогда столько шума было от этой функции?!

                                                        +2

                                                        Посмотрите здесь. UnsafeCell используется вроде бы правильно, но прикол в том что там оно завёрнуто в Rc, и нарушается контракт Rc!


                                                        Кароче уб в расте такое же не очевидное как и в плюсах


                                                        Вот плейграунд c демонстрацией. Я скопировал весь код из actix/cell.rs, чтобы показать уб


                                                        Кстати miri ловит, но естественно только при использовании. Т.е. сама библиотека для мири выглядит вполне ок, а вот её использование через сейф апи приводит к уб (которое мири ловит).

                                                          +2

                                                          Да, согласен, проглядел, в оригинальном исполнении тоже есть ошибка.


                                                          Кароче уб в расте такое же не очевидное как и в плюсах

                                                          Не совсем — мне неизвестен тул, который ловит УБ в плюсах. Есть всякие санитайзеры, но они такие, ненадёжные товарищи. А если закинуть этот код в MIRI то можно получить замечание от машины:


                                                          error: Undefined Behavior: trying to reborrow for SharedReadWrite, but parent tag <4320> does not have an appropriate item in the borrow stack
                                                            --> src/main.rs:50:5
                                                             |
                                                          50 |     v1.push(4);   //v2 mutated through v1
                                                             |     ^^ trying to reborrow for SharedReadWrite, but parent tag <4320> does not have an appropriate item in the borrow stack
                                                             |

                                                          Впрочем, я сторонник не писать unsafe вообще и дать это на откуп популярным библиотекам и std.


                                                          Кстати miri ловит, но естественно только при использовании. Т.е. сама библиотека для мири выглядит вполне ок, а вот её использование через сейф апи приводит к уб (которое мири ловит).

                                                          Не так, скороее МИРИ это интерпретатор который ловит уб который происходит. То есть оно может найти УБ если оно триггерится где-то в программе, но оно не найдет потенциальное УБ которое не эксплуатируется. Ситуация похожа с тестами.

                                                        +4

                                                        В своём проекте подобный саботаж полностью перекрывается #![forbid(unsafe_code)] в корне проекта.

                                                        0

                                                        Я думаю, что проблема с актикс следующая:
                                                        У чела в голове было полуинтуитивное понимание того как это работает и почему оно сейф (оказалось в итоге не полностью правильным). Ему нужно было напрототипировать производителый веб сервер, а доказывать все компилятору лень. Выносить ансейф вверх по стеку в публичное апи, тоже не хочется. В итоге получаем такой говнокод (по меркам раста) как приведен в комментарии выше. Не все же такие фанатики как уважаемый 0xd34df00d, которые пишут доказательство корректности gcd на 200 строк.

                                                          +2

                                                          Просто зачем писать на языке вещи, для которых он подходит слабо, и которые в нём выражаются неидиоматично?


                                                          Неохота ничего доказывать — не берёте раст (или идрис), а берёте С (или хаскель) и решаете задачу. В чём смысл писать сервер на расте с кучей ансейфа (сортировку на идрисе с кучей believe_me) — просто чтобы сказать, «я написал на расте сервер» («я написал на идрисе сортировку»)?


                                                          Тем более, если FFI околобесплатный.

                                                      –1
                                                      как мне кажется, это называется — преждевременная оптимизация. которая корень всех зол.
                                                      и камень в огород с++ => если бы компиляторы умели сами когда можно кешировать .end(), .size() и прочее — никому бы в голову не пришло руками выносить их из цикла. а так — после того как вынос i< vec.size() ускоряет цикл и позволяет его векторизовать — начинаешь на автомате везде это делать. ну и провтыкиваешь в вышеприведенном случае.
                                                        0

                                                        Забавно, но в данном случае виновата как раз недооптимизация. Если бы алгоритм был выбран не квадратичный, а линейный — ошибки бы не было...

                                                          0
                                                          ну а как вы линейно это сделаете? пробежаться по вектору — пометить ненужное и переписать в выходной вектор? памяти *2 как минимум. плюс может быть что кейс с erase() редкий и в хвосте, а так — весь массив переписывать.
                                                            +3

                                                            Надо просто бежать двумя итераторами, а не одним:


                                                            auto i = vec.begin(), j = vec.begin(), end = vec.end();
                                                            for (; j != end; ++j) {
                                                                if (/* some conditions on `j` */) {
                                                                    if (i != j) *i = std::move(*j); // или swap
                                                                    ++i;
                                                                    continue;
                                                                } else {
                                                                    // ...
                                                                }
                                                            }
                                                            vec.erase(i, end);

                                                            Как-то так, если я ничего не напутал.

                                                              +2
                                                              Примерно такая идея, только пишется она так:
                                                              vec.erase(std::remove_if(vec.begin(), vec.end(), [](auto&& j) { /* ... */}), vec.end());
                                                              

                                                              [и ниже это уже написано, оказывается]
                                                        +2
                                                        В С++ хватает подводных камней, но данный пример к ним не относится. Т.к. его можно было избежать, зная устройство вектора. Первый же пример задачек Герба Саттера говорит про инвалидацию итераторов вектора. К тому же следовало использовать лист, если изменение контейнера происходит постоянно. Либо переписать данный кусок с использованием remove и потом в конце вызвать erase
                                                          +4

                                                          На любой баг всегда найдется ответ "а вот в той книжке/той статье/5пункте 6 главы спеки сказано, что вот так делать не надо". Вопрос не в том, что человек так написал, вопрос в том, что кмк это вполне себе могло бы быть ошибкой компиляции. К слову, раст в таком случае выдаст ошибку компиляции, оно и понятно, он ведь не разрешает две мутабельных ссылки, как раз по схожим соображениям.

                                                            +3

                                                            Tmad PsyHaSTe я нормальный и здоровый человек. Я не хочу думать о том, где мне компилятор С++ подсунет очередную жирную свинью. Я хочу сконцентрироваться на решении бизнес задачи. Тупо. Дешево. Сердито. Надежно. Без этих выкрутасов. Пускай меня компилятор страхует от ошибок. А не тупо молчит как партизан.


                                                            Помню, я купил книжку про умные указатели и всю дичь, что с ними вытворяли . Это был далекий 2000. Я просто тащился с того, что эти ребята с Саттером, Александреску творили. Но тащить это в прод. За ради чего ?

                                                              +5

                                                              Вы так меня меншените, как будто я с вами не согласен)

                                                              +1
                                                              Проблема в том, что запрет нельзя превратить в предупреждение, и если в этом примере код действительно приведёт к ошибке, то в тысяче других может быть совершенно валидный код, который просто нельзя скомпилировать.
                                                                +6

                                                                В Rust две мутабельные (исключительные) ссылки на одно и то же — не могут быть валидным кодом. И попытка их создать — это всегда UB. Поэтому нет смысла и в ослаблении ошибки до предупреждения.

                                                                  –6
                                                                  Собственно, я и указал на это, как на проблему Rust.
                                                                    +4

                                                                    Тогда, получается, невозможность присвоить строку целочисленной переменной — это проблема С++? Это же совершенно валидный код, только не компилируется :-)

                                                                      –5
                                                                      В С++ можно присвоить строку целочисленной переменной, если определить соответствующее неявное преобразование. Практика, конечно, так себе, но есть методы и функции, которые не дают какого-то значного оверхеда в плане удобства. А Rust со своим ограничением на мутабельные ссылки будто бы не предлагает пристегнуть ремень, а требует замотаться в поролон и залезть в ёмкость с каким-нибудь гелем. Я всеми руками за безопасность, но имхо это — перебор.
                                                                        +4

                                                                        Ну так и в расте можно — если сделать дереф в какой-нибудь Rc, и практика тоже так себе.


                                                                        Я всеми руками за безопасность, но имхо это — перебор.

                                                                        Во-первых нужно понять что &mut это не "мутабельный", а уникальный. Да, надо было изначально нормально назвать, были пропозалы чтобы это переделать, но оставили как есть в угоду обратной совместимости. По понятным причинам, уникальная ссылка должна быть уникальной.


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

                                                                          0

                                                                          Уточнение: только не в Rc, а в Cell

                                                                            –5
                                                                            > Ну так и в расте можно — если сделать дереф в какой-нибудь Rc, и практика тоже так себе.

                                                                            Я просто хочу получить ссылку, а не использовать новые абстракции.

                                                                            > Во-первых нужно понять что &mut это не «мутабельный», а уникальный.

                                                                            И что даст это понимание?

                                                                            > В хаскелле вон вообще никакой мутабельности нет

                                                                            Хаскелль — функциональщина со сборщиком мусора. Rust же претендует на другую нишу.
                                                                              +3

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

                                                                                –1
                                                                                Преобразование можно написать один раз и забыть, хотя мне не понятно зачем вообще такое может понадобиться.
                                                                                +1
                                                                                И что даст это понимание?

                                                                                Понимание того, почему Cell, RefCell, мьютекс и атомарные типы можно мутировать по "неизменяемой" ссылке и при этом всё в порядке и никакие гарантии не нарушаются.

                                                                                  –1
                                                                                  Вы будто игнорируете то, что я пишу. Лично мне в данном случае предпочтительнее получить простоту использования, чем гарантии, основанные на бутерброде из абстракций.
                                                                                    +2
                                                                                    В С++ можно присвоить строку целочисленной переменной, если определить соответствующее неявное преобразование.

                                                                                    В Rust тоже можно так сделать через явные преобразования. Напишите мне код на C++, дам вам код на Rust.

                                                                                      0

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

                                                                                        0
                                                                                        Вы имели в виду из разных потоков? Да, бывает и такое. Переменная может быть синхронизирована другими способами, или обращение к ней происходит только специальным образом, который гарантирует защиту от гонки.
                                                                                          +3

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

                                                                                            –5
                                                                                            Ага, все они завязаны на блокировке, если тип данных не атомарный. У меня, к примеру, написанный lock-free контейнер, обращение к которому не требует синхронизации. Или же умный планировщик, который даёт соотстветствующие гарантии. Зачем мне эти ваши абстракции?
                                                                                              +1

                                                                                              А умный лок-фри контейнер это не абстракция?

                                                                                                –2
                                                                                                Это абстракция, которая мне нужна. Зачем мне к ней добавлять ещё Rc<RefCell<...> >, к примеру?
                                                                                                  +2

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

                                                                                                +9
                                                                                                Знаете, я даже не знаю: троллинг это или стёб. У нас в проекте тоже есть пара lock-free контейнеров. Так там на каждую строчку несколько часов убито консультаций с мануалами и специалистами по архитектуре процессоров, чтобы быть уверенными в том, что там ни компилятор, ни железка нам ничего не испортят (если вы читали соотвествующую статью, то знаете, что математически правильный lock-free алгоритм без соотвествующих барьеров = испорченные данные). Вбухав в небольшой кусок кода такие ресурсы уж навесить несколько дополнительных пометок для компилятора — точно не проблема.
                                                                                                  –3
                                                                                                  Кейс, описанный в этой статье — единственный тип реордера, который может произойти на x86. У Вас найдется реальный код, в котором этот реордер создает проблемы?
                                                                                                  Centimo, на мой взгляд, указывал на то, что в случае lock-free программирования подстроиться под гарантии Rust'а не получится — придется все писать на unsafe'е. Но это всего лишь моя интерпретация, конечно.
                                                                                                    0

                                                                                                    Вы не правы. Вот например https://github.com/xacrimon/dashmap отличный пример как lock-free структура данных спокойно живет с гарантиями Rust. Реализовано это через внутренюю мутабельность и имея immutable ссылку на непосредственно словарь (обычно разделяемую через Arc) можно удобно обращаться к ней из многих потоков. Понятно что внутри реализации есть определенный процент магии атомик переменных и страшных вещей, но при этом внешний интерфейс простой и удобный для прикладного программиста

                                                                                                      0
                                                                                                      Я понимаю что внешний интерфейс будет нормальным, но внутреннюю реализацию Rust никак не защитит. Но да, «все писать на unsafe'е» — прозвучало слишком сильно.
                                                                                                        –3
                                                                                                        с гарантиями Rust
                                                                                                        с типичными гарантиями, я бы сказал =)
                                                                                                        github.com/xacrimon/dashmap/blob/master/src/iter.rs:238
                                                                                                        unsafe {
                                                                                                                                let k = util::change_lifetime_const(k);
                                                                                                                                let v = &mut *v.as_ptr();
                                                                                                                                return Some(RefMutMulti::new(guard, k, v));
                                                                                                                            }
                                                                                                        ...
                                                                                                                    let mut guard = unsafe { self.map._yield_write_shard(self.shard_i) };
                                                                                                                    let sref: &mut HashMap<K, V, S> = unsafe { util::change_lifetime_mut(&mut *guard) };
                                                                                            +5
                                                                                            Ну так и пишите на C и не морочьте голову ни себе, ни тем, кто знает, зачем и почему в Rust есть запрет на существование двух мутабельных ссылок сразу)
                                                                        +3
                                                                        Извините, но человек, который это написал, ещё не до конца научился готовить С++. Меня в три часа ночи разбуди — не поднимется рука так написать. Я уже молчу, что специально для этого же есть std::remove_if и remove-erase idiom, зачем вообще писать так сложно и с ошибками этот код, который делается в одну строчку на стандартных алгоритмах из ???
                                                                        vec.erase(std::remove_if(vec.begin(), vec.end(), [](auto&& item){return !some_condition(item);}), v.end());

                                                                        Я ещё в свою библиотеку велосипедов-обёрток над std добавил шаблон remove_if(container, predicate), чтоб не писать begin()/end() (и не писать end() два раза). Думаю, с появлением концептов и рейнджей в С++ это будет так же легко делаться из коробки, без обёрток.
                                                                          0

                                                                          Ага, абсолютно логичный и понятный синтаксис. Понятно, что это устойчивая remove-erase idiom, но она все равно очень многословна. Что мешало в стандартную библиотеку добавить такой вариант:


                                                                          std::remove_if(vec, [](auto&& item) { return !some_condition(item); });
                                                                  +3
                                                                  Видимо такой конфигурации мы не дождемся. Я вот тоже хочу C++ еще раз попробовать, т.к. в геймдеве все еще везде C++, но как-то проводить дни в дебаггере за поисками глупых багов совсем не хочется. С растом я дебаггер еще ни разу не запускал.
                                                                    –3
                                                                    Если кода писать на С++ больше намного, чем на Rust, то конечно дебаггер открывать не будете :) Я вот в С++ тоже дебаггер давно не открывал. Выборка не очень репрезентативна, скажем так :)
                                                                      +1
                                                                      Я пишу в основном на плюсах, иногда делаю ошибки (как известно, без ошибок пишет только Торвальдс), и довольно часто приходится их искать с дебагером. ЧЯДНТ?
                                                                        +3
                                                                        ЧЯДНТ?

                                                                        Пишите на плюсах.

                                                                          0
                                                                          А мне на JS дебаггером приходится пользоваться, ЧЯДНТ? :) Может, если я перестану писать на плюсах, это как-то поможет? :)
                                                                            +6

                                                                            Всегда вопрос в системе типов. Чем она мощнее, тем меньше нужда в дебаггере, но тем лучше нужно читать, что за ошибку компилятор пишет. У меня например до сих пор ошибки в хаскель коде иногда вызывают оторопь — непонятно, что не так. Хуже, чем дебажить то, что если ошибка непонятна то просто "пройтись по строчкам и сравнить ожидаемое поведение с реальным" нельзя. Из плюсов — вы доказываете работоспособность в общем случае, а не тот сценарий который протыкан. Ну и навык понимать ошибки компиляции и избегать частых проблем — нарабатывается, а дебажить меньше не приходится. Встречал в хаскель чате людей, которые много лет в прод на нём пишут, а дебажиться не умеют вообще, не слышали про watch window и вот это всё. Забавно, но факт.

                                                                              +3
                                                                              Встречал в хаскель чате людей, которые много лет в прод на нём пишут, а дебажиться не умеют вообще, не слышали про watch window и вот это всё.

                                                                              Моя история.


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

                                                                            0
                                                                            Вопрос был из-за утверждения «если писать много кода на плюсах, то дебагеро открывать не придется».
                                                                  0
                                                                  Имеет смысл переписать это на языке, который предоставит гарантии защиты от таких ошибок?

                                                                  Проблем я вижу две


                                                                  1. Переписывание на Rust не убережет от логических ошибок
                                                                  2. Железо тоже имеет тенденцию подводить. Rust не поможет от того, что благодаря сбою на аппаратном уровне случится флип бита. А между прочим такое случается реально постоянно.
                                                                    +10

                                                                    Знаете, если язык будет беречь от логических ошибок, то зачем ему вообще программист?

                                                                      +5
                                                                      Так кто-то же должен написать программу, что бережёт её от ошибок. Сколько там сейчас программистов на Idris? :)
                                                                        +1

                                                                        idris не спасает от логических ошибок.

                                                                          0
                                                                          Если ошибка в пруфе, то да, полностью согласен.
                                                                            +2

                                                                            Ошибки в пруфе быть не может, идрис (и подобные языки) как раз от них и защищает. Ошибка может быть в доказываемом утверждении (в спеке, иными словами), но это уже совсем другой разговор.

                                                                              0
                                                                              Я это и имел в виду
                                                                                +3

                                                                                Ну тогда правильнее сказать «идрис не спасёт от ошибок в понимании заказчика». Но тут ничего не спасёт.


                                                                                А от ошибки в спеке, кстати, тоже может спасти. Ближайший пример — нахождение наибольшего общего делителя, который тут уже упоминали. Я сначала написал тип соответствующей функции как (m, n : Nat) -> (d ** Gcd d m n) — то есть, для двух натуральных чисел m, n возвращается какое-то число и доказательство того, что оно НОД. Сел доказывать, доказываю, доказываю — упёрся в то, что кусок не могу доказать. А причина-то простая: хотя бы одно из чисел должно быть строго положительным, иначе НОД не определён, и я в процессе доказательства об это споткнулся. Поправил тип — всё доказалось.

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

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

                                                                          0

                                                                          Так наоборот — максимальный уровень verbosity компилятора, а еще лучше — обработка ворнингов как ошибок компиляции и пускай разработчик дорабатывает )

                                                                            +9

                                                                            Ииии вы изобрели раст!

                                                                              +3

                                                                              Только не решили проблему компиляции с++ кода часами и отжора компиляторов всех ресурсов системы )))))

                                                                                +2
                                                                                Только вот она решена отчасти с помощью C++20 модулей. Отчасти потому что кеша инстанциаций в компиляторах нет. Был когда-то проект кеширующего компилятора на базе Clang, но он помер, к сожалению. Zappcc емнип.
                                                                                  +1

                                                                                  От LTO это все равно не спасёт.

                                                                                +2

                                                                                Ну если в плюсах еще докидать тонну атрибутов для лайфтайм ворнингов, а потом все это объявить ошибками при помощи флагов компиляции, то да, один из источников UB мы устраним :) Останется еще 1023)

                                                                            +7
                                                                            Programming Defeatism: No technique will remove all bugs, so let's go with what worked in the 70s.

                                                                            ©

                                                                              +8
                                                                              Простите, а как выглядит код на каком угодно языке, который защищает от флипа бита? Я думал, даже в космосе обходятся тем, что сравнивают два вычисления в железке и чуть что перезагружают вообще всё.
                                                                                0
                                                                                Ну я бы всё таки не путал флип бита и банальную инвалидацию итератора. Вероятность возникновения такого кажется немного разной :)
                                                                                  0

                                                                                  Ну да, одиночные сбои я на работе регулярно наблюдаю, иногда даже большими пачками, а инвалидацию итератора никогда не видел :)


                                                                                  (Не пишу на C++ и занимаюсь испытаниями на радиационную стойкость. В коде на Rust почему‐то инвалидации не происходит, в коде на LabVIEW, C и ассемблере тоже…)

                                                                                +3

                                                                                За 10 лет продакшн работы я встречал наверное тысячи баги, из которых ноль связанных с железом. По-вашему, стоит забить всё же на техники, уменьшающие баги в софте, ведь железо все равно глючит?

                                                                                  0

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

                                                                                    +4

                                                                                    Может, но как верно сказали ниже, нужно предполагать корректную работу железа, математики, ОС и остального чтобы не сойти с ума и сделать хоть что-то полезное.

                                                                                      +3
                                                                                      Да всё, что угодно бывает. У меня знакомые из крупной конторы рассказывали, что Intel им как-то поставил «особо быстрые» CPU… в которых неправильно работало деление. Там итеративный алгоритм, на высокой частоте происходили «гонки»… в результате ответ, иногда, на единичку отличался от нужного, а остаток, сволочь такая, оказывался больше делителя. Конечно hashmap на это отреагировал… не вполне адекватно.

                                                                                      Но пока у вас на 100 (а то и 1000) багов в софте случается один такой аппаратный баг… нужно фиксить софт. А с железом потом уже бороться. Когда в софте не будет столько «косяков».

                                                                                      P.S. В том случае, кстати, софт фиксить не стали. Написали прогу, которая за день прогона определяет — есть бага в CPU или нет и если бага в наличии — выключает Turbo. Ну а потом эти процы по гарантии поменяли.
                                                                                        +6

                                                                                        Вы только что своими словами пересказали историю fdiv bug'а из pentium. Первых которых. Не ммх ещё. Это какой год? 1993?
                                                                                        Или вот опять, снова по тем же граблям ?

                                                                                          0
                                                                                          Сейчас микрокода хватит для подобного в большинстве случаев. Процессоры стали гораздо сложнее с тех пор и тоже уже содержат «софт» внутри.
                                                                                            +1
                                                                                            Intel, вроде как, даже пытался выкатить микрокод, но замедление оказалось заметным, а так как выключение Turbo Boost проблему решало — то выбрали этот вариант.
                                                                                            +1
                                                                                            FDIV имел проблемы не от скорости, а от криво заполненной таблицы — тут.

                                                                                            (Кстати, мне интересно, у них там по-прежнему SRT, или хотя бы Goldsmith применили?)
                                                                                              +2
                                                                                              Или вот опять, снова по тем же граблям ?
                                                                                              Глабли похожие, но другие. В FDIV-баге всё было детерминировано. Тут же — всё зависело от температуры процессора. Что делало, конечно, баг очень весёлым для поимки. В итоге эта модификация «в свободную продажу» не пошла, насколько я знаю. Если вы не Amazon/Facebook/Google (которые получают новые процессоры до их официального объявления да ещё и со скидкой — именно чтобы «в случае чего» такие баги отловить), то вам на эту бяку без разгона нарваться не грозит.
                                                                                          +2
                                                                                          За 10 лет продакшн работы я встречал наверное тысячи баги, из которых ноль связанных с железом
                                                                                          Повезло. За почти 20 лет Delphi видел: битую память, битые сидюки (этих штук 10), битые сети — то постоянно. Переразозногнанные процы, несколько раз. Первое вот что вспоминается.
                                                                                        0

                                                                                        Напомню, что хороший программист на фортране может писать на фортране на любом языке. И с приличной вероятностью при переписывании на раст будет ощутимая доля разработчиков, которые будут фигачить get_unchecked оборачивая в unsafe не задумываясь о соблюдении необходимых инвариантов, как они делали в си, или, с обоснованием что так быстрее, сделают свой RefCell, позволяющий получить &mut T дважды и рассказывая потом что нигде в коде его дважды не дёргают.


                                                                                        С переписыванием криптографии всё очень непросто (хотя есть rustls/ring, как пример), но получение гарантированного поведения (в том числе по влиянию на кэши, предсказатель переходов и т. п.) даже в случае шифров использующих банальный ARX или проверки MAC может быть нетривиально из-за оптимизаций.

                                                                                          +6

                                                                                          Даже если так, это все равно лучше, когда можно грепнуть все unsafe и проверить, что safe-обертка над ними соответствует правилам.

                                                                                        +2
                                                                                        А кто платит за переписывания Директа на Java?

                                                                                        > Хотите действительно безопасности?

                                                                                        Мы хотим адекватного компромисса.
                                                                                        –1

                                                                                        То есть любой возможный будущий репорт стоит 100% ресурсов и полной заморозки проекта на срок равный его прошлой разработке? И у нас точно нет других, чуть менее дорогостоящих, способов с этим жить?

                                                                                          0
                                                                                          А параллельно разрабатывать мы уже не умеем что ли? Портировать существующую кодовую базу не требует задействовать все 100% ресурсов. Это задача несравнимо проще, чем разработка этого продукта с нуля. Более того, многие используют транспайлеры как переходный этап. Код будет не очень красивый, но он будет и сразу. А дальше постепенно его рефакторить.
                                                                                            +7
                                                                                            А зачем мне параллельно писать с выхлопом нулевым, если я этих же программистов посажу на текущую кодовую базу, где они будут писать сразу что-то полезное?
                                                                                              –3

                                                                                              Вы ещё спросите, зачем нужен рефакторинг. Можно ведь хяк-хяк и в продакшн

                                                                                                +7
                                                                                                Интересный перепрыг с темы «Переписывание на другой ЯП» на «Рефакторинг кода на текущем ЯП». Это совсем не одно и тоже.
                                                                                                  +6

                                                                                                  А какая разница, если в результате код становиться лучше и удобнее для использования, расширения и масштабирования в будущем? Ну кроме наверное переучивания программистов.

                                                                                                    +5

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

                                                                                                      +3
                                                                                                      С чего бы ему становиться однозначно лучше?
                                                                                                      В одном случае, речь о более-менее протестированном и известном коде с известными нюансами и особенностями работы, в другом — о подобии черного ящика, который еще тестировать-не_перетестировать… И, что самое забавное, в итоге получить примерно то же самое и это еще в лучшем случае.
                                                                                                        0
                                                                                                        Эм, я несколько смущён, у вас есть понимание, что такое переписать какой нибудь проект более чем на 100-1000 строк с нуля полностью на другом языке? Это годы работы второй команды. Посмотрите сколько у Mozilla ушло на создание прототипа движка Quantum на Rust, который они медленно отдельными изолированными компонентами впиливают в основной проект ;)
                                                                                                          +1

                                                                                                          Справедливости ради, они всё-таки не переписывали "с минимальными правками", а наоборот всячески экспериментировали.


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

                                                                                                            0
                                                                                                            Справедливости ради, они всё-таки не переписывали «с минимальными правками», а наоборот всячески экспериментировали.

                                                                                                            Это то понятно иначе смысла переписывать вообще не было бы никакого.
                                                                                                  0

                                                                                                  Параллельно. Итого мне теперь надо не 100% ресурсов, а 200%. Плюс "старая" команда, поддерживающая продукт будет демотивирована постоянно и бонусом мы получаем внутренний конфликт в компании.
                                                                                                  Кроме этого, новый код будет постоянно отставать от плюсового. Не получим ли мы не 10 лет на разработку, а 15?
                                                                                                  Кто это оплатит?

                                                                                              +21

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


                                                                                              Уменьшение багов и увеличение скорости внедрения новых фич тоже вполне себе переводится на $$. Если мозилла даже четверть браузера смогла переписать (и если кто помнит это изначальная причина почему раст вообще появился), то наверное какой-то смысл в этом есть, не так ли?

                                                                                                –1
                                                                                                Лично я считаю, что основной причиной переписывания Firefox на Rust является популяризация Rust и не более (да, я читал их посты о том, что они не смогли в С++ и теперь у них волосы мягкие и шелковистые). Зачем они это делают? ИМХО — хотят иметь «свой» язык по аналогии с C# у MS, Kotlin у JB, etc. Зачем им это? Не знаю.
                                                                                                  +3

                                                                                                  Беда только… Ну не наблюдается роста доли раста в Firefox.

                                                                                                    +2
                                                                                                    Я думаю, это временное явление и всё ещё будет.
                                                                                                      +2

                                                                                                      Полтора года не растёт. Вначале был всплеск и доля c++ начала падать. А потом всё остановилось. И так оно уже долго.

                                                                                                      +5
                                                                                                      Всё относительно. Они скоро выкатят в релиз обновлённый Firefox на мобильные платформы, с удобным дизайном и который в отличии от старого успешно работает на старых железках с 1 ГБ памяти. В общем посмотрим. Firefox реально лучше любой хромоножки, приватность данных, расширения на могильных платформах. Уже в 12 году вернулся на Firefox и не жалею, он реально для людей делается. Да и это единственный теперь браузер в котором uBlockOrigin полноценно работает.
                                                                                                        +1
                                                                                                        расширения на могильных платформах

                                                                                                        Понимаю, что опечатка, но не могу не спросить — это на Windows Phone, что ли?..

                                                                                                          +2
                                                                                                          На всех поддерживаемых. Я все мобильные платформы так называю переодически :) Они все, скажем так, урезанные, с закрученными гайками и не полноценные для многих задач. Последними нормальными мобильными полноценными ОС были Symbian и Windows Mobile. Сейчас всё слишком весело. Дабы не углубляться в тему слишком глубоко, просто оставлю в качестве примера Don't kill my app!, на Android с этим местом отдельные проблемы, на iOS просто невозможно запустить фоновый процесс с гарантией того что он не будет убит.
                                                                                                            0
                                                                                                            Это вы сейчас про то, что при открытии нескольких вкладок в браузере андроид убивает висящий в фоне и играющий музыку плеер?
                                                                                                              0
                                                                                                              висящий в фоне и играющий музыку плеер?

                                                                                                              у меня не убивает — что я делаю не так? А, точно — это ж у меня в фоне ютуб премиум фигачит

                                                                                                                0
                                                                                                                У меня фубар, задрало, что вариант использования браузером — открыл по ссылке новую вкладку, прочитал, закрыл.
                                                                                                                Почему, блин, на 3гб памяти я не могу просто послушать в фубаре фоном музыку! На винфонах там я только на совсем дохлой люмии при каких-то странных страничках подобное пару раз встречал (которые вешали браузер), а тут — на постоянку…
                                                                                                                  0
                                                                                                                  У меня в смартфоне 6 ГБ и приложения убиваются, музыка обычно работает, но вот всё настолько гнило что виджеты, например, кроме системных часов, не обновляются вообще или обновляются иногда но с чем это связано — не ясно. Даже закреплённые в недавних приложения просто исчезают и перестают быть запущенными, OnePlus 7 Pro. Скатился производитель в говно оказывается, а я этот момент пропустил ибо до этого был 3T где никаких таск киллеров сторонних не было.
                                                                                                                  0
                                                                                                                  Это зависит от версии ОС, ну и поскольку open source то есть производители-мудаки которые туда ещё свои тасккилеры пихают.
                                                                                                        0

                                                                                                        Есть мнение, что Раст уже перерос давно мозиллу.

                                                                                                          +2
                                                                                                          Есть мнение, что и C# уже перерос давно Microsoft.
                                                                                                            0

                                                                                                            Ну так даже если МС от него откажется, то он и правда останется жить, разве нет?

                                                                                                              +1
                                                                                                              Разве нет. В течении более, чем 10 лет Microsoft стремился к тому, чтобы между C# и Windows устновилась, в головах, прочная связь. А дальше — планировалось, что Windows будет везде и одним из агрументов для этого будет «классный язык C#».

                                                                                                              Как у классика: Ничего не будет. Ни киноGNU, ни театраEmbedded, ни книгSymbian, ни газетBlackberry – одно сплошное телевидениеWindows.

                                                                                                              Когда проект Windows Phone «накрылся медным тазом» и стало понятно, что аргумент «C# отлично работает на Windows и за будьте про всё остальное», скорее, заставляет разработчиков выбирать Java (Python и прочее) и уходить с Windows — произошёл разворот на 180 градусов, Visual Studio начала поддерживать вот это вот всё… и C# начали птытаться сделать «кроссплатформенным».

                                                                                                              Но… поздно метаться: слишком много вещей, которые считаются «само собой разумеющимися» в C#-мире по прежнему привязаны к Windows.

                                                                                                              Так что сегодня C# добрался только лишь до стадии: «хорошо под Windows и, если очень надо, можно чуть-чуть для чего-то ещё».

                                                                                                              Этот статус позволяет ему жить, но если Microsoft от C# откажется — смысл в нём исчезнет немедленно.
                                                                                                                0
                                                                                                                Но… поздно метаться: слишком много вещей, которые считаются «само собой разумеющимися» в C#-мире по прежнему привязаны к Windows.

                                                                                                                Это каких таких в контексте .net core? Если убрать UI (WPF/WinForms) составляющую
                                                                                                                  0
                                                                                                                  Это каких таких в контексте .net core? Если убрать UI (WPF/WinForms) составляющую.
                                                                                                                  Понятия не имею какую там нужно составляющую убирать и откуда, но у меня несколько знакомых (не программистов) искали хостинг для своих ASP.NET проектов — на дешёвых хостингах с Linux не заработал ни один.

                                                                                                                  Что там технически происходит — я не в курсе, но связь «C# = Windows, а C# под Linux — для тех, кому нужно приключений на свою задницу» это поддерживает устойчиво.
                                                                                                                    +1

                                                                                                                    Я не знаю, что там на дешевых хостингах, но в контейнерах все работает прекрасно у кучи компаний (из российский на слуху озон, додо, каспер и т.д)

                                                                                                                      –4
                                                                                                                      У кучи компаний оно работает, потому что у них стояла задача «запустить это всё под Linux» — и был выделен на это бюджет.

                                                                                                                      А у энтузиастов бюджета нету: если у вас выбор, условно, платить $10 в месяц или $20, но перед этим заплатить программисту $300-$500 для адаптации… то простая логиика подсказывает, что вы таки будете платить $20.

                                                                                                                      В Java, к примеру, этого нет, потому что все пакеты изначально проектировались и затачивались, в том числе, под Linux.
                                                                                                                        +4

                                                                                                                        Если пилить проект изначально под .net core, то все прекрасно будет работать под Linux.

                                                                                                                          0
                                                                                                                          Вот только основная масса C#-проектов — уже «запилены»… и не под Linux.
                                                                                                                            +1

                                                                                                                            Ну так пусть живут не под Linux. Выгодно будет переписать — перепишут. Это не так и сложно, если нет компонент, прибитых к винде. Но это уже не относится к .net

                                                                                                                              +2

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


                                                                                                                              Можно было бы обойтись куда меньшими правками и перенести вообще в несколько раз быстрее. Но решили попутно еще пару проблем с архитектурой порешать.


                                                                                                                              В общем, кор жив, и все компании которые пишут на шарпе и не хотят остаться за бортом либо переписывают на кор, либо уже переписали свои основные продукты. Кто-то даже на питон переписывает (не спрашивайте меня, почему, я сам задал тот же вопрос), но на виндовом фреймворке оставаться не желает никто.

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

                                                                                                                                Что будет если Microsoft, ну вот прям завтра скажет: «всё, мы передумали, нам играться в кор более не интересно, проворачиваем фарш назад, мясо — корове в зад… возвращаемся на Windows»?

                                                                                                                                Скорее всего тех, кто будет продолжать «переписывать на кор» — станет резко меньше, а тех кто будет «даже на питон» — резко больше.

                                                                                                                                И всё. И кончится C#, который, якобы, давно перерос Microsoft.

                                                                                                                                Вот лет через 5-10, когда на версии для Windows останется только небольшая часть сурового Legacy. И когда разработчиков C# вне Microsoft будет больше чем внутри… да, тогда можно будет сказать «да, C# уже перероc Microsoft».

                                                                                                                                А сейчас — ещё нет. Да, движение в этом направлении идёт… но пока ещё — C# очень сильно завязан на Microsoft…
                                                                                                                                  +2
                                                                                                                                  По-моему вы яростно соглашетесь со мной. Вот смотрите — сейчас ситуация такая, что «на виндовом фреймворке оставаться не желает никто», но при этом часть «переписывают на кор», а часть «даже на питон переписывает».

                                                                                                                                  Понятное дело что есть процент легаси всегда. В тех проектах, на которых я работал и они были мне интересны цифра виндвого фреймворка была 5-30%.


                                                                                                                                  А сейчас — ещё нет. Да, движение в этом направлении идёт… но пока ещё — C# очень сильно завязан на Microsoft…

                                                                                                                                  Я нигде не говорил что C# будет отвязан от майкрософта, скорее наоборот. Это как котлин и жетбрейнс, умрут последние — котлин резко потеряет в коммьюнити. Умрёт гугл — скорее всего го резко исчезнет с радаров.


                                                                                                                                  C# отвязан от винды, уже давно и это так. Но он совсем не отвязан от майкрософта, и скорее всего, никогда не будет.

                                                                                                                                    –1
                                                                                                                                    А теперь возвращаемся в начало дискуссии и спрашиваем себя: а с чем, собственно, вы спорите?
                                                                                                                                      +1
                                                                                                                                      Так что сегодня C# добрался только лишь до стадии: «хорошо под Windows и, если очень надо, можно чуть-чуть для чего-то ещё».

                                                                                                                                      Вот с этим утверждением.

                                                                                                                                        0
                                                                                                                                        С ним можно было бы спорить, если бы массовый переход на core уже состоялся. Но нет — он в самом разгаре «у компаний в теме». А «мелочь пузатая» подтянется лет через 5-10.
                                                                                                                                          +3
                                                                                                                                          С ним можно было бы спорить, если бы массовый переход на core уже состоялся

                                                                                                                                          Состоялся. О чем я уже не раз говорил. Сидят всякие легаси-монстры, которым и COM нужен, и AD, и прочие. Все b2b SAAS — уже там.


                                                                                                                                          Но нет — он в самом разгаре «у компаний в теме». А «мелочь пузатая» подтянется лет через 5-10.

                                                                                                                                          Всё наоборот, мелочь пузатая как раз быстро и чутко адаптирует новые стеки, а догоняющими являются "компании в теме".

                                                                                                                                            0
                                                                                                                                            Всё наоборот, мелочь пузатая как раз быстро и чутко адаптирует новые стеки, а догоняющими являются «компании в теме».
                                                                                                                                            В данном случае я имею в виду не стартапы какие-нибудь, а булошные и кафешки. Которые вообще своего IT-отдела не имеют и заказывают переработку своего сайта/бухгалтерии раз 3-5-10 лет. Ну те, которые плачут горючими слезами про то, что поддержка Windows 7 закончилась (а некоторые — так и с Windows XP не слезли). Они ещё даже не почесались со времени появления .NET core, не то, что не перешли.