Rust vs. C++ на алгоритмических задачах

Не так давно я стал присматриваться к языку программирования Rust. Прочитав Rustbook, изучив код некоторых популярных проектов, я решил своими руками попробовать этот язык программирования и своими глазами оценить его преимущества и недостатки, его производительность и эко-систему.

Язык Rust позиционирует себя, как язык системного программирования, поэтому основным его vis-à-vis следует называть C/C++. Сравнивать же молодой и мультипарадигмальный Rust, который поддерживает множество современных конструкций программирования (таких, как итераторы, RAII и др.) с «голым» C я считаю не правильно. Поэтому в данной статье речь пойдет об сравнении с C++.

Чтобы сравнить код и производительность Rust и C++, я взял ряд алгоритмических задач, которые нашел в онлайн курсах по программированию и алгоритмам.

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

Данная статья достаточно субъективная. Даже сравнение производительности программ, которые делают одинаковые вещи, но написаны на разных языках, подвержены авторскому подходу. Поэтому я не претендую на объективные замеры, и, чтобы не навязывать свои результаты, предлагаю всем ознакомится с кодом программ и с подходом к замеру производительности. Код выложен на github. Буду рад любой критике и замечаниям. Начнем.

Что плохого и хорошего в Rust


+ Разработчики Rust поставляют свой компилятор уже с «батарейками внутри» тут есть: компилятор, менеджер пакетов (он же сборщик проектов, он же отвечает за запуск тестов), генератор документации и отладчик gdb. Исходный код на Rust может включать в себя сразу тесты и документацию, и чтобы собрать все это не требуется дополнительных программ или библиотек.

+Компилятор строг к тексту программы, который подается ему на вход: в его выводе можно увидеть какой код не используется, какие переменные можно изменить на константный тип, и даже предупреждения, связанные со стилем программирования. Часто для ошибок компиляции приведены варианты ее устранения, а ошибки при инстанциировании обобщенного кода (шаблонов) лаконичны и понятны (привет ошибкам с шаблонами STLв C++).

+ При присваивании или передачи аргументов по умолчанию работает семантика перемещения (аналог std::move, но не совсем). Если функция принимает ссылку на объект необходимо явно взять адрес (символ &, как в C++).

+ Все строки — это юникод в кодировке UTF-8. Да, для подсчета количества символов нужно О(N) операций, но зато никакого зоопарка с кодировками.

+ Есть поддержка итераторов и замыканий (лямбда функций). Благодаря этому можно писать однострочные конструкции, которые выполняют множество операций с сложной логикой (то, чем славится Python).

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

- Нужно писать программы так, чтобы компилятор (точнее borrow checker) поверил, что вы не делаете ничего плохого с памятью (или с ресурсами в целом). Часто это работает как надо, но иногда приходится писать код в несколько хитрой форме только для того, что бы удовлетворить условиям borrow checker'а.

- В Rust нет классов, но есть типажи, на основе которых можно построить объектно ориентированную программу. Но скопировать реализацию с полиморфными классами в C++ в язык Rust напрямую не получиться.

- Rust достаточно молод. Мало полезного материала в сети, на StackOverflow. Мало хороших библиотек. Например, нет библиотек для построения GUI, нет портов wxWidgets, Qt.

Алгоритмические задачи на Rust


Бинарный поиск


Нужно для для каждого значения из вектора B найти его позицию в векторе A. По сути нужно применить n раз бинарный поиск, где n — кол-во элементов в B (массив A предварительно отсортирован). Поэтому тут я приведу функцию бинарного поиска.
// return position of the element if found
fn binary_search(vec: &[u32], value: u32) -> Option<usize> {
    let mut l: i32 = 0;
    let mut r: i32 = vec.len() as i32 - 1;
    while  l <= r {
        let i = ((l + r) / 2) as usize;
        if vec[i] == value {
          return Some(i);
        } else if vec[i] > value {
          r = i as i32 - 1;
        } else if vec[i] < value {
          l = i as i32 + 1;
        } 
    }
    None
}


Кто первый раз видит Rust, обратите внимание на пару особенностей:
  1. Тип возвращаемого значения указывается в конце объявления функции
  2. Если переменная изменяемая, то нужно указывать модификатор mut
  3. Rust не переводит типы неявно, даже числовые. Поэтому нужно писать явный перевод типа l = i as i32 + 1


Сортировка слиянием


Формально задача звучит так: для заданного массива подсчитать кол-во перестановок, необходимых для сортировки массива. По факту нам требуется реализовать сортировку слиянием, подсчитывая по ходу длину перестановок элементов.

Давайте рассмотрим код чтения массива с stdin
fn read_line() -> String {
    let mut line = String::new();
    io::stdin().read_line(&mut line).unwrap();
    line.trim().to_string()
}

