Сравниваем Nim и Rust

http://arthurtw.github.io/2015/01/12/quick-comparison-nim-vs-rust.html
  • Перевод
Предлагаю читателям «Хабрахабра» перевод статьи «A Quick Comparison of Nim vs. Rust». Мои замечания будут выделены курсивом.

Rust и Nim — два новых языка программирования за развитием которых я слежу. Вскоре, после моего первого поста о Rust, вышел в свет Nim 0.10.2. Это побудило меня поближе познакомиться с Nim и, естественно, сравнить его с Rust.

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



Пример №1: Подсчёт слов (wordcount)



В этом примере используется файловый I/O, регулярные выражения, хеш-таблицы (ассоциативные массивы) и парсинг аргументов переданных команде. Как следует из названия, прогрмма подсчитывает количество слов в файлах или stdin.

Пример использования:

Usage: wordcount [OPTIONS] [FILES]

Options:
    -o:NAME             set output file name
    -i --ignore-case    ignore case
    -h --help           print this help menu


Если мы передадим аргумент -i, то результат будет таким:

2       case
1       file
1       files
1       h
2       help
...


Nim версия



Программа на Nim достаточно проста. В ней используются tables.CountTable для подсчёта слов, parseopt2.getopt для парсинга аргументов команды и sequtils.mapIt для функциональной операции мэппинга. Для регулярных выражения я выбрал модуль pegs, который рекомендуется документацией Nim заместо re.

Деректива {.raises: [IOError].} на строке 3 гарантирует что процедура doWork выбрасывает только исключение IOError. Для этого я поместил input.findAll(peg"\w+") внутрь try выражения в строке 21 чтобы отловить исключения, которые, теоретически, могут возникнуть.

Часть кода wordcount.nim:

proc doWork(inFilenames: seq[string] = nil,
            outFilename: string = nil,
            ignoreCase: bool = false) {.raises: [IOError].} =
  # Open files
  var
    infiles: seq[File] = @[stdin]
    outfile: File = stdout
  if inFilenames != nil and inFilenames.len > 0:
    infiles = inFilenames.mapIt(File, (proc (filename: string): File =
      if not open(result, filename):
        raise newException(IOError, "Failed to open file: " & filename)
    )(it))
  if outFilename != nil and outFilename.len > 0 and not open(outfile, outFilename, fmWrite):
    raise newException(IOError, "Failed to open file: " & outFilename)

  # Parse words
  var counts = initCountTable[string]()
  for infile in infiles:
    for line in infile.lines:
      let input = if ignoreCase: line.tolower() else: line
      let words = try: input.findAll(peg"\w+") except: @[]
      for word in words:
        counts.inc(word)

  # Write counts
  var words = toSeq(counts.keys)
  sort(words, cmp)
  for word in words:
    outfile.writeln(counts[word], '\t', word)


Rust версия



Для лучшего понимания Rust я реализовал простую структуру BTreeMap сродни collections::BTreeMap, но в конечном итоге я использовал collections::HashMap для справедливого сравнения с Nim (код BTreeMap остался в репозитории для ознакомления). Пакет getopts используется для парсинга аргументов команды в мою структуру Config. Далее всё должно быть понятно.

Часть кода из моего проекта Rust wordcount:

fn do_work(cfg: &config::Config) -> io::Result<()> {
    // Open input and output files
    let mut readers = Vec::with_capacity(std::cmp::max(1, cfg.input.len()));
    if cfg.input.is_empty() {
        readers.push(BufReader::new(Box::new(io::stdin()) as Box<Read>));
    } else {
        for name in &cfg.input {
            let file = try!(File::open(name));
            readers.push(BufReader::new(Box::new(file) as Box<Read>));
        }
    }
    let mut writer = match cfg.output {
        Some(ref name) => {
            let file = try!(File::create(name));
            Box::new(BufWriter::new(file)) as Box<Write>
        }
        None => { Box::new(io::stdout()) as Box<Write> }
    };

    // Parse words
    let mut map = collections::HashMap::<String, u32>::new();
    let re = regex!(r"\w+");

    // let re = Regex::new(r"\w+").unwrap();
    // let re = regex!(r"[a-zA-Z0-9_]+");
    // let re = Regex::new(r"[a-zA-Z0-9_]+").unwrap();
    for reader in &mut readers {
        for line in reader.lines() {
            for caps in re.captures_iter(&line.unwrap()) {
                if let Some(cap) = caps.at(0) {
                    let word = match cfg.ignore_case {
                        true  => cap.to_ascii_lowercase(),
                        false => cap.to_string(),
                    };
                    match map.entry(word) {
                        Occupied(mut view) => { *view.get_mut() += 1; }
                        Vacant(view) => { view.insert(1); }
                    }
                }
            }
        }
    }
    // Write counts
    let mut words: Vec<&String> = map.keys().collect();
    words.sort();
    for &word in &words {
        if let Some(count) = map.get(word) {
            try!(writeln!(writer, "{}\t{}", count, word));
        }
    }
    Ok(())
}


Zachary Dremann предложил pull request в котором используется find_iter. Я оставил captures_iter для согласованности с Nim версией, но немного улучшил свой код.

Сравнение времени выполнения



Я скомпилировал код с флагами -d:release для Nim и --release для Rust. Для примера взял файл в 5 мегабайт составленый из исходников компилятора Nim:

