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 |
Примечания:
- В Rust regex! работает быстрее чем Regex, и r"[a-zA-Z0-9_]+" быстрее чем r"\w+". Все 4 комбинации были протестированы.
- Версия «debug» просто для сравнения
- 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! и неблокирующего таймера. Возможно вы захотите посмотреть.
Сравнение времени выполнения
Для сравнения времени выполнения необходимо произвести некоторые изменения в коде:
- Закомментировать вызов sleep в conway.nim и
main.rs
- Изменить количество итераций цикла с 300 до 3000
- Перерисовка поля тратит много времени, поэтому произведены два замера (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:
- Продуктивность: в одинаковых временных рамках вы запилите больше возможностей в Nim
- Простота в изучении
- Компилируемый язык как скриптовый, хорош для протипирования, интерактивного исследования, пакетной обработки данных и т.д
- Фишечки:
- переопределение методов
- определение новых опереаторов
- именованные аргументы и значения по-умолчанию
- мощные макросы
Сильные стороны Rust:
- Настоящий системный язык прогрммирования: встраиваемый, без сборщика мусора, близок к железу
- Безопасный, дисциплинирующий, надёжный
- Сильная команда ядра и активное сообщество
- Фишечки:
- превосходная реализация сопоставления с образцом (pattern matching)
- перечисления (enum), хотя в Nim перечисления тоже хороши
- let mut вместо var (маленькая, но важная вещь)
- мощный синтаксис разыменования структур
Обработка ошибок: в Nim используется общий механизм исключений, Rust использует возвращаемый тип Result (и макрос panic!). У меня нет предпочтений в этом, но я посчитал важным упомянуть это различие.
Релиз 1.0 на подходе
Nim и Rust должны зарелизиться в этом году (Rust зарелизился). Это очень здорово! Rust получил уже достаточно много внимания, но и Nim становится более известным. Они очень разные на вкус, но оба великолепные новые языки программирования. Rust показывает себя с лучшей стороны в вопросах производительности и безопасности. Nim проворный (игра слов: Nim is nimble), выразительный, реализует сильные стороны скриптовых и компилируемых языков. Они оба станут отличным дополнением вашего инструментария.
Надеюсь, после прочтения этой статьи, вы составили своё мнение об этих языках программирования.
Комментарии (49)
amarao
10.06.2015 16:03+12Rust идёт с идеей «как избавиться от GC и не страдать с ручным управлением памятью». А с чем идёт nim?
Zibx
10.06.2015 17:04+9В Rust ещё чудесная идея безопасного распараллеливания задач. А у Nim идея несколько другая — зачем писать на скриптовых языках то что можно скомпилить в очень быстрый код? Nim может помочь переписать горы питоньего кода из системных утилит линукса и тем самым повысить общую производительность системы + низкий порог входа.
youlose
16.06.2015 10:27+2Вам не кажется что это попахивает «предварительной оптимизацией»? Те утилиты, для которых важна производительность уже написаны на c++ и nim там не особо нужен.
BlessMaster
22.06.2015 01:04+2Как пример — исследование с большим объёмом данных.
А в чём будет заключаться избыточность оптимизации, если время написания кода не увеличивается, а время выполнения — сокращается, увеличивая производительность процесса разработки в целом?splav_asv
22.06.2015 08:29Исследования с большими данными часто упараются в потолок по памяти. А там GC уже убивает производительность полностью. Но да, много случаев и когда памяти хватает с запасом.
fogone
10.06.2015 16:35+5Всё же на мой субъективный взгляд, на расте код получается более красивый и визуально понятный. Нимовские лямбды в реализации подсчета слов меня вообще повергли в уныние — черезмерно громоздки. Вцелом нимовский код выглядит угловатым и за него всё время цепляется взгляд. И это при том, что раст это скорее системный язык. Это что касается эстетической, а значит субъективной стороны. В остальном же, не совсем понятно, что с чем сравнивается, ведь языки похоже создавались для разных целей, а подобные тесты в плане производительности не дают никаких репрезентативных результатов.
Но в любом случае, больше спасибо за статью, до этого момента не смотрел на ним, теперь обязательно посмотрю на него поближе.overmes
10.06.2015 17:28+3У меня обратный случай. Я Питонист и синтаксис Nim'а мне очень приятен(за исключением лямбд, они действительно выглядят дико), а синтаксис Rust'а пугает.
defuz
10.06.2015 19:24+3Как питонист скажу что к синтаксису Rust быстро привыкаешь. Тем более, что в нем нет никаких лишних символов, как в С++ или Rust. Точка с запятой в нем играет семантическую роль, а круглые скобки только там, где они действительно нужны.
solver
10.06.2015 16:37+9Что-то все как-то обходят стороной, в обсуждениях Nim, такой не маловажный момент, как команда разработчиков языка.
За Rust стоит хорошая команда. А что там с Nim?
Вы тоже, в статье, скромно умолчали про это.scalavod Автор
10.06.2015 21:55Т.к. автор оригинальной статьи причислил «strong core team» к сильным сторонам Rust, могу предположить, что Nim этим не отличается. В контрибьютерах Nim самым активным (что не удивительно) является сам автор языка Andreas Rumpf.
beduin01
10.06.2015 17:11+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); } } }
shock_one
10.06.2015 17:16-8Но ведь это язык из другой весовой группы. Он выполняется в виртуальной машине.
ik62
10.06.2015 17:18+2поддерживаю насчет D — очень приятный язык (субъективно конечно)
beduin01
10.06.2015 17:24+6Мне он тоже кажется куда более лаконичным чем Rust. Я удивлен где люди в Rust находят какую-то красоту, хотя решения на нем получаются значительно длиннее чем на D, при том что возможности языков примерно одинаковые. Иллюстрация этому — программа подсчета слов.
Чтобы предотвратить развитие темы про не отключаемый GC в D уточню, что он в нем уже отключаемый.BlessMaster
10.06.2015 17:43+3Для этого наверно нужно действительно подождать появления Rust-style кода. Можно попробовать не разворачивать итераторы в for — не будет такого давления вложенностями. Rust действительно обеспечивает «нулевую стоимость абстракций» и их не стоит бояться. Но для всего этого нужен опыт, с которым пока туго — язык молодой и до сих пор сильно менявшийся.
Это примерно как и с питоном — можно сесть и сразу написать по опыту программирования на другом языке какое-нибудь решение в лоб и оно будет относительно неплохо работать. Но проходит время, набирается опыт, меняется стиль, и со временем обнаруживаешь, что писать на питоне можно значительно понятней и компактней.
На днях исправлял в вики примеры кода 2013-го года на актуальные. Местами Rust стал значительно компактней.CONSTantius
10.06.2015 18:04Вот тут, WIP, style guidelines
BlessMaster
10.06.2015 18:07+1Как-то так пока:
github.com/rust-lang/rust/blob/master/src/doc/style/style/README.md
CONSTantius
10.06.2015 18:06+9Отключаемый-не отключаемый — это не так интересно. В Nim он тоже «отключаемый» и можно при желании пользоваться голыми указателями.
Сила Rust не в отсутствии GC как таковом, а в отсутствии GC и безопасности работы с памятью при этом.
fogone
10.06.2015 21:02Ничего личного, но код в статье я бы примером для подражания не назвал.
beduin01
11.06.2015 08:59+2>Сила Rust не в отсутствии GC как таковом, а в отсутствии GC и безопасности работы с памятью при этом.
К сожалению обратная сторона этой силы чрезвычайная сложность изучения языка. В итоге вероятность написания говнокода сильно возрастает из-за неправильно понимания концепций работы со всей этой память.
Для кучи задач GC это добро т.к. позволяет больше думать про архитектуру приложения, чем про указатели и память.splav_asv
11.06.2015 09:34+6Ну вот для остальных был создан Rust. Как альтернатива C/C++ в первую очередь.
Googolplex
10.06.2015 21:30Программа подсчёта слов на Rust делает гораздо больше чем вышеприведённый пример на D (о чём автор того комментария, кстати, сказал). Я думаю, что аналогичная по функциональности программа на D будет аналогична по длине.
К тому же код в статье можно сделать почище, например, избавиться от лишнихas Box<Reader/Writer>
.
withkittens
10.06.2015 18:22+4Rust вроде тоже не страшен ;) Чуток многословнее, но того требует обработка ошибок в отсутствие исключений.
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( () ) }
defuz
11.06.2015 00:02+4Обычно Rust более многословен только потому, что не позволяет закрыть глаза на необходимость обработки ошибок. Но как по мне, это плюс, а не минус: лучше учится делать все по уму, чем выпендриватся однострочниками.
Конечно, посчитать что-то в пару нажатий клавишь как в ipython не получится. Но он для этого и не предназначался.
Fedcomp
10.06.2015 17:23Я так понимаю что Nim из-за GC не может производить .dll?
DMinsky
10.06.2015 20:13Может, но есть нюансы. Цитата из доки: «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!»
potan
10.06.2015 19:35Пара вопросов про nim.
1. Есть ли в нем pattern matching? В документации я нашел только примитивное использование case и множественное присваивание в let. Есть ли полноценный механизм?
2. Rust мне очень нравится тем, что функция возвращает значение последнего выражения и не надо писать return или использовать другие хитрые механизмы. В nim в документации описаны два механизма — оператор return и приваивание псевдопеременной result. Но в примерах встречается код, где похоже что просто возвращается значение последнего выражения. Это работает всегда или только в каких-то особых случаях?ingrysty
11.06.2015 00:061. Хотелось бы узнать более точный пример «Полноценного механизма», чтобы дать ответ. Потому что в разных языках — степень реализации разная.
2. Есть еще 2 способа, которые не описаны в документации: https://gist.github.com/Nyarum/059400eb32da81b8d901potan
11.06.2015 15:451. Что бы можно было реализовать извлечение данных из аналога Option/Maybe из других языков.
case x of Some 0 -> 1 Some x -> x*2 Nothing -> 0
2. Самое интересное то и недокументировано.ingrysty
11.06.2015 18:271. Да, на этот вопрос ответили ниже.
2. На самом деле документировано, но не в определенном разделе. Другие способы есть в Internal documentation.
scalavod Автор
11.06.2015 07:28+1Более года назад автор Nim уже отвечал на вопрос про pattern matching.
Как я понимаю, такого как в Scala нет, но его, якобы, можно реализовать через макросы.potan
11.06.2015 15:38Thanks!
В Julia есть пакет, использующий макросы. Вероятно, это удачное решение при наличии менеджера пакетов.
stepik777
11.06.2015 01:16Стоит отметить, что хэш-таблица из стандартной библиотеки Раста сделана с защитой от DoS атак, поэтому работает медленнее, чем могла бы.
CONSTantius
11.06.2015 01:48+2Каких DoS-атак?
splav_asv
11.06.2015 08:14+2Имеется в виду атака на коллизии в hash-функции, когда специально генирируются ключи, хэш от которых будет отдинаковый, что приводит к росту сложности с O(1) до O(n) -> увеличение времени обработки операций -> потенциально отказ в обслуживании.
RPG
18.06.2015 12:22+2Предложу также сравнить с Bash:
grep -oP '\w+' $@ | sort | uniq -c
Штуки шутками, но этот вариант не сильно медленнее, учитывает русские слова и выдаёт человеческую сортировку (без учёта регистра). Nim, в частности, не учитывает русские слова и не умеет человечную сортировку. Такие мелочи как отсутствие русского языка огорчают. Как можно переписать код, чтобы \w в nim понимал UTF-8?RPG
18.06.2015 12:32Сам спросил, сам нашёл:)
Переделать на \letter+ и дело в шляпе. Интересный язык, этот nim.
xGromMx
Довольно интересно =)