fn main() {
    // 1. Read the array
    let n: usize = read_line().parse().unwrap();
    let mut a_vec: Vec<u32> = vec![0; n as usize];
    for (i, token) in read_line().split_whitespace().enumerate() {
        a_vec[i] = token.parse().unwrap();    
    }

...


  1. У классов нет конструкторов, но можно делать статические методы-фабрики, которые возвращают объекты классов, как String::new() выше.
  2. Функции, которые могут ничего не вернуть, возвращают объект Option, который содержит None или результат корректного завершения функции. Метод unwrap позволяет получить результат или вызывает panic!, если вернулся None.
  3. Метод String::parse парсит строку в тип возвращаемого значения, т.е. происходит вывод типа по возвращаемому значению.
  4. Rust поддерживает итераторы (как генераторы в Python). Связка split_whitespace().enumerate() генерирует итератор, который лениво читает следующий токен и инкрементирует счетчик.


Приведу сначала неправильную сигнатуру вызова функции _merge, которая сливает in place два отсортированных подмассива.
fn _merge(left_slice: &mut [u32], right_slice: &mut [u32]) -> u64


Данная конструкция не взлетит в Rust без unsafe кода, т.к. тут мы передаем два изменяемых подмассива, которые располагаются в исходном массиве. Система типов в Rust не позволяет иметь две изменяемых переменных на один объект (мы знаем, что подмассивы не пересекаются по памяти, но компилятор — нет). Вместо этого надо использовать такую сигнатуру:
fn _merge(vec: &mut [u32], left: usize, mid: usize, right: usize) -> u64


Кодирование Хаффмана


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

Заведем класс Node:
// Type of the reference to the node
type Link = Option<Box<Node>>;

// Huffman tree node struct
#[derive(Debug,Eq)]
struct Node {
    freq: u32,
    letter: char,
    left: Link,
    right: Link,
}

impl Ord for Node {
    // reverse order to make Min-Heap
    fn cmp(&self, b: &Self) -> Ordering {
        b.freq.cmp(&self.freq)
    }
}


  1. Можем использовать типы до их описания.
  2. Тут можно видеть странный синтаксис наследования #[derive(Debug,Eq)]. Debug — поддерживаем форматированную печать объекта по-умолчанию. Eq — определяем операцию сравнения на равенство.
  3. Для Node определяется типаж сравнения на больше/меньше Ord. Типажи позволяют расширять возможности объектов. В частности, здесь мы сможем использовать Node для хранения Min-куче.


Метод для посещения нод дерева сверху вниз и для составления таблицы кодирования.
impl Node {
...
    // traverse tree building letter->code `map`
    fn build_map(&self, map: &mut HashMap<char, String>, prefix: String) {
        match self.right {
            Some(ref leaf) => leaf.build_map(map, prefix.clone() + "1"),
            _ => { },
        }
        match self.left {
            Some(ref leaf) => { leaf.build_map(map, prefix + "0"); },
            _ => { map.insert(self.letter, prefix); },
        }
    }
}


  1. Рекурсивно вызывает ветки бинарного дерева, если их указатель не пустой &Some(ref leaf).
  2. Конструкция match похожа на switch в C. match должен обработать все варианты, поэтому тут присутсвует _ => { }.
  3. Помните про семантику перемещения по-умолчанию? Поэтому нам нужно писать prefix.clone(), чтобы в каждую ветвь дерева передалась своя строка.


Декодирование Хаффмана


Обратная задача: для известной таблицы кодирования и закодированной строки получить исходное сообщение. Для декодирования удобно пользоваться бинарным деревом кодирования, поэтому в программе нам нужно из таблицы кодирования получить дерево декодирования. На словах задача простая: нужно перемещаться вниз по дереву (0 — влево, 1 — вправо), создавая промежуточные узлы при необходимости, и в листья дерева помещать символ исходного сообщения. Но для Rust задача оказалась сложная, ведь нам нужно перемещаться по изменяемым ссылкам, создавать объекты, и при этом избегать ситуации, когда объектом владеет более одной переменной. Код функции заполнения бинарного дерева:
fn add_letter(root: &mut Link, letter: char, code: &str) {
    let mut p: &mut Node = root.as_mut().unwrap();
    for c in code.chars() {
        p = match {p} {
            &mut Node {left: Some(ref mut node), ..} if c == '0' => {
                node
            },
            &mut Node {left: ref mut opt @ None, ..} if c == '0' => {
                *opt = Node::root();
                opt.as_mut().unwrap()
            },
            &mut Node {right: Some(ref mut node), ..} if c == '1' => {
                node
            },
            &mut Node {right: ref mut opt @ None, ..} if c == '1' => {
                *opt = Node::root();
                opt.as_mut().unwrap()
            },
            _ => { panic!("error"); }
        }
    }
    p.letter = letter;
}


  1. Тут match используется для сравнения структуры переменной p. &mut Node {left: Some(ref mut node), ..} if c == '0' означает «если p это изменяемая ссылка на объект Node у, которого поле left указывает на существующий node и при этом символ c равен '0'».
  2. В Rust нет исключений, поэтому panic!("...") раскрутит стек и остановит программу (или поток).


Расстояние Левенштейна


Нужно для двух строк посчитать расстояние Левенштейна — кол-во действий редактирования строк, чтобы из одной получить другую. Код функции:
fn get_levenshtein_distance(str1: &str, str2: &str) -> u32 {
    let n = str1.len() + 1;
    let m = str2.len() + 1;
    // compute 2D indexes into flat 1D index
    let ind = |i, j| i * m + j;
    let mut vec_d: Vec<u32> = vec![0; n * m];
    for i in 0..n {
        vec_d[ind(i, 0)] = i as u32;
    }
    for j in 0..m {
        vec_d[ind(0, j)] = j as u32;
    }

    for (i, c1) in str1.chars().enumerate() {
        for (j, c2) in str2.chars().enumerate() {
            let c =  if c1 == c2 {0} else {1};
            vec_d[ind(i + 1, j + 1)] = min( min( vec_d[ind(i, j + 1)] + 1
                                               , vec_d[ind(i + 1, j)] + 1
                                               )
                                          , vec_d[ind(i, j)] + c
                                          );
        }
    }
  return vec_d[ind(n - 1, m - 1)];
}


  1. str1: &str — это срез строки. Легковесный объект, который указывает на строку в памяти, аналог std::string_view C++17.
  2. let ind = |i, j| i * m + j; — такой конструкцией определяется лямбда функция.


Замеры производительности Rust vs C++


В конце, как обещал, прикладываю таблицу сравнения времени работы программ, описанных выше. Запуск производился на современной рабочей станции Intel Core i7-4770, 16GB DDR3, SSD, Linux Mint 18.1 64-bit. Использовались компиляторы:
[>] rustc --version
rustc 1.22.1 (* 2017-11-22)
[>] g++ -v
...
gcc version 7.2.0



Пару замечаний по результатам:
  1. Измерялось полное время работы программы, в которое включено чтение данных, полезные действия и вывод в /dev/null
  2. В * (core) измерялось только время работы алгоритмической части (без ввода/вывода)
  3. Делалось 10 прогонов каждой задачи на каждом наборе данных, далее результаты усреднялись
  4. Все скрипты, производящие компиляцию, подготовку тестовых данных и замеры производительности, представлены в репозитории. К ним есть описание.
  5. По моему мнению, на ряде задач C++ проигрывает из-за библиотеки потокового чтения/записи iostream. Но эту гипотезу еще предстоит проверить.И да, в коде есть std::sync_with_stdio(false)
  6. По моему мнению, Rust сильно проигрывает в тесте Huffman encoding по причине медленных хешей в HashMap
  7. На полном времени выполнения задач Rust показал, что по производительности он не уступает C++. В каждом языке есть свои особенность реализации стандартной библиотеки, которые сказывают на скорости работы задач.
  8. На замерах только алгоритмической части, Rust проигрывает порядка 10%, но, думаю ситуация бы исправилась, если будем использовать хэши побыстрее в первой задаче.


Заключение


Повторюсь, что статья субъективна, и признана в первую очередь оценить в грубом масштабе, где находится Rust по отношению к C++. Если у вас есть пожелания, идеи или замечания, которые позволят добавить статье объективности, пишите в комментарии.

Вы можете ознакомиться со всеми кодами, необходимыми для повторения замеров на github: https://github.com/dmitryikh/rust-vs-cpp-bench.

Спасибо, что дочитали до конца!

Обновление от 11.12.2017


Спасибо за вашу работу в комментариях!
Я учел ряд ваших замечаний:
  1. Исправил функцию binary_search. Теперь она возвращает Option
  2. Задача Binary_search теперь принимает на вход отсортированный массив A, поэтому во время работы теперь не входит сортировка A
  3. Добавил -DNDEBUG при компиляции C++. Посыпаю голову пеплом..
  4. Исправлена грубая ошибка в задаче Huffman_decoding, из-за который программа на C++ ДВАЖДЫ декодировала строку. Прошу прощения за это.
  5. Добавлен замер времени выполнения только алгоритмической части, без учета операций ввода-вывода и загрузки программы.


Теперь ситуация стала более объективной. Но в целом, оба языка держаться в поле ±10% на данных задачах.
Поделиться публикацией
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 210
  • +15
    За статью, безусловно, спасибо. Однако, подача материала, а главное, цель статьи меня смущают.

    Мне кажется, что плюсер, прочитавший статью и не знакомый с Rust, в лучшем случае «пожмет плечами» или пробурчит «как на Фортране можно писать на любом языке программирования».

    Сила Rust не только и не столько в итераторах, ссылках и семантике перемещения, сколько в иной философии и подходу к проектированию программ. Но из статьи этого понять не выйдет.

    У меня по крайней мере, сложилось бы впечатление, вроде: «Мда… ну и зачем весь этот бред? Учитесь писать на плюсах, а не выдумывайте очередного его “убийцу”».
    • +3
      А можно как проходящему мимо человеку запросить более обстоятельный комментарий (а возможно даже и статью) про разницу в философии проектирования программ?
      • +9
        Rust — уникальный проект. И как язык и как экосистема.

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

        Философия языка — это сплав современных представлений computer science и хиропрактик системного программирования, повернутых, однако, лицом к программисту-прикладнику.

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

        Да, тут я говорю и про C++. Программирование на C++ — это сделка с дьяволом. Ты получаешь большие возможности в обмен на свою душу, когда самолет под управлением твоей программы грохнется в океан. C++ пестрит ситуациями, приводящими к неопределенному поведению, о которых программист обязан помнить.

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

        Rust пытается сделать то же самое на уровне системного программирования. Есть фундаментальная разница между утверждениями «корректность системы проверена тестами» и «корректность системы доказана математически». В первом случае, даже после десяти лет эксплуатации, мы все равно не можем быть уверены, что система надежна.

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

        Все вместе позволяет без опаски реализовывать (а главное рефакторить!), сложные многопоточные программы. Здесь очень кстати будет классический уже пост Fearless Concurrency от одного из авторов языка, плюс великолепная серия статей Lin Clark про внутреннее устройство Firefox Quantum, который стал возможен исключительно благодаря возможностям Rust.

        Как экосистема Rust уникален тем, что ядру команды удалось сформировать удивительное сообщество, готовое к диалогу и дружелюбное к новичкам. Буквально каждый момент взаимодействия с этим сообществом вызывает чувство глубокой благодарности. Тем удивительнее это видеть в среде, близкой к системному программированию.
        • +4
          Вот бы все на Хабре излагали свою точку зрения как вы! Спасибо!
          • +3
            Спасибо на добром слове :)
    • +13
      Без аналогичного кода на С++, с которым проводилось сравнение, довольно бессмысленная статья. Предполагаю, что раз есть такая большая разница, программы написаны коряво. Оба языка компилируемые, и оптимизатор С++ уж точно не хуже Rust. Кстати, с какими флагами компилировался код на С++?
      • +10
        На всякий случай повторю, что цель статьи не в том, чтобы сказать, что Rust быстрее C++, а сказать, что Rust не медленней C++.

        Код для C++ не представлен, чтобы не раздувать размер статьи. Код доступен на github, там же вы можете оценить корявость кода и флаги компиляции. Если у вас будут объективные замечания к коду — я обязательно их учту. Флаги:
        g++ -std=c++11 -O2 -o main_cpp main.cpp
        rustc -O --crate-name main_rust main.rs

        Rust компилирует в LLVM код, как и clang, поэтому оптимизации, заложенные в LLVM, должны работать и там, и тут.

        У Rust система типов дает оптимизатору дополнительные гарантии по владению памяти, что потенциально может привести к более оптимизированному коду.
        • +3
          Кстати, ради интереса, можно было бы к компиляции плюсов через gcc 7.2 добавить и clang тот же, что использовался для раста.
          • +4
            А замена О2 на О3 может привести к более оптимизированному коду С++.
            • +6

              Прежде чем хвататься за оптимизации, для начала посмотрите код теста на С++.
              Да и сам автор писал, что замеры не достаточно объективны.
              Плюс, насколько я понимаю, время считается и с учетом ввода/вывода данных.


              P.s. я в Rust вообще не разбираюсь, но мне кажется, что тут что-то не чисто)
              И первое, что я бы сделал, так это бы выбросил ввод/вывод.

              • +4
                А что может быть не чисто? Любой язык должен быть медленнее сей или плюсов? И кто-то сказал, что код на расте идеален? Смысл в этом посте не в том, что показать, что что-то быстрее или медленнее, а в том, чтобы показать, что они равноценны по производительности хотя бы. Так что смысла в вашем комментарии вижу мало, разве что про убирание вывода соглашусь, было бы неплохо.
                • +1
                  Спорить о быстродействии глупо, т.к. разные реализации стандартной библиотеки.
                  И без тестов можно было бы предположить что С++ может быть медленней(в std упор на универсальность, особенно это касается hashmap), но вот разогнать производительность можно без проблем.
                  • +3
                    Спорить глупо, да, потому что реальных и максимально честных замеров в этой статье не приводится, а приводится грубая оценка, чтобы понимать, на каком они уровне и можно ли говорить, что они равнозначны в производительности вообще, а не так, что Java и C++.
            • +3

              Того же можно добиться и на крестиках.
              Интуитивно — оптимизированные до упора программы должны дать примерно одинаковое быстродействие, и сравнивать надо будет уже сложность программ (количество boilerplate кода и т.п.)

              • +3
                Интуитивно — оптимизированные до упора программы должны дать примерно одинаковое быстродействие


                Спасибо за эту мысль. Я ее не смог высказать в статье.

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

                Мы ведь хотим писать быстро, и чтоб работало быстро, а не зависать над ассемблером и профилировщиком :)
                • +6

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

                  • +4

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

                    • 0

                      Если так — то нафига он такой красивый нужен?
                      Но мне почему-то кажется, что вы заблуждаетесь. Иначе с чего бы расту претендовать на ту же нишу?
                      Опять же, на плюсах есть куча мест, где для написания предельно эффективного кода — надо или писать кучу бойлерплейта, или использовать совсем адское метапрограммирование на темплейтах (зовите экзорцистов, пусть изгонят дух Александреску!). Казалось бы, раст посвежее, и авторы должны были постараться найти решение получше.
                      Но я раст не знаю, так что подожду, пока кто-то не сделает такое сравнение. Или пока мне самому раст не понадобится.

                      • +7

                        Поясню. В С++ у вас вся программа представляет собой один сплошной unsafe блок. Где-то промахнулись с указателем — и всё, приехали. Причём учтите, что с тривиальными ошибками помогают смарт-указатели. С нетривиальными — всё по старому.


                        Теперь — чем лучше rust конкретно здесь. Возможностью сказать компилятору "конкретно здесь я сделаю сам, а вокруг уж будь добр проконтроллируй ты". Именно поэтому я и говорю, что если, как вы пишете,


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

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


                        Если же вы имели ввиду написание максимально оптимального кода, но с соблюдением С++-стайл — на С++ будет, скорее всего, длиннее. Почему — инфраструктура написания кастомных контейнеров на С++ всё ещё в зачаточном состоянии. Особенно в области написания типов итераторов.

                        • –3
                          Поясню. В С++ у вас вся программа представляет собой один сплошной unsafe блок. Где-то промахнулись с указателем — и всё, приехали. Причём учтите, что с тривиальными ошибками помогают смарт-указатели. С нетривиальными — всё по старому.

                          Всё не совсем так, а вернее совсем не так.

                          Все эти рассуждения сводятся к одному — мы сравниваем несравнимые вещи. Мы сравниваем кейсы с указателями, которые в расте точно так же требуют указателей, именно поэтому в stdlib через строчку unsafe.

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

                          Опять же, не так. Никакой компилятор ничего не контролирует. Схема очень простая — мы просто запрещаем использовать всё опасное, а вернее 95% языка.

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

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

                          В конечном итоге раст ничем не отличается от того же stl, где никакие указатели не нужны. Единственная разница в том, что в C++ писать unsafe не надо, а в расте надо. Но написать ты его можешь где угодно из его наличия ровным счётом ничего не следует.

                          Есть какие-то рассуждение на тему того, что «а unsafe видно лучше», но кем лучше и почему — не сообщается. Чем его лучше видно, нежели new — неясно. Все рассуждения про «new может быть где-то спрятан» — правильно, так же как и unsafe.

                          В конечном итоге раст — это просто набор обёрток, которые можно реализовать где угодно и они реализованы на С++. Никакой разницы нет. Единственная разница в том, что нужно писать unsafe — на этом разница заканчивается.

                          Все рассуждения о том, что unsafe что-то там — не работают. Такие же рассуждения были о том, что «мы заставляем всех проверять ошибки», но почему-то добавили unwrap() и теперь весь код — это unwrap() через unwrap() и где всё эти рассуждения? А нигде. Никому они не интересны. Остались на уровне гайдлайнов, как и в С++.

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

                          Ничего из этого намеренно сделано не было, никакой явности нет. Никто и никогда не расскажет о том, чем код в unsafe более явный, нежели С++. Как максимум всякие субъективные рассуждения на тему «мне так нравится», но каждый своё болото хвалит и никаких объективных предпосылок к этому нет.

                          Если же вы имели ввиду написание максимально оптимального кода, но с соблюдением С++-стайл — на С++ будет, скорее всего, длиннее.

                          Чего длиннее и почему — никто не расскажет и не покажет.

                          Почему — инфраструктура написания кастомных контейнеров на С++ всё ещё в зачаточном состоянии.

                          В зачаточном состоянии она на расте — без unsafe ничего не напишешь.

                          Да, можно накастылить семантику указателя через ссылку + option, как это сделал автор, да и кто угодно сделает. Только есть нюансы — указатель хранит состояние бесплатно, а option платно. Привет оверхед по памяти 50-100% на ноду. Естественно, что в хелвордах это мало кому интересно, но это интересно за рамками оных.

                          Особенно в области написания типов итераторов.

                          Что это такое, какие тут есть проблемы — не знаю ни я, ни, скорее всего, все остальные.
                          • +6

                            Вы интересный. Вам всё непонятно и неизвестно.


                            Для начала — что может Rust unsafe:


                            1. Разыменовывать указатели (не ссылки а именно указатели)
                            2. Вызывать unsafe функции, в т.ч. FFI
                            3. Реализовывать для типов unsafe трейты
                            4. Менять статические переменные

                            Нас будет интересовать в основном №1 и немножко №2, из-за операций над указателями и transmute.


                            Всё не совсем так, а вернее совсем не так.

                            А я могу сказать, что примерно так и есть. В С++ вы не можете статически гарантировать, что указатель не указывает "в молоко". Вы не можете гарантировать, что ещё жива стуктура, на которую он указывает. Имеющиеся там ссылки из-за неперемещаемости почти неюзабельны как поля структур. Так что в С++ указатели — повсеместное средство.


                            Все эти рассуждения сводятся к одному — мы сравниваем несравнимые вещи. Мы сравниваем кейсы с указателями, которые в расте точно так же требуют указателей, именно поэтому в stdlib через строчку unsafe.

                            Угу. Только потом эти указатели не будут торчать наружу. В этом отличие и есть.
                            По количеству — https://github.com/rust-lang/rust/search?utf8=%E2%9C%93&q=unsafe&type=
                            1068 вхождений просто слова. На весь проект. Натыкался где-то, что реальных использований около 200-250, но ссылку к сожалению привести не могу.


                            В конечном итоге раст ничем не отличается от того же stl, где никакие указатели не нужны. Единственная разница в том, что в C++ писать unsafe не надо, а в расте надо.

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


                            Есть какие-то рассуждение на тему того, что «а unsafe видно лучше», но кем лучше и почему — не сообщается. Чем его лучше видно, нежели new — неясно. Все рассуждения про «new может быть где-то спрятан» — правильно, так же как и unsafe.

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


                            В конечном итоге раст — это просто набор обёрток, которые можно реализовать где угодно и они реализованы на С++. Никакой разницы нет. Единственная разница в том, что нужно писать unsafe — на этом разница заканчивается.

                            В конечном итоге они оба транслируются в ассемблер, которому на все эти новомодные штучки наплевать — он байты перекладывает. Разница есть. Либо вы прилагаете дополнительные усилия, пишете обёртки, доп. проверки, призываете статические чекеры чтобы не отстрелить себе ногу. Либо вы пишете дополнительный код, чтобы какие-то ограничения локально отменить.


                            Все рассуждения о том, что unsafe что-то там — не работают. Такие же рассуждения были о том, что «мы заставляем всех проверять ошибки», но почему-то добавили unwrap() и теперь весь код — это unwrap() через unwrap() и где всё эти рассуждения? А нигде. Никому они не интересны. Остались на уровне гайдлайнов, как и в С++.

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


                            Ничего из этого намеренно сделано не было, никакой явности нет. Никто и никогда не расскажет о том, чем код в unsafe более явный, нежели С++. Как максимум всякие субъективные рассуждения на тему «мне так нравится», но каждый своё болото хвалит и никаких объективных предпосылок к этому нет.

                            Вот этот пассаж вообще не понял. Выше уже написал, что конкретно делает ансэйф.


                            Чего длиннее и почему — никто не расскажет и не покажет.

                            Ну да, для этого надо почитать код какого-нибудь контейнера на Rust и С++. Мой личный опыт — на С++, чтобы сделать всё правильно, писанины сильно больше.


                            В зачаточном состоянии она на расте — без unsafe ничего не напишешь.

                            Попробуйте написать итератор над любой нетривиальной структурй данных. С поддержкой всех нужных категорий. И сравните с как правило одним или двумя методами, которые надо реализовать для итератора на Rust. Я именно об этом.


                            Да, можно накастылить семантику указателя через ссылку + option, как это сделал автор, да и кто угодно сделает. Только есть нюансы — указатель хранит состояние бесплатно, а option платно. Привет оверхед по памяти 50-100% на ноду. Естественно, что в хелвордах это мало кому интересно, но это интересно за рамками оных.

                            Оптимизировано.
                            https://github.com/rust-lang/rust/blob/master/src/libcore/nonzero.rs
                            И да, известные мне реализации optional для С++ такую оптимизацию не поддерживают. Чем option+reference лучше голого указателя — отдельная тема.


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

                            Тогда зачем вообще комментируете эту часть? Если вы хотели пояснений — написали бы "Поясните". А не "Я не знаю, значит никто не знает, значит никому не нужно".

                            • –7
                              Ну тут типичный набор вранья, игнорирования и манипуляций.

                              Я определил пару основных тезисов — никакой компилятор ничего не гарантирует и ни к чему, о чём говорилось — не имеет( это было проигнорировано), никакой компилятор ни по каким «рукам» не даст. Это всё обычная лапша и обёртки, которые есть везде, с одной лишь разницей — разделение языка на unsafe/safe, но из этого так же ничего не следует. И это мой второй тезис.

                              Использование указателя с припиской unsafe ничем не отличается от использования указателя без неё. А все рассуждения об unsafe в конечном итоге сводятся с тому «так надо», а не «компилятор бьёт по рукам». Это так же было проигнорировано.

                              Я привёл пример с unwrap(), и сказу же указал на то, что опять будет слив на «стайлгайды» и «так надо», но декларируется не «делай как надо» — ведь «делать как надо» — никто не запрещает и в крастах. Евангелистами декларируется какое-то обязательство со стороны языка, и через это они и выделяют преимущество — там можно, либо нельзя, а у нас только «можно». Что неверно.

                              Далее, на вопрос по теме unsafe ответа так же не последовало. Опять пошла песня про «лучше видно», при этом никаких оснований этому нет. Чем unsafe видно лучше, нежели new — непонятно. И никто и никогда этого не расскажет.

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

                              Что это значит — неясно. Что из этого следует — неясно. Весь safe в расте — это примитивная stdlib, которая никакого отношения к языку не имеет. Аналогичное пишется на крестах и уже написано, при этом в крестах такой же safe.

                              Единственная разница в том, что в крестах unsafe пишется без приписки unsafe — на это разница заканчивается. Что-бы в расте появилась обёртка — её надо написать, точно так же, как и на крестах.

                              Что-бы в расте было safe — надо использовать эту обёртку. То же самое и в крестах.

                              Всякие рассуждения о статических проверках и прочем — это враньё и манипуляции. Если ты используешь в коде new — он ловиться статической проверкой, если используешь в расте unsafe new — это ловится тем же. Рассуждения о том, что unsafe можно вырубить — не котируются. Без unsafe ты ничего не напишешь.

                              Вот этот пассаж вообще не понял. Выше уже написал, что конкретно делает ансэйф.

                              Очередные попытки игнорировать реальность. О чём вы говорили? О том, что в расте что-то там выразительнее — вам сказали, что ваше утверждение ни из чего не следует. И вам нужно либо показать, что оно из чего-то следует, либо оно не следует и это просто трёп.

                              Попробуйте написать итератор над любой нетривиальной структурй данных. С поддержкой всех нужных категорий. И сравните с как правило одним или двумя методами, которые надо реализовать для итератора на Rust.

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

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

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

                              Оптимизировано.

                              Прикостылен костыль, и чем больше будет «оптимизацией» — тем больше будет таких мест. Хотя спустя 5-7лет прикостылили оптимизацию для такого элементарного случая, а таких случаев тысячи и тысячи.

                              А далее мы захотим использовать индексы вместо указателей — привет bound checking и прощай производительность, либо прощай bound checking и привет unsafe.

                              Тогда зачем вообще комментируете эту часть? Если вы хотели пояснений — написали бы «Поясните». А не «Я не знаю, значит никто не знает, значит никому не нужно».

                              Ну дак что же вы не пояснили? И опять же — очередные попытки врать и выдавать враньё за мои цитаты.

                              • +6
                                Единственная разница в том, что в крестах unsafe пишется без приписки unsafe — на это разница заканчивается.

                                Только это ключевая разница: в крестах код по умолчанию unsafe, в расте — по умолчанию safe.

                                Я, если что, не апологет раста и вообще нежно люблю кресты. И хаскель ещё.

                                А далее мы захотим использовать индексы вместо указателей — привет bound checking и прощай производительность, либо прощай bound checking и привет unsafe.

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

                                Вру, мешают невыразительные системы типов. Увы, языки, где это решено, считаются чересчур академическими.
                                • –8
                                  Только это ключевая разница: в крестах код по умолчанию unsafe, в расте — по умолчанию safe.

                                  Неверно. Очередная агитка, из которой ровным счётом ничего не следует. Никакого «умолчания» нету, вы придумали ахинею и повторяете её.

                                  Код в расте по умолчанию такой же unsafe, а safe он будет тогда, когда глобально вырубить unsafe, но тогда это будет жаваскрипт, а не «альтерантива» крестам. Но и опять же, а если я разработчик stdlib, то у меня получается unsafe раст? В stdlib safe не нужно?

                                  В конечном итоге всё сводится к проверке — написан ли у тебя unsafe код, а в каком виде этот unsafe код будет — в виде raw-pointer, либо unsafe raw-pointer — абсолютно неважно.

                                  Я, если что, не апологет раста и вообще нежно люблю кресты. И хаскель ещё.

                                  Не надо за апологетами повторять убогие тезисы. Они пустые и ничего не значат, из них ничего не следует.

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

                                  Это всё голубые мечты. Да и данный кейс не про обход в массиве.

                                  • +8
                                    Код в расте по умолчанию такой же unsafe, а safe он будет тогда, когда глобально вырубить unsafe, но тогда это будет жаваскрипт, а не «альтерантива» крестам.

                                    Эм, нет.

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

                                    Что вы будете грепать в плюсах?

                                    Не надо за апологетами повторять убогие тезисы. Они пустые и ничего не значат, из них ничего не следует.

                                    Я не за ними повторяю. Я просто по своему опыту знаю, что в хаскеле я могу прогрепать код на предмет unsafe-функций (их там условно три), а также на предмет error и undefined, и в чём-то убедиться. Могу даже с библиотеками так сделать, и, например, выкинуть clang-pure, ибо это говнокод какой-то с тамошним unsafePerformIO в каждой второй функции. А ещё Гугл в авторах, тоже мне.

                                    Это всё голубые мечты.

                                    Вполне выражаемые через зависимые типы.

                                    Да и данный кейс не про обход в массиве.

                                    А к чему было про bounds checking?
                                    • –7
                                      Вы можете прогрепать весь ваш код на тему unsafe

                                      Очередной идиотский тезис. Иди прогрепай stdlib на тему unsafe и расскажи об успехе, и да, по 100баксов вычти то же.

                                      Кстати, замечаем как меняется риторика. Вначале у нас было «по умолчанию безопасный», теперь, оказывается, что надо грепать. А т.к. ты 100% что-то нагрепаешь, то оказывается нужно проводить ручной анализ.

                                      Это типичный пример подмены понятий, вранья и манипуляций.

                                      Никакой лоулевел код невозможен без unsafe. Любой биндинг — unsafe.

                                      Что вы будете грепать в плюсах?

                                      Берёшь какой-нибудь clang-tidy и пишешь за пол часа набор правил. Что надо искать? */&/new — искать не сложнее, чем unsafe.

                                      Вполне выражаемые через зависимые типы.

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

                                      Т.е. есть безопасность, а есть бесплатная безопасность. Это разные вещи.

                                      А к чему было про bounds checking?

                                      bounds checking не заканчивается на обходе массива. Вернее он там и не нужен. Он нужен при доступах по индексу.

                                      Действительно, мы можем реализовать какие-то безопасные индексы, на С++ может это сделать статически( раст, конечно, ничего тут не сможет), только вот проблема с интеграцией в текущий safe-код, да и с реализацией есть проблемы.

                                      Нам нужно дать следующий блок? Как? Привет bounds checking. Мы же не будет в массив записывать весь набор index_t и ходить по нему?

                                      Как мы преобразуем порядок бита в битмапе к валидному индексу? Будем делать unsafe cast? Да, мы можем(предположим) сделать интерфейс safe, но реализация будет unsafe.

                                      В этом заключается типичная для всех пропагандистов раста подмена понятий, мы куда-то постоянно деваем реализацию. Вы это то же подтверждаете. Т.е. писать stdlib можно без safe? Писать биндинги можно без safe, писать любой лоулевел код — можно без safe? Его писать что-ли не надо? Как этому помогает раст? Никак.

                                      Есть два разных мира — мир написания обёрток и мир их использования. И если я разработчик stdlib раста, то никаких фишек он мне не даёт. Он даёт их тем, кто будет использовать эту stdlib, и то это спорно.

                                      Я не за ними повторяю. Я просто по своему опыту знаю, что в хаскеле я могу прогрепать код на предмет unsafe-функций (их там условно три), а также на предмет error и undefined, и в чём-то убедиться.

                                      Из этого ничего не следует. В крестовом коде может быть то же ноль указателей, ноль new и прочее. И потом вы это можете точно так же искать.

                                      Только вот проблема в том, что мир не заканчивается на ваших представлениях и вашем коде. Я вам уже приводил пример с stdlib раста. Идите и прогрепайте её. И так будет с любым лоулевел кодом.

                                      Да, всегда можно написать условно-безопасную обёртку, но её нужно написать. И её нужно и можно написать и в крестах. А можно не написать — и ничего не поменяется. Это можно сделать что на крестах, что в расте.
                                      • +5
                                        Иди прогрепай stdlib на тему unsafe и расскажи об успехе, и да, по 100баксов вычти то же.

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

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

                                        Потому что если вы прогрепали и не нашли unsafe, то он безопасный.

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

                                        Я в своём коде не писал unsafePerformIO ни разу. Так что нет, не «полюбас что-то нагрепаешь».

                                        Берёшь какой-нибудь clang-tidy и пишешь за пол часа набор правил. Что надо искать? */&/new — искать не сложнее, чем unsafe.

                                        Любой код с * и &? Прям все ссылки и указатели запретить? Успех!

                                        new? Ну буду писать вместо этого std::make_unique<Meh>().release(). Такой код от некоторых карго-культуристов я видел в проде.

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

                                        Не валялись по каким критериям?

                                        Нам нужно дать следующий блок?

                                        Этот пример я не понял.

                                        Как мы преобразуем порядок бита в битмапе к валидному индексу?

                                        Возьму Idris/Coq/ATS и докажу как теорему.

                                        В крестовом коде может быть то же ноль указателей, ноль new и прочее.

                                        Кресты без ссылок и указателей и раст без unsafe — это немножко разные вещи по выразительности и интероперабельности с внешними библиотеками.
                                        • –6
                                          В коде за авторством себя и прочих обычных программистов я лажу находил сильно чаще, чем в stdlib. В рамках данной дискуссии проблемами stdlib можно пренебречь и взять её корректность за аксиому.

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

                                          Большинство unsafe скрыто именно в сишном рантайме, который не переписан и никогда не будет переписан на раст. И даже не смотря на то, что основная часть айсберга невидна — наверх торчат тысячи и тысячи unsafe.

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

                                          Потому что если вы прогрепали и не нашли unsafe, то он безопасный.

                                          Каждый раз, когда мне кто-то говорит, что он не адепт — он адепт.

                                          Вам действительно так сложно в логику, что вы неспособны понять того, чем отличается «раст по умолчанию безопасный» и «раст безопасный после того, как мы его погрепаем и не найдёт unsafe», хотя их мы найдём.

                                          С вами дискутировать не имеет смысла, вы за 10строк 10 раз родите взаимоисключающие параграфы. Вы сами же опровергли свой тезис о том, что «раст по умолчанию безопасный», но продолжаете делать вид, что это не так.

                                          Любой код с * и &? Прям все ссылки и указатели запретить? Успех!

                                          Опять же, либо полная неспособность понять то, что вам пишут, либо неадекват. В расте нет указателей и это никого не заботит, а тут вдруг уже подавай указатели. & — это не ссылка, есличто.

                                          Ну и самое главное, я ведь не зря сказал про clang-tidy, но что-то вы это проигнорировали? Почему?

                                          new? Ну буду писать вместо этого std::make_unique().release(). Такой код от некоторых карго-культуристов я видел в проде.
                                          Для справки — результатом release() будет указатель, от которых мы выше отказались. Вы действительно не понимаете всей бессмысленности того, что пишите?

                                          Не валялись по каким критериям?

                                          По производительности и возможностей к написанию лоулевел кода.

                                          Возьму Idris/Coq/ATS и докажу как теорему.

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

                                          Кресты без ссылок и указателей

                                          Никто вам о ссылка не говорил. & — это не ссылка, а взятие адреса, а * — это разименование и тип переменной. Я не запрещал умножение и and.

                                          это немножко разные вещи

                                          Это одно и то же.

                                          по выразительности и интероперабельности

                                          Это вы только что придумали и это ни из чего не следует.

                                          с внешними библиотеками.

                                          Любая программа на расте, кроме хелвордов,, да и сам раст — это биндинги к С/С++. Что-то они пишут биндинги — напишите и вы. Никаких проблем нет.

                                          А захотите написать что-то новое — напишите без указателей, ведь на расте же пишут.

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

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

                                          И почему-то вы не задаётесь вопросом — какой смысл существования раста? Если нужно было бы добавить греп в С++ — можно было бы за месяц написать идеальный clang-tidy плагин, который бы находил всё это влёт.

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

                                          И тут основной вопрос заключается в том — чего вы хотите. Понять, либо убедить себя в том, что всё так, как вам рассказали. Объективная реальность — это сложно. Все уверены, что они не повторяют пустые тезисы, но именно это они и делают.

                                          • +9
                                            Да, вы правы. Различий между безопасностью и выразительностью языков нет, всё давно заляпано сишным неверифицированным ядром и libc. Бери хоть хаскель, хоть жс — всё будет так, исхода нет. Всё тлен. И проблему останова не решить, и арифметика Пеано не полна.
                                            • –5
                                              Слив засчитан, а далее пошла ахинея.

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

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

                                              То же самое и с libc — да, есть код и есть новомодные языки, которые претендуют на замену С/С++, но почему-то ни один из них не является самостоятельным, является нахлабучкой на С/С++ рантайм, на этих «языка» НИЧЕГО серьёзного не написано, кроме их компиляторов, хотя в случае раста и компилятора нет.

                                              И что получается. Ничего нет, но трёп о том, что «выкидывай С++ хоть завтра» — есть. Дак выкинь, продемонстрируй как надо. Нет — никто этого не делает и не сделает.

                                              Каждый год появляется новый убийца, каждый код появляются его адепты. В начале нулевых везде вещали о том, что жава все догнала и С/С++ ненужен. Потом релизнулся v8 и вещали о том, что С/С++ не нужен.

                                              До раста ГЦ был моден и быстр, да и сам раст на нём был. Не фортануло — выкинули, и вот он уже не моден и не быстр и без него быстрого языка не сделаешь. Хотя адепты раста — это хаскелисты/лисписты, которые буквально вчера любили ГЦ.

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

                                              Так всегда было есть и будет. Заходишь в тред по С++ и критикуешь кресты — тебя минусуют и «ты ничего не понимаешь». Эта слепая вера в то, что наш объект обожания идеален, что раз везде написано «проблем нет» — значит их действительно нет.

                                              Мне уже давно не интересно этим заниматься — я даже не знаю зачем я снова тут отвечают, ведь отвечаю я в пустоту.
                                      • +2
                                        Что вы будете грепать в плюсах?
                                        В плюсах в недалеком будущем можно будет прогнать проверку статическим анализатором c++ core guidelines и посмотреть все места, где он выдает предупреждения.
                                        • +4
                                          Осталось доказать, что Core Guidelines обеспечивают ту же безопасность, что система типов в расте.
                                          • +2

                                            Хорошая шутка) http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines Почитайте и поймете, что ту же безопасность не обеспечить не смогут.

                                            • –2
                                              Если вдруг не обеспечат (а я не знаю, ибо не особо знаком с системой типов в расте) и будет надо — выпустим новую версию core c++ guidelines, делов-то
                                              • +2
                                                Правила заимствования вы в core c++ guidelines не внесете в любом случае
                                                • +3

                                                  Как и трейты Sync и Send. Ну то есть вы можете все это внести, но тогда получится Rust.

                                    • –3
                                      Забыл ещё один пример привести, кроме unwrap — это immutable. У нас так же везде immutable, но в реальности же — везде mut через mut, при этом в очередной раз мы услышим отмазку про то, что «а вот в реальных проектах». И какой бы код ты не показывай — везде ответ один «это неправильный код», при этом мы сразу же забываем о том, что «компилятор должен бить по руками» и «гарантии».

                                      При этом, когда мы говорим о крестах — у нас почему-то этот аргумент пропадает. И почему я не могу сказать «а вот в реальном коде new чрез new никто не использует», и это действительно так.

                                      Мы берём ссылки и сравниваем их с указателями, будто бы в крестах ссылок нет. Мы сравниваем то, что удобно нам, игнорируя то, что неудобно.

                                      И так везде — никто не скажете того, чем строки в расте более safe, нежели в крестах. Мы всегда съезжаем на тему «а можно их не использовать», дак ведь в расте их то же можно не использовать.
                                      • +12

                                        "Я бежала пять миль чтобы сказать вам как вы мне безразличны"


                                        Я бы может ответил вам на ваши тезисы. Но судя по тому, как вы их подаёте, вы банально не владеете вопросом, уж простите за столь грубый пассаж. Чтобы ответить на всё это, мне пришлось бы цитировать вам главы из документации, где всё это объяснено. Держите ссылки:



                                        Пара комментариев:


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

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


                                        Это всё обычная лапша и обёртки, которые есть везде, с одной лишь разницей — разделение языка на unsafe/safe, но из этого так же ничего не следует.

                                        Изучите вопрос. Ссылки выше в комментарии.


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

                                        http://en.cppreference.com/w/cpp/iterator
                                        Просвещайтесь.


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

                                        В этом топике речь если шла, то о неидиоматичном коде. И вы, кстати, ни одного примера кода не привели.


                                        Мы берём ссылки и сравниваем их с указателями, будто бы в крестах ссылок нет. Мы сравниваем то, что удобно нам, игнорируя то, что неудобно.

                                        http://en.cppreference.com/w/cpp/language/reference
                                        Почитайте, чем отличается.

                                        • –9
                                          Как всегда, ничего нового. Ничего конкретно, одни обвинения «ты ничего не знаешь», «да иди читай и просвещайся» — зачем вы живёте? Какой смысл в вашем существовании и пребывании тут? Спастить мне ссылку? Просто так меня обвинять?

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

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

                                          Это не более чем манипуляции. Компилятор компилирует код с unsafe? Компилирует. На это финиш.

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

                                          Это в жаве я не могу написать неправильный код( и то я даже не уверен в этом), но в данном случае все гарантии — это гарантии программиста.

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

                                          Изучите вопрос. Ссылки выше в комментарии.

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

                                          en.cppreference.com/w/cpp/iterator
                                          Просвещайтесь.

                                          И опять — ответов нет. К чему и что следует из этой ссылки, к чему вы её спастили и какие мои слова она опровергает — неясно. Недостаточно просто кинуть ссылку — надо связать её с контекстом и вывести следствие. У вас нет ни того, ни другого.

                                          Категории итераторов — это расширение функционала и не более того. Если вы в расте взяли и определили, что у нас итератора есть InputIterator, то руководствуйтесь и в С++ такой же логикой. InputIterator такой же итератор — всё остальное — сверху.

                                          Вы не получите поведение аналогичное другим категориям, реализую одну/две функции. Будь то С++, раст, пхп, либо что угодно.

                                          В этом топике речь если шла, то о неидиоматичном коде. И вы, кстати, ни одного примера кода не привели.

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

                                          Вы говорите «раст, компилятор даст по рукам», а в следующем ответе уже говорите иное. Оказывается проверять ошибка не обязательно, иммутабельность — не обязательно, safe — не обязательно. И вся аргументация сводится к тому, что «а просто не пиши unsafe» — дак я то же самое могу делать и в С++.

                                          Я могу точно так же нарушить unsafe как угодно, и осознания того, что это «неправильный код» — ничего мне не даст. И никакой компилятор мне ничего не скажет.

                                          en.cppreference.com/w/cpp/language/reference
                                          Почитайте, чем отличается.

                                          И опять то же самое. Зачем мне кидать ссылки? Что из них следует? Даже предположим, что отличия есть( на самом деле тут уже подлог, ведь в С++ ссылки — это конструкции языка, а в расте — просто обёртки и сравнивать их некорректно. Я могу сделать какую угодно обёртку и назвать её ссылкой), то что из этого следует?

                                          Вы понимаете, что нельзя просто так взять и сказать «разница есть». Ведь смысл не в разнице, а в том, что из неё следует. И это следствие вы не вывели.

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

                                          Вот и тут то же самое. Вам указали на то, что сравнивать голые указатели в С++ и ссылки в расте — некорректно. Вы поплыли в сторону «они различаются» — различаются, дальше что?
                                          • +10
                                            Ведь вы никогда мне не скажете и не покажете — где я не прав, в чём я не прав, что и почему мне нужно смотреть. Вы где-то прочитали тезис, но не можете его аргументировать, и именно поэтому как только у вас возникают проблемы — вы пытаетесь снять с себя ответственность.

                                            По существу у вас возражения есть?


                                            Это не более чем манипуляции. Компилятор компилирует код с unsafe? Компилирует. На это финиш.

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


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

                                            Но для начала неплохо бы владеть предметом. А для этого надо немножко почитать.


                                            И опять — ответов нет. К чему и что следует из этой ссылки, к чему вы её спастили и какие мои слова она опровергает — неясно. Недостаточно просто кинуть ссылку — надо связать её с контекстом и вывести следствие. У вас нет ни того, ни другого.

                                            Эта ссылка была дана вам чтобы показать, что в С++ реализация итератора сводится далеко не к одному перегруженному оператору "++". Даже для простого InputIterator.


                                            Категории итераторов — это расширение функционала и не более того. Если вы в расте взяли и определили, что у нас итератора есть InputIterator, то руководствуйтесь и в С++ такой же логикой. InputIterator такой же итератор — всё остальное — сверху.

                                            В Rust нет категорий итераторов в понимании С++.


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

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


                                            Даже предположим, что отличия есть( на самом деле тут уже подлог, ведь в С++ ссылки — это конструкции языка, а в расте — просто обёртки и сравнивать их некорректно. Я могу сделать какую угодно обёртку и назвать её ссылкой), то что из этого следует?

                                            Эта фраза чётко говорит о том, что вы не понимаете, что такое ссылки в Rust. Уважьте, перейдите по ссылкам и прочтите.


                                            Вы понимаете, что нельзя просто так взять и сказать «разница есть». Ведь смысл не в разнице, а в том, что из неё следует. И это следствие вы не вывели.

                                            Смысл в разнице, т.к. из неё следует тот самый вывод. А разница в том, что в С++ ссылка не может быть перемещена и переназначена — что делает её применение сильно ограниченным. Что в Rust ссылка может менять указуемый объект, может быть перемещена и может свободно храниться в поле структуры, не "пригвождая" структуру к одному месту. И что не может пережить объект, на который указывает.


                                            Вот и тут то же самое. Вам указали на то, что сравнивать голые указатели в С++ и ссылки в расте — некорректно. Вы поплыли в сторону «они различаются» — различаются, дальше что?

                                            Как раз корректно. Ссылки в Rust несут ту же нагрузку, что и в С++ — и в дополнение бОльшую часть нагрузки указателей.


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


                                            Засим откланиваюсь.

                                            • –9
                                              По существу у вас возражения есть?

                                              Возражения на что? У вас нет «по существу» ничего, и вам об этом уже сообщили. А возражать «по существу» на нечто — нельзя. «существу» нет.

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

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

                                              Причины того, почему он не настучит — мне не интересны и к делу отношения не имеют, мне важен факт «не настучит», а значит вы соврали — всё просто.

                                              Но для начала неплохо бы владеть предметом. А для этого надо немножко почитать.

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

                                              Вот у вас есть требования какого-то «предмета», но почему и на каком основании вы его требуете — у вас ничего этого нет. Это обыкновенный слив и ничего более.

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

                                              Эта ссылка была дана вам чтобы показать, что в С++ реализация итератора сводится далеко не к одному перегруженному оператору "++". Даже для простого InputIterator.

                                              Во-первых, об этом никто не говорил. А во-вторых — en.cppreference.com/w/cpp/concept/InputIterator По это же ссылке чётко указанно — что нужно. ++/*/== — всё, при этом всё это необходимо для итератора.

                                              В Rust нет категорий итераторов в понимании С++.

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

                                              И это функционал к С++ отношения не имеет. Хочешь быстрое +10 — никто не будет долбить ++ 10раз. А хочешь долбить 10раз — что-то кроме InputIterator тебе не нужно и С++ никак к этому не обязывает.

                                              В конечном итоге — что мы имеем? В руста * и ++ — это одна функция, а в С++ — две. Поэтому в С++ нужно реализовать лишь ==, что в ходит в первоначально определение «одну-две функции».

                                              Я даже не знаю, что на это ответить. «Безопасность уровня программиста» это как? Не давать программисту нажимать неправильные кнопки? Rust даёт определённые средства отлова и устранения определённых классов ошибок. И даёт средства локально отключать некоторые из этих ограничений — если того требует задача.

                                              Раст ничего не даёт, вы никогда не покажете то, что он даёт и причина проста — это невозможно.

                                              Отключить глобально unsafe вы не можете, а значит оно будет включено, а значит все гарантии — гарантии уровня рантайм/обёрток/etc, но никак не языка. Все те же гарантии реализуются на С++.

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

                                              Эта фраза чётко говорит о том, что вы не понимаете, что такое ссылки в Rust. Уважьте, перейдите по ссылкам и прочтите.

                                              Как всегда я вижу одно и тоже. Бесполезный трёп из которого ровным счётом ничего не следует. Ну и самое главное, вы нигде мне не рассказали — что именно я не понимают, что именно в моей логике ошибочно, но и самое главное — с чего я вообще должен понимать непонятно что.

                                              Смысл в разнице, т.к. из неё следует тот самый вывод. А разница в том, что в С++ ссылка не может быть перемещена и переназначена — что делает её применение сильно ограниченным.

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

                                              В расте нет ссылок — в расте есть обёртка в stdlib. Никаким образом эту обёртку нельзя сравнивать с конструкцией языка.

                                              Что в Rust ссылка может менять указуемый объект, может быть перемещена и может свободно храниться в поле структуры, не «пригвождая» структуру к одному месту. И что не может пережить объект, на который указывает.


                                              В расте нет ссылок, повторю это ещё раз. Подобное поведение реализуется и на С++, если нужно. И никаким образом дефолтные ссылки это не ограничивают.

                                              Как раз корректно. Ссылки в Rust несут ту же нагрузку, что и в С++ — и в дополнение бОльшую часть нагрузки указателей.

                                              Неверно. Нельзя сравнивать обёртку над указателем и ссылку, у которой есть чёткая семантика. Нельзя выдавать своё «хочу» за правильно.

                                              С чего вы взяли, что ваша «ссылка», которая «не имеет ограничений» лучше, нежели ссылка, которая их имеет? Для меня — лучше та, которая имеет?

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

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

                                              Я могу показать где и что вы игнорировали, но вы не сможете показать ни одного. Почему? Потому что это пустой трёп.

                                              По поводу ваших обвинений в отрицании — это подмена понятий. Это стандартный приём, когда мы выкатываем тезис и наделяем его нужным нам следствием. Например — «у вас ник на T — вы идиот». И далее играть в игру «но ведь он на T».

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

                                              • –5
                                                Поясню на примере.

                                                Что в Rust ссылка может менять указуемый объект,

                                                Вот тут мы объявлением тезис, что ссылка чего-то там не может. Но тут есть ошибка, мы берём какое-то свойство, говорим о том, что оно есть. И далее делаем вывод о том, что «лучше», хотя из наличия какого-то свойства «лучше» не следует.

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

                                                Что из этих требований следует? Ничего.

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

                                                Ну и самое главное — почему мы ссылку в расте сравниваем с ссылкой в С++, а не с std::reference_wrapper? А причина проста — мы мало того, что делаем сравнения из которых ничего не следует — мы заведомо ограничиваем одну из сторон сравнивая несравнимое.

                                                В конечном итоге всё сводится к тому, что везде всё должно быть как в расте. Почему? Просто так. Хочешь как в расте — сделай как в расте, это С++ позволяет. Но путём подмены понятий, мы выдаём за альтернативу в С++ ссылку(&), а на самом деле не должны ничего выдавать — ведь в расте этого попросту нет.

                                                В конечном итоге «ссылка» в С++ может быть такой же, как «ссылка» в расте. С единственной разницей в том, что там она будет в stdlib, а в С++ нет( и то не факт). Но из этого ровным счётом ничего не следует — ведь никаких свидетельства за то, что надо так, а не иначе — нет.

                                                Поэтому, стандартная реализации из С++ имеет та кое же право на существование, как и реализация раста. А если необходим функционал «как в расте» — он реализуется, да и уже есть.

                                                • +7

                                                  Извините, а с чего вы взяли, что в Rust ссылки являются «обёртками в stdlib», а не конструкцией языка?

                                            • –3
                                              Ну и вы полностью проигнорировали всё, я вам повторил свои два тезиса, вдруг я первый раз сформулировал это непонятно. Но вы опять их проигнорировали. И тут уже нельзя сослаться на то, что вы сделали это не специально — нет, вы делаете это специально.

                                              Я вам сказал про пример оверхеда на option, вы мне показали, что спустя 5-7 лет существования языка — кастыль впилили. Но ведь кастыль ничего не значит — это показывает фундаментальную дыру, когда у нас есть дырявые абстракции, для которых мы каждый раз должны нарушать логику, нарушать инкапсуляцию.

                                              И я вас привёл следующий пример, который вы проигнорировали. Есть проблемы те, которые решает раст, но есть множество проблем, которые он производит. При этом, большинство эти решений — это решения не уровня языка, а уровня программиста, хотя почему-то раст их декларирует как решения уровня языка. Что странно.

                                              Вся эта тема — это сплошные манипуляции. В stl есть много мест для совместимости с си, которые «не безопасны», но они там взялись не из-за того, что С++ — это «100% небезопасно», а из-за того, что есть множество сишного кода, который нужно интегрировать.

                                              То же самое происходит и с растом — раст без unsafe не позволяет интегрировать в себя сишный/крестовый код, а раст без него попросту существовать не может. llvm, libc, шланговые рантайм для исключений и прочее и прочее. Сколько биндингов к сишному коду в том же серво?

                                              Каким образом этот коде безопасней? Никаким. В идеально мире, где у нас весь код расте/смартпоинтерах — всё хорошо, и в хелвордах на ресте — то же всё хорошо, как и в хелвордах на смартпоинтерах. Но реальность она сложнее.

                                              И не стоит заниматься этими манипуляциями и подменой понятий, сравнивая несравнимое. Это хорошо звучит, люди любят популизм, но далеко на нём не уедешь.
                                              • +6

                                                Ну попробуй со мной похоливарить. Rust — это язык с контролируемой возможностью отключения гарантий и фич.


                                                1)


                                                Я вам сказал про пример оверхеда на option, вы мне показали, что спустя 5-7 лет существования языка — кастыль впилили.

                                                Он тебя дезинформировал, увы. Кроме Option было оптимизировано ну просто много чего. http://camlorn.net/posts/April%202017/rust-struct-field-reordering.html.
                                                Хочешь отключить, чтобы сохранить прямой порядок для взаимодействия с C — используй repr(C). Приятно иметь такую оптимизацию из коробки, не правда ли?


                                                2)


                                                в реальности же — везде mut через mut

                                                Может, ты перепутал c ключевым словом unsafe? Я еще не видел людей, которые бы хаяли изменяемость данных :D


                                                Константность — это тоже фича(снимается добавлением ключевого слова mut). Константность, которую не предоставляет C++, в котором есть псевдо, которая легко снимается const_cast, либо заметается под ковёр ключевым словом mutable либо очень "очевидным" implicit кастом const T -> T&&, который, вообще-то, мутабельный.


                                                3)


                                                раст без unsafe не позволяет интегрировать в себя сишный/крестовый код, а раст без него попросту существовать не может

                                                Может =)


                                                4)


                                                разделение языка на unsafe/safe, но из этого так же ничего не следует.

                                                Звучит как "The flat earth society has members all around the globe".

                                                • +1
                                                  Может =)

                                                  А можно ссылочку на документацию? Меня интересует эта тема.

                                                • –5
                                                  Кроме Option было оптимизировано ну просто много чего.


                                                  Ничего. Это капля в море. Да и опять же, дело не в этом — дело в том, что все это прошлогоднее, хотя о мистической оптимальности рассуждалось десять лет.

                                                  Приятно иметь такую оптимизацию из коробки, не правда ли?

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

                                                  Да и это хелворд, цена которому ноль.

                                                  Может, ты перепутал c ключевым словом unsafe? Я еще не видел людей, которые бы хаяли изменяемость данных :D

                                                  Я про то, про что я.

                                                  Константность — это тоже фича(снимается добавлением ключевого слова mut).

                                                  Это не просто фича, это хайпилось как основной локомотив «безопасности», а теперь уже «просто фича». Хотя и safe — это просто фича.

                                                  Константность, которую не предоставляет C++, в котором есть псевдо, которая легко снимается const_cast, либо заметается под ковёр ключевым словом mutable либо очень

                                                  safe, которую не предоставляет rust, в котором есть псевдо, которая легко снимается unsafe.

                                                  «очевидным» implicit кастом const T -> T&&, который, вообще-то, мутабельный.

                                                  Очевидный кому? Никакой const T в T && не превратится — в T входит const. Поэтому это будет const T &&&, для константных lvalue.

                                                  Кстати, про constexpr в расте расскажешь?

                                                  Может =)

                                                  Не может, иначе бы unsafe был выключен. Загляни в свою stdlib, либо в любую, не хелвордистый, код — ты там увидишь unsafe через unsafe.

                                                  Звучит как «The flat earth society has members all around the globe».

                                                  Опять какая-то ахинея и ноль аргументации.

                                                  В крестах так же есть разделение unsafe/safe, только оно не декларируется через unsafe. На это различия заканчиваются. А декларация unsafe как unsafe не имеет смысла, вернее ни ты, ни кто-либо ещё никогда о нём не расскажет.

                                                  Написания raw-pointer и unsafe raw-pointer ничем друг от друга не отличаются, ведь raw-pointer итак unsafe в любом его проявлении.
                                                  • +7

                                                    Подожди. Давай будем объективны.


                                                    1)


                                                    Ничего. Это капля в море.
                                                    Какой коробки и какую оптимизацию?

                                                    Я отвечу на эти вопросы не тебе, но будущим читателям, потому что ты поленился пройти по ссылке и ознакомиться с предметом. Садись, два, плохой тролль, плохой!


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


                                                    Пример не оптимальной структуры(C++, занимает 12 байт)


                                                    struct MyStruct
                                                    {
                                                      uint8_t var0;
                                                      uint32_t var1;
                                                      uint8_t var2;
                                                      uint8_t var3;
                                                      uint8_t var4;
                                                    };

                                                    Но можно оптимизировать структуру, переставив 0 и 1 поле местами(C++, теперь структура занимает 8 байт):


                                                    struct MyStruct
                                                    {
                                                      uint32_t var1;
                                                      uint8_t var0;
                                                      uint8_t var2;
                                                      uint8_t var3;
                                                      uint8_t var4;
                                                    };

                                                    Таким образом, потребление памяти данной структурой снижается на 33.3% без потери информации. Впечатляющий результат! Но Rust коду не требуется ручного вмешательства, компилятор это делает сам(Rust, выведет sizeof MyStruct 8):


                                                    struct MyStruct
                                                    {
                                                      var0: u8,
                                                      var1: u32,
                                                      var2: u8,
                                                      var3: u8,
                                                      var4: u8,
                                                    }
                                                    
                                                    fn main() {
                                                      println!("sizeof MyStruct {}", std::mem::size_of::<MyStruct>());
                                                    }

                                                    Эту оптимизацию и встроили в коробку, т.е. в компилятор Rust. Он это делает за тебя. C++ так не может и не сможет, потому что считается, что каждая структура в C++ обязана маппиться на C. Это древнее наследие, которое никак не сломать.


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


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


                                                    2)


                                                    Это не оптимизация, а костыльная оптимизация, которая закрывает изначальную дыру, которой нет в крестах и там эта «оптимизация» не нужна.

                                                    Мой развернутый ответ в 1 пункте покрывает это утверждение с лихвой.


                                                    3)


                                                    Константность — это не просто фича, это хайпилось как основной локомотив «безопасности», а теперь уже «просто фича».

                                                    Звучит неубедительно, предоставь пруфы.


                                                    4)


                                                    Что надо искать? */&/new — искать не сложнее, чем unsafe.
                                                    Хотя и safe — это просто фича.
                                                    safe, которую не предоставляет rust, в котором есть псевдо, которая легко снимается unsafe

                                                    Ты думаешь, что внутри unsafe происходит что-то ну совсем страшное. На самом деле, там пишут то, что пишут в обычном С++. Весь код вокруг unsafe проверяет компилятор, а внутри unsafe проверяет сам программист. Если ты накосячил внутри unsafe — это твои проблемы. Тебя предупреждали. Программисты C++ любят повторять: "Писать безопасный код на С++ можно, для этого надо придерживаться определенных правил", ну так вот, этих правил и стоит придерживаться внутри unsafe. И всего лишь. Для желающих узнать больше.


                                                    5)


                                                    «очевидным» implicit кастом const T -> T&&, который, вообще-то, мутабельный.

                                                    Прости, был не прав. C++ предпочитает мутабельное инстанцирование иммутабельному. Спонсор западни — Константин Крамлин из Яндекса. Как много опытных плюсоидов завалилось на этом примере, просто жуть.


                                                    6)


                                                    Кстати, про constexpr в расте расскажешь?
                                                    Да, конечно.

                                                    7)


                                                    То же самое происходит и с растом — раст без unsafe не позволяет интегрировать в себя сишный/крестовый код, а раст без него попросту существовать не может.
                                                    Может =)

                                                    Моё утверждение, что Rust может существовать без C или C++ непоколебимо. Что в вопросе, то и в ответе =)

                                                    • –9
                                                      Я отвечу на эти вопросы не тебе, но будущим читателям, потому что ты поленился пройти по ссылке и ознакомиться с предметом. Садись, два, плохой тролль, плохой!

                                                      Я не буду это комментировать — нужны публике клоуны — пусть будут клоуны.

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

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

                                                      godbolt.org/g/4cC91L

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

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

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

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

                                                      Звучит неубедительно, предоставь пруфы.

                                                      Иммутабельности по умолчанию уже достаточно, ну и основа хайпа mt-safety, а он базируется на иммутабельности.

                                                      Ты думаешь, что внутри unsafe происходит что-то ну совсем страшное. На самом деле, там пишут то, что пишут в обычном С++.

                                                      Никакого «обычного С++» не существует. Это не имеет смысла. В unsafe используются небезопасные конструкции, которые являются такими же небезопасными и в С++.

                                                      Весь код вокруг unsafe проверяет компилятор

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

                                                      Тебя предупреждали. Программисты C++ любят повторять: «Писать безопасный код на С++ можно, для этого надо придерживаться определенных правил», ну так вот, этих правил и стоит придерживаться внутри unsafe. И всего лишь. Для желающих узнать больше.

                                                      Опять какой-то бред.

                                                      Никакого «безопасного» кода в С++ не существует. В С++ так же существуют безопасные и небезопасные конструкции и эти правила относятся к небезопасным.

                                                      Написание безопасного кода в С++ — это не правила написания unsafe кода — это правила написания safe кода, который ничем не отличается от раста.

                                                      Единственная разница между С++ и растом — это в том, что для написания указателя — в С++ тебе не нужно писать unsafe, а в расте нужно. Но из этого ровным счётом ничего не следует.

                                                      Единственный аргумент, который мне смогли предоставить — это «unsafe проще найти грепом», но никто в здравом уме код не грепает. Для этого существуют статические анилизаторы, для которых ты пишешь за пол часа правило и он тебе находит все использования указателей в коде.

                                                      Прости, был не прав.

                                                      Для меня это не новость.

                                                      C++ предпочитает мутабельное инстанцирование иммутабельному.

                                                      То, что «ты» написал — это не «мутабельное инстанцирование » — ты сам придумал эту ахинею. Оно УНИВЕРСАЛЬНОЕ. Читай по слогам до просветления.

                                                      Зачем ты в «иммутабельномое инстанцирование» передаёшь не иммутабельный объект? В этом твои проблема, что делаешь непонятно что и непонятно зачем. Если ты передашь иммутельный, то у тебя будет первая перегрузка.

                                                      Да, конечно.

                                                      Это не constexpr, а хрен пойми что. Никакого описания того, что оно может и зачем существует — нет.

                                                      Моё утверждение, что Rust может существовать без C или C++ непоколебимо.

                                                      Нет, то, что не соответствует реальности — является бредом.

                                                      А того, что у тебя, в мире хелвордов, может существовать какой-то раст без С/С++ — из этого ровным счётом ничего не следует, ведь это не общий случай.

                                                      В мире существуют, на самом деле их почти нет, «реальные» проекты на расте и ни один из них без С/С++ не существует. Начиная от компилятора раста, заканчивая его stdlib, сервой и прочим.

                                                      Но ты можешь им помочь и спасти их от С/С++, а то видишь как — они не могут, а ты можешь. Нужно срочно пойти и их научить.

                                                      • +6
                                                        godbolt.org/g/4cC91L
                                                        А тут мы видим рядовую картину того, что рядовой эксперт — это просто ретранслятор того, что он где-то увидел, либо прочитал в интернете. Он ретранслирует какую-то ахинею, и казалось бы — ну пойди ты и проверь, но нет.

                                                        Ну и?


                                                        movl $8, %edx

                                                        Что является подтверждением моих слов.


                                                        https://godbolt.org/g/idN4Uz


                                                        movl $12, %edx

                                                        Опять всё, как я сказал. Ты бы и рад соврать, да никак не получается, да? Обидно, небось)))

                                                        • +6

                                                          Факты:



                                                          И еще один факт: ты тролль, который апеллирует к субъективным величинам. Вон из профессии.

                                                          • –6
                                                            Мне было интересно — побежит ли этот клоун обвинять меня, делая вид, что мы обсуждаем только один тезис.

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

                                                            А вы не отвлекайтесь, минусуйте/плюсуйте — ведь вам же не интересно то, кто говорит что-то сознанное, а кто просто трепится и пытается свой слив замаскировать под «ты тролль, я с тобою не играю».
                                                          • –3
                                                            Ты бы и рад соврать, да никак не получается, да? Обидно, небось)))

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

                                                            godbolt.org/g/GxbPZa

                                                            Давай я чутка тебя расстрою. Ну ты это, давай, побольше скобочек.
                                                            • +5
                                                              Warning: the -fpack-struct switch causes GCC to generate code that is not binary compatible with code generated without that switch. Additionally, it makes the code suboptimal.  Use it to conform to a non-default application binary interface.

                                                              Т.е. о чем я и говорил. Ломает древнее наследие совместимости с C. Глобально.


                                                              Объясни мне, в чем толк этой оптимизации C++, от которой код падает? Как ты можешь передать указатель на структуру во внешний C код, если у тебя внутри порядок изменился?

                                                              • –4
                                                                Т.е. о чем я и говорил.

                                                                Ни о чём ты не говорил. Ты как всегда сел в лужу, но пытаешься как-то из неё всплыть.

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

                                                                Объясни мне, в чем толк этой оптимизации C++, от которой код падает?

                                                                Никакой код не падает. Иди передай мне во внешний код, собранный растом до оптимизации.

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

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

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

                                                                По поводу твоих супер-раст оптимизаций. Мне лень гуглить, но ir умеет структуры и скорее всего раст их форвардит в ir, а ir — это llvm.org/doxygen/group__LLVMCCoreTypeStruct.html Т.е. судя по всему шланг так же форвардит упаковку на llvm.

                                                                И очень высока вероятность того, что великие оптимизаторы раста просто включили флажок в llvm и раструбили это среди адептов как «супер-оптимизацию». Это не ново.

                                                        • +4
                                                          Он это делает за тебя. C++ так не может и не сможет, потому что считается, что каждая структура в C++ обязана маппиться на C.

                                                          Технически, наверное, имеет право для то ли не POD-, то ли не trivially layout-типов (там определения с каждым стандартом меняются, не знаю точно).


                                                          C++ предпочитает мутабельное инстанцирование иммутабельному.

                                                          Какой-то странный пример, эквивалентный двум перегрузкам с const T& и T&. Чего там удивительного?

                                                          • +1
                                                            Технически, наверное, имеет право для то ли не POD-, то ли не trivially layout-типов (там определения с каждым стандартом меняются, не знаю точно).

                                                            Звучит логично. Только в примере от phponelove MyStruct — POD, и представлление этой структуры было оптимизировано.


                                                            godbolt.org/g/GxbPZa

                                                          • +3
                                                            Прости, был не прав. C++ предпочитает мутабельное инстанцирование иммутабельному. Спонсор западни — Константин Крамлин из Яндекса. Как много опытных плюсоидов завалилось на этом примере, просто жуть.

                                                            Интересно, а почему компилятор должен предпочитать обратное когда параметр — мутабельная переменная?

                                                            • +4

                                                              https://youtu.be/oXw2vXOUr1g?t=29m23s


                                                              Понимаешь, в чем дело, на зал из 100 человек правильно ответили 5. Это спустя 3 года работы с C++14 тебе эти вещи кажутся очевидными, но они контринтуитивны.

                                                              • +4
                                                                На самом деле контринтуитивна тут вовсе не константность, а универсальная ссылка. Такое ощущение, что универсальные ссылки появились в языке совершенно случайно — но появившись всем сразу так понравились что их оставили.

                                                                По хорошему, для них нужно было придумывать какой-нибудь отдельный синтаксис и не перегружать &&.

                                                                Я вот все еще никак не могу привыкнуть что Bar && — это ссылка на временный объект (простите, rvalue reference), а ID && — универсальная ссылка.
                                                                • +2
                                                                  Причём работает всё равно через пень-колоду. Синтаксиса для того, чтобы сделать универсальную ссылку на «немножко специализированный» тип, вроде, не знаю,

                                                                  template<typename T>
                                                                  void doSmth(std::variant<T>&&); // хочу универсальную ссылку тут
                                                                  


                                                                  нет.
                                                        • +3
                                                          Я еще не видел людей, которые бы хаяли изменяемость данных

                                                          Ну я, например. Я чситаю важным достижением Rust, что он поощеряет иммутабельность, а мутабельность заставляет указывать явно.
                                                  • +1
                                                    Попробуйте написать итератор над любой нетривиальной структурй данных. С поддержкой всех нужных категорий.

                                                    Бустовскими адаптерами/фасадами для итераторов можно пользоваться?
                                                    • +3

                                                      Да я как бы про них в курсе. Меня смущает ситуация, когда стандартная библиотека построена таким образом, что для решения созданных ею проблем требуется или писать кучу бойлерплейта, или втаскивать стороннюю библиотеку в несколько раз большего объёма. Что не всегда возможно. И никаких изменений уже лет 20 в этом направлении. ranges-v3 может и поменяют ситуацию, но последний раз когда я в них лазил, я не нашёл адапторов, аналогичных бустовским.

                                                      • +2
                                                        Я бы сказал, что не в библиотеке дело (если вы достаточно часто пишете итераторы, то я вам искренне завидую — интересные задачи решаете, небось), а в том, что трейты (ну или каноничные тайпклассы из более упоротых языков) являются более общим и более удобным средством.
                                    • +2
                                      У вас в С++ даже ассерты не ушли, надо -DNDEBUG добавить чтобы они исчезли. Код ужасно оптимизирован. Кстати в clang, rust использует то же AST дерево, так что добиться на С++ той же оптимизации обычно можно, я другого не встречал. А вот в обратную, как я ни старался, у меня не получалось в низкоуровневом коде. Там есть проблемы, например пока отсутствует alloca.
                                      • +5
                                        Про -DNDEBUG спасибо! Я обязательно исправлю этот недочет. Возможно, цифры изменятся.

                                        Код ужасно оптимизирован

                                        Этот комментарий нам никак не поможет, напишите конкретное место и как его улучшить.
                                        • +1
                                          Ну тут довольно много писать просто придется. Попробую которко. Во первых указателями на ноды оперировать слишком жирно, особенно в x64. В классисеском оптимизированном варианте там либо 2 таблицы lson\rson заводят, либо вообще одну state, где кодируют признак листа и смещения. Второй основно хак — это входные биты слать от старшего в байте\инте или 64бит регистре, и ипользовать что то типа add eax, eax для вытеснения бита в carry и использовать инструкцию adc без бранчей. Потом к текущему стейту прибавляем этот adc (обычно умноженный на 2\4), делаем запрос в таблицу state по вычисленному смещению, выясняем лист ли это, если да — то output byte, если нет то циклимся. Все, внутренний цикл очень короткий. Надо оптимизировать си код, пока инннер луп не будет выглядеть близким к тому что надо, это можно сделать, я проделовал чтобы было хорошо под всеми мажорными компиляторами.

                                          Сходу совсем хороших примеров не найду, но нашел вот это www.virtualdub.org/blog/pivot/entry.php?id=205 специфический кейс для yuv.
                                          • +1
                                            Этот комментарий нам никак не поможет

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

                                            Замена подсчёта частоты на нормальный — уже в 10раз быстрее. И оптимизация всё ещё даже не начиналась.

                                            struct string {
                                              
                                              string() {}
                                              
                                              string(const std::string & str) {
                                                if(str.length() > data.size()) throw std::runtime_error{"bad length"};
                                                len = str.length();
                                                strncpy(std::data(data), std::data(str), str.length());
                                              }
                                              
                                              operator std::string_view() { return {std::data(data), len};}
                                            protected:
                                              std::array<char, 32 - 1> data;
                                              uint8_t len = 0;
                                            };
                                            
                                            struct encoder {
                                              encoder(const std::unordered_map<char, std::string> & map) {
                                                for(size_t i = 0; i < m.size(); ++i) {
                                                  auto it = map.find(i);
                                                  m[i] = (it != std::end(map)) ? it->second : "";
                                                }
                                              }
                                              
                                              std::string_view encode(char c) {
                                                return m[uint8_t(c)];
                                              }
                                              
                                              std::string encode(const std::string & str) {
                                                std::string out;
                                                out.reserve(str.length() * 32);
                                                for(auto & x : str) {
                                                  out += encode(x);
                                                }
                                                return out;
                                              }
                                              
                                              std::array<string, 256> m;
                                            };
                                            


                                            Ничего особо не проверял — если работает, замените им своё.

                                  • 0
                                    Для наглядности было бы неплохо добавить голый C. И сравнение с -O2 и -O3. И clang для C/C++. А то в данном случае не совсем понятно, что с чем сравнивается.
                                    • +10
                                      Есть некоторые недочеты в статье, один из них — Option — это не «типа исключение», а вполне себе отдельный тип для своих нужд. В качестве типа для содержаний ошибки он никогда не используется, так как для этого есть тип `Result`. Далее, дурить borrow checker не нужно, он в 95% случаев все правильно говорит, и городить приходится не «хитрый» код, а именно *правильный* код с точки зрения всех возможных проблем.
                                      • +6
                                        и городить приходится не «хитрый» код, а именно правильный код с точки зрения всех возможных проблем.

                                        Всё-таки над non-lexical lifetimes не зря работают. Иногда borrow checker действительно заставляет делать "дополнительные приседания", хотя, как по мне, это не особо страшно.

                                      • +6
                                        Тут можно видеть странный синтаксис наследования #[derive(Debug,Eq)]

                                        Не знаю, знаете вы или нет, по этому предложения для меня непонятно. Но это не синтаксис наследования, это автоматическая реализация трейтов Debug и Eq.
                                        • +5

                                          Побуду занудой


                                          fn _merge(left_slice: &mut [u32], right_slice: &mut [u32]) -> u64

                                          https://doc.rust-lang.org/std/primitive.slice.html#method.split_at_mut


                                          И никаких мучений с


                                          fn _merge(vec: &mut [u32], left: usize, mid: usize, right: usize) -> u64
                                          • +8

                                            Идиоматически правильно использовать Option, чтобы показать, нашли мы что-то или нет. Поэтому результат возврата binary_search будет Option, а не i32. И пропадут все явные касты, которые совершенно не нужны. Советую посмотреть реализацию в libcore.

                                            Удачи в изучении Rust и спасибо за статью!

                                            • +5

                                              Спасибо за статью. Есть несколько замечаний по коду, но в целом у вас вполне себе идиоматичный Rust получился.


                                              Бинарный поиск:


                                              1. Передавать &Vec<u32> нет никакого смысла — в таких случаях нужно передвать срез &[u32], это делает сигнатуру немного чище и обобщённее.
                                              2. Для индексов стоит использовать usize вместо u32, и возвращать Option<usize>.

                                              fn binary_search(vec: &[u32], value: u32) -> Option<usize> {
                                                  let mut l = 0;
                                                  let mut r = vec.len() - 1;
                                                  while l <= r {
                                                      let i = (l + r) / 2;
                                                      if vec[i] == value {
                                                        return Some(i + 1);
                                                      } else if vec[i] > value {
                                                        r = i - 1;
                                                      } else if vec[i] < value {
                                                        l = i + 1;
                                                      } 
                                                  }
                                                  None
                                              }

                                              Должен отметить, что возвращать 1-based индекс немного странно, ну да ладно :) Как видите, код стал чуть чище.


                                              Слияние.


                                              Данная конструкция не взлетит в Rust без unsafe кода, т.к. тут мы передаем два изменяемых подмассива, которые располагаются в исходном массиве. Система типов в Rust не позволяет иметь две изменяемых переменных на один объект (мы знаем, что подмассивы не пересекаются по памяти, но компилятор — нет).

                                              Это не совсем верно. На срезах в Rust есть метод split_at_mut(), который возвращает два непересекающихся изменяемых среза из одного исходного. Но в общем случае да, вы правы — без специального кода и возможно unsafe сделать две мутабельные ссылки в один объект, даже если они не пересекаются, не получится.


                                              Кодирование Хаффмана:


                                              1. Передавать String как аргумент, как правило, не нужно — но в вашей ситуации это оправдано, потому что вы этот объект немедленно добавляете в HashMap. Во втором match, однако, вызывать clone() на ней не нужно, потому что дальше по коду строка нигде не используется и её можно переместить в оператор конкатенации и дальше в HashMap.
                                              2. Конструкцию типа match &self.left { &Some(ref leaf) => ... } вполне можно заменить на match self.left { Some(ref leaf) => ... }, и символов станет немного поменьше.
                                              • +6
                                                > Есть несколько замечаний по коду, но в целом у вас вполне себе идиоматичный Rust получился.

                                                Не соглашусь. Использование -1 для индикации отсутствия результата вместо Option<usize> — грубая ошибка. В Rust столько усилий положили на то, чтобы было легко выражать и обрабатывать случаи отсутствия значений, а автор всё это игнорирует. Ценность этого примера для людей, которые хотят изучить Rust — даже не нулевая, а отрицательная. И это не единственная проблема такого рода в статье.
                                                • +3
                                                  > Использование -1 для индикации отсутствия результата вместо Option — грубая ошибка.

                                                  Собственно, это было как раз первым пунктом моих замечаний.
                                                • +2
                                                  Спасибо большое за замечания! Я исправлю код в репозитории и в статье.

                                                  Насчет возврата -1 из binary_search, как так получилось: изначально в задаче в онлайн курсе было такое требование (возвращать -1, индексация с 1), изначально я решал данную задачу на C++ и, недостаточно подумав, переписал так же на Rust. Действительно, лучше возвращать Option, а при записи результата делать unwrap_or(-1)

                                                • +1
                                                  Все уже протестировано и с С++ и с С.
                                                  • +2
                                                    Слышал, что это не самый эффективный способ сравнивать производительность.
                                                    • 0
                                                      А как ещё оценить «производительную стоимость» абстракции, создающиеся любым языком высокого уровня?
                                                      Берем один и тот же алгоритм и реализуем его на разных инструментах, замеряя время выполнения каждого, какие ещё тут варианты есть?

                                                      П.С. аргумент «я слышал» — это же не серьёзный дискус.
                                                      • +2
                                                        Да, я знаю, что апелляция к личному опыту — плохая привычка.

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

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


                                                        Вот, что я смог найти из старых дискуссий.
                                                        • +3
                                                          То есть самая быстрая прога та, которую лучше всего реализовали. Сам язык не при чём.

                                                          Снова предположение без фактов и доказательств.
                                                          Реализации и тесты алгоритмов открыты, вы можете сами убедиться в честности и способностях участников.
                                                  • +4
                                                    Два замечания:
                                                    1. Использование gcc для компиляции C++ в данном случае не правильно. Дело в том, что Rust использует LLVM в качестве бекенда, а gcc нет. Надо было использовать Clang, иначе твой бенчмарк сравнивает не столько Rust vs C++ сколько LLVM vs gcc.
                                                    2. В статье об этом не сказано, но в коде я кажется вижу, что вы замеряете время вызова функций руками (вижу в этом файле common/measure/src/main.rs ). В Rust в ночной сборке есть модуль для бенчмарков и надо использовать его, что бы получить более надежные результаты. Я не C++ программист, но уверен у них тоже есть своя либа для бенчмарков. Руками такое делать не стоит т.к. есть миллион граблей на которые можно наступить и получить неправильный результат.
                                                    • –9
                                                      Я не C++ программист,
                                                      Заметно.
                                                      но уверен у них тоже есть своя либа для бенчмарков.
                                                      На чём, извините, эта уверенность построена?

                                                      Да, в C++ есть такая либа. И не одна. Десятки их, если не сотни. Если мы сейчас начнём между ними выбирать, то «утонем».

                                                      SPECи, coremarkи и прочие замеряют время работы программы. Тупо. По секундомеру. 5-7 запусков, в зачёт идёт лучший результат (идея в том, что OS может замедлить программу 100500 разными способами, а вот ускорить — так это вряд ли). Разумеется программы подобраны так, чтобы работать несколько минут — что позволяет пренебречь временем загрузки программ и временем ввода-вывода.

                                                      Руками такое делать не стоит т.к. есть миллион граблей на которые можно наступить и получить неправильный результат.
                                                      Если вы сравниваете отдельные процедуры с целью микрооптимизаций — то да, не стоит. Если же вы хотите сравнить два языка, то лучше всего — программа time(1), так как она точно работает одинаково для разных программ.
                                                      • +2
                                                        1. Согласен, как только доберусь до машинки, на которой проводил замеры, соберу данные по clang. Постараюсь взять clang такой версии, чтобы версия LLVM совпадала с Rust.
                                                        2. Я измеряю не время вызова функций — это действительно сложная история и есть много готовых решений. Я измеряю полное время выполнения программы. Я отказался от программы time, т.к. она под Mac os x не выдавала результат с нужной мне точностью. Программа measure, на которую вы указали, — это лишь попытка обойти это ограничение и иметь возможность проводить замеры на Linux & Mac os x.
                                                        • +1
                                                          Полное время выполнения программы это вообще нестабильная история. Для близких пар языков вроде C++ и Rust это может быть не особо существенно, но конечно Java и Rust так уже сравнивать совсем нельзя. Но и в данном случае я бы перестраховался — вроде Rust генерирует бинарники по больше чем C++ так как больше тянет из стандартной библиотеки и это может влиять на время запуска.
                                                          Так же не очень стабильно ведет себя единичный запуск. Даже для языков, где нет сложных адаптивных JIT-ов и GC, всё равно второй запуск выполнится быстрее потому, что CPU имеет некоторый элемент адаптивности внутри (предсказатель переходов и кэш). Поэтому по хорошему надо гонять бенчмарк несколько раз пока время не перестанет изменяться, и взять уже только стабильные замеры времени для ответа.
                                                          И ещё я забыл сказать: у вас в замерах не хватает погрешности. Всё таки время выполнения это случайная величина. Насколько я помню JMH для Java при подсчете погрешности считает, что время распределено нормально. Думаю это приемлемо, хотя конечно я бы взял какие-нибудь квантили — чисто на всякий случай.
                                                          Тут ещё такой момент, о которым вы тоже пишете, от которого я сам не знаю как избавиться: у языков всё таки разные стандартные библиотеки и результаты больше говорят о разницы в реализации структур данных в библиотеке, чем о чем-то другом. По хорошему надо отдельный бенчмарк сделать для этих структур в обоих языках, а потом оценить их вклад в общее время выполнения теста и как-то его вычесть наверное.
                                                          • +1
                                                            Для близких пар языков вроде C++ и Rust это может быть не особо существенно, но конечно Java и Rust так уже сравнивать совсем нельзя.
                                                            А почему нельзя-то? Что вы такое собрались делать, что время запуска для вас неважно, а скорость работы — важна?

                                                            Что-то долгоживущее — это, обычно, что-то в чём вы язык выбирать по скорости не будете. Писать плагин для emacs'а вы будете на Lisp'а, а для CLion'а — на Java или Kotlin'е. Rust ни там, ни там не подходит. И не из-за того, что он медленный.

                                                            А если вы делаете отдельную утилитку — то скорость её работы будет определяться, в том числе, временем запуска. Писать её на языках, в которых рантайм запускается настолько долго, что на этом фоне незаметно, сколько времени сортируется массив на несколько миллионов элементов — просто глупо. И да, если в результате этих замеров «неожиданно» окажется, что python или perl лучше и быстрее java… то это потому, что так оно и есть — для подобных задач, разумеется…
                                                            • 0
                                                              Все зависит от цели. Если цель стоит измерить время запуска приложения — его и надо мерить. А если цель — это измерить производительность конкретного участка кода — надо мерить его, уже без времени запуска. Тут вариантов очень много разных и конечно не зная юзкейса делать абстрактный бенчмарк в некотором роде бессмысленно. Скажем атомарные операции стоят дороже в зависимости от того как часто они выполняются — все это надо учитывать когда вы беретесь за реальную работу.
                                                        • –3

                                                          Rust на данный момент подразумевает использование LLVM, а в C++ можно использовать как gcc так и clang. Поэтому если задачей является сравнение производительности, то сравнение c++ скомпилированого g++ и rust скомпилированого llvm вполне корректно. Если бы вопрос был в том какой из языков llvm лучше оптимизирует, тогда Ваше замечание было бы справедливо.

                                                        • +3
                                                          Попробуйте заменить стандартный HashMap на FnvHashMap из crates.io/crates/fnv
                                                          • +1
                                                            Время работы программы является случайной величиной, и сравнивать между собой единичные испытания некорректно.
                                                            • +1
                                                              Процитирую статью:
                                                              Делалось 10 прогонов каждой задачи на каждом наборе данных, далее результаты усреднялись


                                                              Можно было брать больше прогонов, но дисперсия времени выполнении была невелика.
                                                              • +2
                                                                Лучше не усреднять, а брать минимум. Обычно первые один-два прогона чуть медленнее.

                                                                Но самое главное — выставить правильный governor, иначе у вас и после 20 запусков программа будет всё ускоряться и ускоряться…
                                                                • +1
                                                                  Но самое главное — выставить правильный governor, иначе у вас и после 20 запусков программа будет всё ускоряться и ускоряться…

                                                                  Может и наоборот быть, если intel pstates включился. Первые запуски будут под турбобустом, а потом ой.

                                                                  Я для бенчмарков на ноуте специально перезагружался с выключенными pstates.
                                                                  • +2
                                                                    Есть и ещё более патологические случаи. Мы как-то пытались померить скорость работы на ARM'е. Оказалось, что ни на одном доступном нам телефоне ничего толком померить нельзя с точностью до 1% — ибо перегреваются они, собаки.

                                                                    Спас ChromeBook (уже не помню какой модели): он достаточно велик для того, чтобы на однопоточных тестах, по крайней мере, охлаждения хватало и тормозов бы не было.
                                                            • –5
                                                              Будет ли программа считать 10 или 5 секунд — обычно не так важно. А вот будем ли мы её писать и отлаживать 3 дня или 10 дней это важно.

                                                              Что у rust со скоростью написания и отладки?
                                                              • +10
                                                                Будет ли программа считать 10 или 5 секунд — обычно не так важно

                                                                В системных языках, каким себя позиционирует Rust, важно.
                                                                • –10
                                                                  Если тиражи штучные — неважно. Мне намного важнее лишние 2-3 тысячи долларов за написание кода, чем 5 долларов за более мощный проц.

                                                                  P.S. не говоря уже о том, что если скорости не хватает — то изменением алгоритма получается выигрыш в разы, а не на проценты.

                                                                  P.P.S Скорость — это всего лишь фетиш с++ников.
                                                                  • +11
                                                                    От задач зависит. Не бывает серебряных пуль или «no free lunch».
                                                                    Алгоритмы менять до бесконечности невозможно — найдется предел около текущего state of the art.
                                                                    5 долларов на лишний проц — это прекрасно, пока речь не идет о системах с сотнями, тысячами и более серверов.
                                                                    Думаю, любой инженер должен понимать, что «от задачи зависит».
                                                                    • –8
                                                                      Алгоритмы менять до бесконечности невозможно — найдется предел около текущего state of the art.

                                                                      Поскольку у нас один и тот же LLVM и для С++ и для Rust, то не так сложно поменять код для достижения ровно того же результата. Лет 50 назад это было любимой темой — как поменять код, чтобы компилятор его оптимальней скомпилировал. :-) Так что какие-нибудь обработчики прерываний в итоге будут иметь одинаковый бинарный код и для Rust и для С++.

                                                                      5 долларов на лишний проц — это прекрасно, пока речь не идет о системах с сотнями, тысячами и более серверов.
                                                                      Но это уже не штучные тиражи. И потом — ну сэкономили вы 5 тысяч долларов на тысяче серверов. Сколько недель работы команды вы этим деньгами оплатите?

                                                                      Если я не ошибся в подсчетах — то всего лишь неделю для команды в 5-10 человек.

                                                                      Думаю, любой инженер должен понимать, что «от задачи зависит».
                                                                      Угу. Только приведите мне пример задачи, где реально выгоднее замедлить на 50% разработку ради получения на 50% более быстрого кода? Мне как-то кажется, что это или экзотика или opensource, где разработчикам не платят.
                                                                      • +7
                                                                        Но это уже не штучные тиражи. И потом — ну сэкономили вы 5 тысяч долларов на тысяче серверов. Сколько недель работы команды вы этим деньгами оплатите?
                                                                        Давайте пересчитаем.
                                                                        Угу. Только приведите мне пример задачи, где реально выгоднее замедлить на 50% разработку ради получения на 50% более быстрого кода?
                                                                        На 50% более быстрый код, говорите? Ну так значит вместо тысячи серверов у нас будет уже 500. Предположим, сервера у нас — середнячки, по 2500$ каждый. Экономия — 1250000$.
                                                                        • 0
                                                                          на 50% более быстрый — это 667 серверов, вместо 1000. Увеличения числа серверов нам не нужно — затраты по памяти и сети те же. Так что увеличиваем число ядер. Замена 8-ядерных I7 на 14ядерные обойдется примерно в 500 долларов. 500 на 333 = 166 500 долларов. В 7.5 раз меньше.

                                                                          Теперь рассмотрим вашу сумму. 1.25 миллиона долларов это 830 тысяч долларов зарплаты (если работаем по-белой бухгалтерии). Разработчик получает 2 тысячи долларов месяц. Итого 415 человеко-месяцев. Проект большой, работает команда из 50 человек. Так что на 8 месяцев этих денег хватит. Ну или на месяц с хвостиком, если мы ставим более быстрые процессоры.

                                                                          Теперь посмотрим, что мы теряем за эти 8 месяцев. предположим проект приносит 12 миллионов долларов дохода в год (дохода, а не прибыли). Значит за эти 8 месяцев мы потеряем 8 миллионов.

                                                                          Так что баланс — не в вашу пользу. Лучше запуститься с неоптимизированным кодом, чем ждать оптимизации. Тем более, что иметь запас по скорости — очень полезно. Мало ли что бизнесу потребуется. Да и сервера дольше живут, если недогружены. И на пиковую нагрузку запас надо оставить. И не 50%, а 200-300%.

                                                                          Так что пример не катит…

                                                                          • +6
                                                                            Если важно именно как можно скорее выкатить продукт — то можно оптимизацию отложить на потом. Если же продукт уже на рынке, и выполняет свои задачи, то можно потратить время и на оптимизацию. Facebook писался сразу на обычном PHP, а потом они начали вкладывать деньги штуки типа HHVM для оптимизации. Последние версии PHP также гораздо быстрее предыдущих — разработчики потратили немало времени на оптимизацию. Правда, конечные веб-разработчики часто съедают возросшую проворность PHP использованием тяжёлых фреймворков.

                                                                            Хороший антипример: Skype на мобильных платформах. Он похож на неповоротливого монстра. В итоге этим IM я практически не пользуюсь на телефоне. Только при крайней необходимости запускаю, и каждый раз это боль. Кажется, я не один такой: на мобильных платформах популярны совершенно другие IM, которые гораздо более шустры. И все они появились гораздо позднее Skype.
                                                                            • 0
                                                                              Напомню:
                                                                              приведите мне пример задачи, где реально выгоднее замедлить на 50% разработку ради получения на 50% более быстрого кода?
                                                                              Вот и получается, что таких примеров мало. Я ведь не про оптимизацию «вообще», а про конкретный баланс.

                                                                              Много примеров, где стоит потратить 5% времени ради оптимизации на 200-300%. А вот 50% времени разработки на 50% скорости — редкость. Тому же скайпу 50% не помогут — он все равно будет тормозить.

                                                                              Котенок в свое время ускорил PHP в 10 раз. Это дало общий прирост скорости… всего в два раза. Трудоемкость оценить сложнее, но видимо где-то не большее 10-15 процентов.

                                                                              Вот такую оптимизацию — я понимаю. А 50% ускорения в обмен на 50% трудоемкости — не понимаю.
                                                                              • +3
                                                                                А вот 50% времени разработки на 50% скорости — редкость. Тому же скайпу 50% не помогут — он все равно будет тормозить.
                                                                                Если прирост производительности будет 50% за год разработки даже с 75% замедлением появления новых фишечек из-за этого, то глядишь за столько лет сколько он тормозит — им стало бы приятно пользоваться. И кто-то даже может быть начал бы рассматривать его как адекватный мобильный IM. А тормозит он с самого появления в 2010 (говорю про версию под Android).
                                                                                • –2
                                                                                  Увы, 50% — это разовый прирост. И то, поскольку и С++ и Rust используют один и тот же LLVM, то достичь того же прироста можно без смены языка — просто написав более удобно для оптимизатора.
                                                                            • +4

                                                                              Это вы круто Rust разрабам 2к зп приписали… ))) Умножайте на 3

                                                                    • +6
                                                                      Бред полный, есть задачи, где скорость важна, есть задачи, где нет и вы тут со своими 5 долларами вообще ни при чём.
                                                                      • –3
                                                                        Ну так приведите пример, где реально выгоднее замедлить на 50% разработку ради получения на 50% более быстрого кода.
                                                                        • +5

                                                                          Примеры такие редки, не спорю. И в подавляющем большинстве случаев стоит отдать предпочтение языку и технологиям, на которых разработка быстрее и стабильнее.
                                                                          Но вы просили пример: допустим у меня нет возможности выбрать железо — мобильная разработка, в ней преимущество в скорости может быть оправдано, поскольку увеличит круг пользователей. Пример: есть ли разница во времени прокладки маршрута по карте между 5 секундами и почти 8ю, когда у твоих конкурентов — 2секунды?
                                                                          Или обработка данных на локальной машине, чтобы попробовать модели разные: разница между 10часами и 5ю будет значительной.
                                                                          Стоит войти в условия ограниченности ресурса по вычислениям, например, и задача выжать еще 50% даже ценой смены языка перестает выглядеть фантастично.

                                                                          • –2
                                                                            А что лучше: запустить приложение сейчас или через год? А для ускорения — может стоит пожертвовать памятью и хранить там предвычисленные значения? А локальную машину я бы сменил на быстрый сервер.

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

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

                                                                              На это ответит только бизнес и конкретные обстоятельства. Всякое бывает. У мобильных устройств не всегда есть связь или мы делаем систему с нулевым доверием.
                                                                              Или у нас уже есть тройка спецов на чудо быстром языке, которых схантили с успешного проекта конкурента, и мы можем использовать их опыт.
                                                                              Задачи и условия бывают разные. Чаще всего выигрыш по скорости в 2-3 раза относительно C или C++ не стоит значительно большей разработки и увеличения рисков на поддержке (мало спецов). Но от этого ваше изначальное утверждение верным не становится.

                                                                              • –2
                                                                                Повторю свое изначальное утверждение:
                                                                                Будет ли программа считать 10 или 5 секунд — обычно не так важно. А вот будем ли мы её писать и отлаживать 3 дня или 10 дней это важно.
                                                                                Не вижу в вашем ответе опровержения этого тезиса. Более того, с первой половиной тезиса вы уже согласись, сказав Примеры такие редки, не спорю.

                                                                                Вы хотите опровергнуть вторую половину тезиса?
                                                                          • +8
                                                                            Сама Mozilla, которая развивает Rust, сейчас тратить немало денег на оптимизацию и переписывание своего браузера. Firefox 57 реально ощутимо проворнее Firefox 52. Впереди ещё много работы в этом направлении. Может быть, это поможет Mozilla предотвратить потерю пользователей, которая происходит уже много лет. Браузер пишет, скажем, 50 разработчиков. А пользуются миллионы.

                                                                            Любой код, который выполняется на миллионах машин, имеет смысл оптимизировать даже на самом низком уровне, под конкретные архитектуры и наборы инструкций. Например, libjpegturbo — оптимизированный декодер JPEG — спонсируется и используется Mozilla, Google и рядом других компаний. Наблюдаю за разработкой Opus. Периодически вижу, что разработчики (помимо повышения качества кодирования) отдельно занимаются оптимизацией кодека даже для конкретных наборов инструкций конкретных процессоров (SSE, AVX и т.д.), сейчас проект спонсируется Mozilla. В разработке видеокодек AV1 — более десятка гигантов мира IT собралось для того, чтобы сделать самый лучший в мире видеокодек. И уж поверьте, без вкладывания денег в оптимизацию оно не обойдётся. Миру нужен эффективный кодек, который и жмёт хорошо, и за разумное время.

                                                                            Всё зависит от задачи. Если вы пишете сайтик — его можно и на PHP написать, и то что код на этом языке, грубо говоря, в 100 раз медленнее — не так страшно, так как основной тяжёлый код (та же БД), который будет выполняться — всё равно достаточно оптимальный код на C/C++. Вот код той же MySQL имеет смысл оптимизировать. Если бы её можно было бы магически ускорить на 50% — этим стоило бы заняться. Потому что 50% ускорение для такой штуки — это реально очень много и ощутимо. Выигрыш от такой оптимизации сложно оценить в цифрах, потому что от неё по сути выиграли бы вообще все. Проспонсировать такую оптимизацию мог бы кто-нибудь, кто сам очень активно использует эту БД, и хотел бы в первую очередь сократить свои расходы на сервера, а польза для остального мира — это уже как бонус.
                                                                            • +1
                                                                              Rust ощутимо выгодней во времени отладки. Он страхует от кучи ошибок. И это даст возможность Мозилле не бежать вдог