$ cat c_code/3_3/*.c > /tmp/input.txt
$ wc /tmp/input.txt
  217898  593776 5503592 /tmp/input.txt


Команда для запуска программы:

$ time ./wordcount -i -o:result.txt input.txt


Вот результат на моём Mac mini с процессором 2.3 GHz Intel Core i7 и памятью 8 GB: (1x = 0.88 секунды)

Rust regex! \w Regex \w regex! […] Regex […] Nim
release, -i 1x 1.30x 0.44x 1.14x 0.75x
release 1.07x 1.33x 0.50x 1.24x 0.73x
debug, -i 12.65x 20.14x 8.77x 19.42x 3.51x
debug 12.41x 20.09x 8.84x 19.33x 3.25x


Примечания:
  1. В Rust regex! работает быстрее чем Regex, и r"[a-zA-Z0-9_]+" быстрее чем r"\w+". Все 4 комбинации были протестированы.
  2. Версия «debug» просто для сравнения
  3. Nim работает на 1-2% медленнее с флагом --boundChecks:on, я не стал добавлять этот результат в пример.


Пример №2: Игра «Жизнь»



Этот пример запускает «Жизнь» в консоле с фиксированными размером поля и шаблоном (для изменения размера или шаблона отредактируйте исходный код). В нём используется ANSI CSI код для перерисовки экрана.

После запуска экран будет выглядеть примерно так:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . (). (). . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . (). . . (). . . . . . . . . . .
. . . . . . . . . . . . . . (). . . . . . . (). . . . . . . . . . . . ()().
. . . . . . . . . . . . . ()()()(). . . . (). . . . (). . . . . . . . ()().
. ()(). . . . . . . . . ()(). (). (). . . . (). . . . . . . . . . . . . . .
. ()(). . . . . . . . ()()(). (). . (). . . (). . . (). . . . . . . . . . .
. . . . . . . . . . . . ()(). (). (). . . . . . (). (). . . . . . . . . . .
. . . . . . . . . . . . . ()()()(). . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . (). . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . (). . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . (). (). . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . ()(). . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . (). . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . (). . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ()()(). . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
n = 300   Press ENTER to exit


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

Nim версия



Вот часть кода из моего проекта Nim conway:

type
  Cell = bool
  ConwayMap* = array[0.. <mapHeight, array[0.. <mapWidth, Cell]]

proc init*(map: var ConwayMap, pattern: openarray[string]) =
  ## Initialise the map.
  let
    ix = min(mapWidth, max(@pattern.mapIt(int, it.len)))
    iy = min(mapHeight, pattern.len)
    dx = int((mapWidth - ix) / 2)
    dy = int((mapHeight - iy) / 2)
  for y in 0.. <iy:
    for x in 0.. <ix:
      if x < pattern[y].len and pattern[y][x] notin Whitespace:
        map[y + dy][x + dx] = true

proc print*(map: ConwayMap) =
  ## Display the map.
  ansi.csi(AnsiOp.Clear)
  ansi.csi(AnsiOp.CursorPos, 1, 1)
  for row in map:
    for cell in row:
      let s = if cell: "()" else: ". "
      stdout.write(s)
    stdout.write("\n")

proc next*(map: var ConwayMap) =
  ## Iterate to next state.
  let oldmap = map
  for i in 0.. <mapHeight:
    for j in 0.. <mapWidth:
      var nlive = 0
      for i2 in max(i-1, 0)..min(i+1, mapHeight-1):
        for j2 in max(j-1, 0)..min(j+1, mapWidth-1):
          if oldmap[i2][j2] and (i2 != i or j2 != j): inc nlive
      if map[i][j]: map[i][j] = nlive >= 2 and nlive <= 3
      else: map[i][j] = nlive == 3


Rust версия



Вот часть кода из моего проекта Rust conway:

type Cell = bool;

#[derive(Copy)]
pub struct Conway {
    map: [[Cell; MAP_WIDTH]; MAP_HEIGHT],
}

impl Conway {
    pub fn new() -> Conway {
        Conway {
            map: [[false; MAP_WIDTH]; MAP_HEIGHT],
        }
    }

    pub fn init(&mut self, pattern: &[&str]) {
        let h = pattern.len();
        let h0 = (MAP_HEIGHT - h) / 2;
        for i in 0..(h) {
            let row = pattern[i];
            let w = row.len();
            let w0 = (MAP_WIDTH - w) / 2;
            for (j, c) in row.chars().enumerate() {
                self.map[i + h0][j + w0] = c == '1';
            }
        }
    }

    /// Iterate to next state. Return false if the state remains unchanged.
    pub fn next(&mut self) -> bool {
        let mut newmap = [[false; MAP_WIDTH]; MAP_HEIGHT];
        for i in 0..(MAP_HEIGHT) {
            for j in 0..(MAP_WIDTH) {
                let mut nlive = 0;
                for i2 in i.saturating_sub(1)..cmp::min(i+2, MAP_HEIGHT) {
                    for j2 in j.saturating_sub(1)..cmp::min(j+2, MAP_WIDTH) {
                        if self.map[i2][j2] && (i2 != i || j2 != j) {
                            nlive += 1;
                        }
                    }
                }
                newmap[i][j] = match (self.map[i][j], nlive) {
                    (true, 2) | (true, 3) => true,
                    (true, _) => false,
                    (false, 3) => true,
                    (false, _) => false,
                };
            }
        }
        // let changed = self.map != newmap;
        let changed = true;
        self.map = newmap;
        changed
    }
}

impl fmt::Display for Conway {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for row in self.map.iter() {
            for cell in row.iter() {
                try!(write!(f, "{}", if *cell { "()" } else { ". " }));
            }
            try!(write!(f, "\n"));
        }
        Ok(())
    }
}


В строке 49 я определил переменную для отслеживания изменения отображения, но простое сравнение self.map != newmap не работает для массивов длинной более 32 элементов, пока вы не реализуете трейт PartialEq.

Отмечу, что я использовал небезопасный libc::exit в моём main.rs, что очень не характерно для Rust. Zachary Dremann предложил pull request в котором элегантно избегается libc::exit с использованием макроса select! и неблокирующего таймера. Возможно вы захотите посмотреть.

Сравнение времени выполнения



Для сравнения времени выполнения необходимо произвести некоторые изменения в коде:

  1. Закомментировать вызов sleep в conway.nim и
    main.rs
  2. Изменить количество итераций цикла с 300 до 3000
  3. Перерисовка поля тратит много времени, поэтому произведены два замера (1) с перерисовкой и (2) без неё (т.е. с закомментированными строками вывода поля на печать в conway.nim и main.rs)


Вот результаты при компиляции с флагами -d:release для Nim и --release для Rust:

Rust Nim Nim/bc:on n=30000
(1) with map print 1x 1.75x 1.87x 1x=3.33s
(2) without map print 1x 1.15x 1.72x 1x=0.78


Т.к. Rust делает проверку на выход за границу списка, для справедливости я добавил колонку Nim/bc:on для Nim версии скомпилированной с флагом --boundChecks:on.

Nim или Rust



Хоть Nim и Rust компилируемые языки с рассчётом на хорошую производительность они очень разные. Для меня их сходства заключаются в следующем:

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


Но их различия более интересны.

Философия: свобода или дисциплина



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

Однако есть и обратная сторона у такой свободы: могут пострадать ясность, чистота и поддерживаемость. Вот небольшой пример: в Nim import импортирует все имена модуля в ваше пространство имён. Имена из импортируемого модуля могут быть ограничены использованием синтаксиса module.symbol или использованием from module import nil для контролируемого импорта имён, но, скажите мне, кто этим пользуется? Тем более, что этот подход не характерен для Nim. В результате вы не сможете понять какие имена пришли из какого модуля при чтении чужого (или собственного) кода (к счастью, противоречий наименований не случается, т.к. в таких случаях Nim заствляет отделять мух от котлет).

Ещё примеры: UFCS позволяет использовать len(x), len x, x.len() или x.len как вам вздумается; не разделяет имена с подчёркиванием и разным регистром, так mapWidth, mapwidth и map_width будут преобразованы в одно и тоже имя (я рад что они включили правило «частичной чувствительности к регистру» в версии 0.10.2, поэтому Foo и foo будут считаться различными именами); в порядке вещей является использование неинициализированных переменных. В теории, вы можете следовать строгим принципам кодирования, но при программировании в Nim вы будете чувствовать себя более раскованно.

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

Оба имеют свои плюсы и минусы. Как программист, я больше наслаждаюсь от Nim; как майнтейнер, я бы лучше сопровождал продукты написанные на Rust.

Визуальный стиль: Python или C++



Как и Python, Nim использует отступы для отделения блоков кода и в нём меньше всяких знаков. Rust более похож на C++. {}, ::, <> и & будут знакомы C++ программистам, плюс Rust добавляет некоторы новые вещи вроде 'a.

Иногда Nim может быть слишком буквальным. Для примера, я думаю синтаксис match в Rust:

match key.cmp(&node.key) {
    Less    => return insert(&mut node.left, key, value),
    Greater => return insert(&mut node.right, key, value),
    Equal   => node.value = value,
}


выглядит чище, чем выражение case в Nim:

case key
of "help", "h": echo usageString
of "ignore-case", "i": ignoreCase = true
of "o": outFilename = val
else: discard


Но, в целом, код на Nim менее зашумлен. По моему мнению, особенный беспорядок в Rust вносят параметры времени жизни (lifetime parameters) и это уже не изменится.

Управление памятью: Сборщик мусора или ручное управление



Хоть Nim и позволяет небезопасное управление памятью и обеспечивает поддержку управления сборщиком мусора в рантайме для более предсказуемымого поведения. Это всё ещё язык со сборщиком мусора, который обладает всеми плюсами и минусами от него. Объектам в Nim присваиваются копии значений. Если для вашей задачи сборщик мусора не помешает, то управление памятью в Nim не будет вызывать у вас проблем.

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

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

В зависимости от ваших требований, либо вам будет достаточно сборщика мусора Nim, либо ваш выбор падёт на Rust.

Другие различия



Сильные стороны Nim:

  1. Продуктивность: в одинаковых временных рамках вы запилите больше возможностей в Nim
  2. Простота в изучении
  3. Компилируемый язык как скриптовый, хорош для протипирования, интерактивного исследования, пакетной обработки данных и т.д
  4. Фишечки:
    • переопределение методов
    • определение новых опереаторов
    • именованные аргументы и значения по-умолчанию
    • мощные макросы



Сильные стороны Rust:

  1. Настоящий системный язык прогрммирования: встраиваемый, без сборщика мусора, близок к железу
  2. Безопасный, дисциплинирующий, надёжный
  3. Сильная команда ядра и активное сообщество
  4. Фишечки:
    • превосходная реализация сопоставления с образцом (pattern matching)
    • перечисления (enum), хотя в Nim перечисления тоже хороши
    • let mut вместо var (маленькая, но важная вещь)
    • мощный синтаксис разыменования структур



Обработка ошибок: в Nim используется общий механизм исключений, Rust использует возвращаемый тип Result (и макрос panic!). У меня нет предпочтений в этом, но я посчитал важным упомянуть это различие.

Релиз 1.0 на подходе



Nim и Rust должны зарелизиться в этом году (Rust зарелизился). Это очень здорово! Rust получил уже достаточно много внимания, но и Nim становится более известным. Они очень разные на вкус, но оба великолепные новые языки программирования. Rust показывает себя с лучшей стороны в вопросах производительности и безопасности. Nim проворный (игра слов: Nim is nimble), выразительный, реализует сильные стороны скриптовых и компилируемых языков. Они оба станут отличным дополнением вашего инструментария.

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

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

Комментарии 50
    +3
    Довольно интересно =)
      +12
      Rust идёт с идеей «как избавиться от GC и не страдать с ручным управлением памятью». А с чем идёт nim?
        +9
        В Rust ещё чудесная идея безопасного распараллеливания задач. А у Nim идея несколько другая — зачем писать на скриптовых языках то что можно скомпилить в очень быстрый код? Nim может помочь переписать горы питоньего кода из системных утилит линукса и тем самым повысить общую производительность системы + низкий порог входа.
          +6
          Т.е. ниша Go?
            +4
            Да, тоже появилось ощущение «ниши Go». Даёшь сравнение nim и go!
            +2
            Вам не кажется что это попахивает «предварительной оптимизацией»? Те утилиты, для которых важна производительность уже написаны на c++ и nim там не особо нужен.
              +2
              Как пример — исследование с большим объёмом данных.
              А в чём будет заключаться избыточность оптимизации, если время написания кода не увеличивается, а время выполнения — сокращается, увеличивая производительность процесса разработки в целом?
                0
                Исследования с большими данными часто упараются в потолок по памяти. А там GC уже убивает производительность полностью. Но да, много случаев и когда памяти хватает с запасом.
          0
          github.com/rust-lang/regex/issues/66 однако все еще открыт
            0
            BurntSushi closed this in #164 on 16 Feb 2016
            +5
            Всё же на мой субъективный взгляд, на расте код получается более красивый и визуально понятный. Нимовские лямбды в реализации подсчета слов меня вообще повергли в уныние — черезмерно громоздки. Вцелом нимовский код выглядит угловатым и за него всё время цепляется взгляд. И это при том, что раст это скорее системный язык. Это что касается эстетической, а значит субъективной стороны. В остальном же, не совсем понятно, что с чем сравнивается, ведь языки похоже создавались для разных целей, а подобные тесты в плане производительности не дают никаких репрезентативных результатов.
            Но в любом случае, больше спасибо за статью, до этого момента не смотрел на ним, теперь обязательно посмотрю на него поближе.
              +3
              У меня обратный случай. Я Питонист и синтаксис Nim'а мне очень приятен(за исключением лямбд, они действительно выглядят дико), а синтаксис Rust'а пугает.
                +3
                Как питонист скажу что к синтаксису Rust быстро привыкаешь. Тем более, что в нем нет никаких лишних символов, как в С++ или Rust. Точка с запятой в нем играет семантическую роль, а круглые скобки только там, где они действительно нужны.
                  +3
                  ??? "… к синтаксису Rust быстро привыкаешь… в нем нет никаких лишних символов, как в… Rust"
                    0
                    Это опечатка. Должно было быть «как в С++ или Javascript». :)
              +9
              Что-то все как-то обходят стороной, в обсуждениях Nim, такой не маловажный момент, как команда разработчиков языка.
              За Rust стоит хорошая команда. А что там с Nim?
              Вы тоже, в статье, скромно умолчали про это.
                0
                Т.к. автор оригинальной статьи причислил «strong core team» к сильным сторонам Rust, могу предположить, что Nim этим не отличается. В контрибьютерах Nim самым активным (что не удивительно) является сам автор языка Andreas Rumpf.
                +3
                Было бы интересно сравнение с D увидеть.
                Вот фрагмент из книги «The D Programming Language» выполняющий подсчет слов. Как по мне гораздо нагляднее и проще чем на Rust и Nim. Обращаю внимание, что код не полностью идентичен решениям из статьи. Просто как пример взял.

                import std.stdio, std.string; 
                
                void main() { 
                	uint[string] dictionary; 
                	foreach (line; stdin.byLine()) { 
                	// Break sentence into words 
                	// Add each word in the sentence to the vocabulary 
                	foreach (word; splitter(strip(line))) { 
                		if (word in dictionary) continue; // Nothing to do 
                		auto newlD = dictionary.length; 
                		dictionary[word] = newlD; 
                		writeln(newlD, '\t', word);
                		}
                	}
                }
                
                
                  –8
                  Но ведь это язык из другой весовой группы. Он выполняется в виртуальной машине.
                    +2
                    нет, он компилируемый в нативный код
                      0
                      Вы правы, почему-то считал, что это язык для CLR.
                      +1
                      Нет, вы что-то путаете. D это компилируемый язык.
                      +2
                      поддерживаю насчет D — очень приятный язык (субъективно конечно)
                        +6
                        Мне он тоже кажется куда более лаконичным чем Rust. Я удивлен где люди в Rust находят какую-то красоту, хотя решения на нем получаются значительно длиннее чем на D, при том что возможности языков примерно одинаковые. Иллюстрация этому — программа подсчета слов.

                        Чтобы предотвратить развитие темы про не отключаемый GC в D уточню, что он в нем уже отключаемый.
                          +3
                          Для этого наверно нужно действительно подождать появления Rust-style кода. Можно попробовать не разворачивать итераторы в for — не будет такого давления вложенностями. Rust действительно обеспечивает «нулевую стоимость абстракций» и их не стоит бояться. Но для всего этого нужен опыт, с которым пока туго — язык молодой и до сих пор сильно менявшийся.

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

                          На днях исправлял в вики примеры кода 2013-го года на актуальные. Местами Rust стал значительно компактней.
                            0
                            Вот тут, WIP, style guidelines
                              +1
                              Как-то так пока:

                              github.com/rust-lang/rust/blob/master/src/doc/style/style/README.md
                            +9
                            Отключаемый-не отключаемый — это не так интересно. В Nim он тоже «отключаемый» и можно при желании пользоваться голыми указателями.

                            Сила Rust не в отсутствии GC как таковом, а в отсутствии GC и безопасности работы с памятью при этом.
                              0
                              Ничего личного, но код в статье я бы примером для подражания не назвал.
                                +2
                                >Сила Rust не в отсутствии GC как таковом, а в отсутствии GC и безопасности работы с памятью при этом.
                                К сожалению обратная сторона этой силы чрезвычайная сложность изучения языка. В итоге вероятность написания говнокода сильно возрастает из-за неправильно понимания концепций работы со всей этой память.

                                Для кучи задач GC это добро т.к. позволяет больше думать про архитектуру приложения, чем про указатели и память.
                                  +6
                                  Ну вот для остальных был создан Rust. Как альтернатива C/C++ в первую очередь.
                                0
                                Программа подсчёта слов на Rust делает гораздо больше чем вышеприведённый пример на D (о чём автор того комментария, кстати, сказал). Я думаю, что аналогичная по функциональности программа на D будет аналогична по длине.

                                К тому же код в статье можно сделать почище, например, избавиться от лишних as Box<Reader/Writer>.
                              +4
                              Rust вроде тоже не страшен ;) Чуток многословнее, но того требует обработка ошибок в отсутствие исключений.

                              use std::collections::HashMap;
                              use std::io::{stdin, BufRead, Result};
                              
                              fn main() {
                                  word_count().unwrap();
                              }
                              
                              fn word_count() -> Result<()> {
                                  let mut dictionary = HashMap::new();
                                  let stdin = stdin();
                                  for line in stdin.lock().lines() {
                                      for word in try!(line).trim().split(' ') {
                                          let new_len = dictionary.len();
                                          if dictionary.insert(word.to_owned(), new_len).is_none() {
                                              println!("{}\t{}", new_len, word);
                                          }
                                      }
                                  }
                                  Ok( () )
                              }
                              
                                +4
                                Обычно Rust более многословен только потому, что не позволяет закрыть глаза на необходимость обработки ошибок. Но как по мне, это плюс, а не минус: лучше учится делать все по уму, чем выпендриватся однострочниками.

                                Конечно, посчитать что-то в пару нажатий клавишь как в ipython не получится. Но он для этого и не предназначался.
                              0
                              Я так понимаю что Nim из-за GC не может производить .dll?
                                0
                                Может, но есть нюансы. Цитата из доки: «Nim supports the generation of DLLs. However, there must be only one instance of the GC per process/address space. This instance is contained in nimrtl.dll. This means that every generated Nim DLL depends on nimrtl.dll. Note: Currently the creation of nimrtl.dll with thread support has never been tested and is unlikely to work!»
                                0
                                Пара вопросов про nim.
                                1. Есть ли в нем pattern matching? В документации я нашел только примитивное использование case и множественное присваивание в let. Есть ли полноценный механизм?
                                2. Rust мне очень нравится тем, что функция возвращает значение последнего выражения и не надо писать return или использовать другие хитрые механизмы. В nim в документации описаны два механизма — оператор return и приваивание псевдопеременной result. Но в примерах встречается код, где похоже что просто возвращается значение последнего выражения. Это работает всегда или только в каких-то особых случаях?
                                  0
                                  1. Хотелось бы узнать более точный пример «Полноценного механизма», чтобы дать ответ. Потому что в разных языках — степень реализации разная.

                                  2. Есть еще 2 способа, которые не описаны в документации: https://gist.github.com/Nyarum/059400eb32da81b8d901
                                    0
                                    1. Что бы можно было реализовать извлечение данных из аналога Option/Maybe из других языков.
                                    case x of
                                     Some 0 -> 1
                                     Some x -> x*2
                                     Nothing -> 0
                                    


                                    2. Самое интересное то и недокументировано.
                                      0
                                      1. Да, на этот вопрос ответили ниже.

                                      2. На самом деле документировано, но не в определенном разделе. Другие способы есть в Internal documentation.
                                    +1
                                    Более года назад автор Nim уже отвечал на вопрос про pattern matching.
                                    Как я понимаю, такого как в Scala нет, но его, якобы, можно реализовать через макросы.
                                      0
                                      Thanks!
                                      В Julia есть пакет, использующий макросы. Вероятно, это удачное решение при наличии менеджера пакетов.
                                    0
                                    Стоит отметить, что хэш-таблица из стандартной библиотеки Раста сделана с защитой от DoS атак, поэтому работает медленнее, чем могла бы.
                                      +2
                                      Каких DoS-атак?
                                        +2
                                        Имеется в виду атака на коллизии в hash-функции, когда специально генирируются ключи, хэш от которых будет отдинаковый, что приводит к росту сложности с O(1) до O(n) -> увеличение времени обработки операций -> потенциально отказ в обслуживании.
                                      +3
                                      Реквестирую добавление в обзор языка Crystal!
                                        +1
                                        Благодарю за наводку, прочитал всю документацию и все записи из блога: язык понравился. Буду следить за его развитием.
                                          0
                                          Кстати, в примерх прилагаемых к Crystal есть и wordcount и conway.
                                          +2
                                          Предложу также сравнить с Bash:

                                          grep -oP '\w+' $@ | sort | uniq -c
                                          

                                          Штуки шутками, но этот вариант не сильно медленнее, учитывает русские слова и выдаёт человеческую сортировку (без учёта регистра). Nim, в частности, не учитывает русские слова и не умеет человечную сортировку. Такие мелочи как отсутствие русского языка огорчают. Как можно переписать код, чтобы \w в nim понимал UTF-8?
                                            0
                                            Сам спросил, сам нашёл:)
                                            Переделать на \letter+ и дело в шляпе. Интересный язык, этот nim.

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

                                          Самое читаемое