LINUX.ORG.RU

познать Rust

 


2

6

Доброго времени суток. Ищется материал по Rust, где:

1. В сжатой, но понятной форме изложено положение Rust в контексте других языков. В чём суть его новизны. В качестве базы ожидается C++, Java, Common Lisp. Желательно, с примерами кода.

2. Критика. Какие грабли уже проявили себя.

Желательный объём - до 5 страниц текста.

★★★★★

Последнее исправление: den73 (всего исправлений: 1)

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

Из двух зол выбирают меньшее. Про Go люди даже специально репу подняли https://github.com/ksimka/go-is-not-good, чтобы не получилось с ним, как с php. Надо на корню давить таких уродцев, пока они не попортили генофонд. Здесь конечнее сложнее, это тебе не очередной nim, здесь гугл бабло вливает, но давить нужно - по мере сил...

foror ★★★★★
()
Последнее исправление: foror (всего исправлений: 1)

В треде о хрусте сгенерили пять страниц срача о деструкторах в сипипи. Норм, чо. Очевидно, что сам хруст никому не вперся.

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

Очевидно, что сам хруст никому не вперся.

Очевидно, что тред ты не читал.

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

Там вообще довольно забавные предъявы.

is compiled
no semicolons at line endings
un-googlable name
weird mascot (gopher)
psuedointellectual arrogance of Rob Pike and everything he stands for

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

И, что самое обидное, несмотря на всю заявленную мощь и выразительность C++ залазишь в реализацию STL и видишь

Ничего ужастного не вижу. Тебя смущают _M, __first, __last?

По остальному тексту видно, что ты ну совсем ничего не знаешь.

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

Про Go люди даже специально репу подняли https://github.com/ksimka/go-is-not-good, чтобы не получилось с ним, как с php.

Гофер и поднял: «I don't think anyone would deny that go has weaknesses: it certainly has. But how do you know, is it really a language design flaw or is it just you, doing something completely wrong? This list here to help you quickly answer the question.»

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

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

В 2015 году уже должно быть стыдно наследовать класс от класса (не интерфейса).

anonymous
()

Заподозрил сегодня, что исключения - это Зло.

Смотрю - в go их нет, а в Rust вместо них panic. Где найти описание, как работает panic и чем по сути отличается от исключений? В официальной документации как-то куцо, в обзорах на Русском тоже мало чего внятного сказано. Интересно понять мотивы, почему сделано именно так, а не иначе.

То, что нашёл, удивляет: http://nerohelp.info/848-rust-io.html

Вызов макроса fail! (~строка) или конструкции assert! порождает исключение и приводит к откату стека вызовов, принудительному вызову деструкторов объектов, освобождению памяти и завершению текущего потока. Перехватить исключение внутри потока невозможно: возникнув, оно всегда приводит к его остановке, но можно обработать исключение в родительском потоке

Т.е., я так понял, panic служит для фатальных для данного треда ошибок - тред падает, но программа может продолжать исполнение.

А что делается с обычными ошибками? ПРоверять коды возврата, как это обычно делается в С?

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

И второй, связанный вопрос - как в Расте обстоит дело с обработчиками сигналов (ведь он претендует на роль С).

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

Только рвотный пакетик не забудь. Там tmtowtdy в худшем виде: пять разных способов, один «краше» другого. Я так понял, что код на расте наполовину состоит из муторного протаскивания ошибок по стеку. Немного паранойей это отдает. Ну как вариант можно забивать болт на все с unwrap.

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

Смотрю - в go их нет, а в Rust вместо них panic.

Ссылку уже дали, добавлю только, что в Го паника тоже есть. Причём обрабатывается несколько похожим на try/catch образом. Собственно, как и в расте. То есть, возможность есть, просто она специально сделана неудобной, ну и «не рекомендуется» для всеобщего использования.

Перехватить панику (в расте) (уже) можно не только на границе потоков.

как в Расте обстоит дело с обработчиками сигналов (ведь он претендует на роль С).

Сейчас в языке нет ничего специально для этого. Возможно, будет:

https://github.com/rust-lang/rfcs/issues/1368

https://github.com/rust-lang/rust/issues/11203

В библиотеках можно ещё поискать. Вот тут ещё обсуждается, если интересно:

https://users.rust-lang.org/t/unix-signals-in-rust/733/12

ПРоверять коды возврата, как это обычно делается в С?

По ссылке это есть, но я всё-таки подчеркну, что Option/Result - это не совсем «коды возврата» так как случайно использовать ошибочный результат не получится. Ну и у них есть методы типа map/map_or и т.д., которые несколько уменьшают количество кода типа

if error return error
Плюс макрос try!. Ну и есть эксперименты типа

https://github.com/TeXitoi/rust-mdo

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

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

Там tmtowtdy в худшем виде: пять разных способов, один «краше» другого.

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

Немного паранойей это отдает.

Дык, язык пытается быть безопасным и всё такое. Это ещё одно подчёркивание идеи, что ошибки надо обрабатывать.

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

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

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

Многое из этих «разных вариантов» - просто разные способы уменьшить количество кода.

Так это плохо же. Лучше бы один каноничный способ. Мне показался самым человечным матчинг вариантов, хоть он и самый многословный. А его пытаются «упростить» все более запутанными приемами. Как работает try! я так толком и не понял, надо на практике смотреть. После исключений это все боль, хоть и ясно, что исключения не айс.

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

try! я так толком и не понял

macro_rules! try {
    ($e:expr) => (
        match $e {
            Ok(result) => result,
            Err(err) => return From::from(err),
        }
    );
}
anonymous
()
Ответ на: комментарий от anonymous

Матчинг не может быть каноническим. Это всего лишь одна управляющая конструкция, не универсальная. Не факт, что её хватит, чтобы рассортировать ситуации в конкретном случае.

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

Например:

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

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

переполнение стека - можно что-то делать, но стек закончился (или его немного осталось)

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

Пришёл сигнал Ctrl-Break - что делать? В одном случае это может означать закрытие приложения, а в другом нужно пискнуть, не прекращая работы.

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

Так это плохо же. Лучше бы один каноничный способ.

Дык, большинство способов отличаются не больше, чем возврат bool от возврата кода ошибки - в плюсах. «Принципиально разных» способов в расте два - как и почти везде: возврат результата и паника (те же исключения, только «неполноценные). И то, можно сказать, что не два, а полтора так как панику крайне рекомендуют использовать скорее как abort/terminate, то есть для крайних случаев, а не для обработки ошибок.

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

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

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

Значит, в моём языке на первых порах исключений не будет

Использование ADT для отказа от исключений в Rust работает благодаря обязательной статической типизации.

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

Эту фразу не понял. Когда руки дойдут до исключений, вернусь к этой теме. Сейчас на повестке дня стоят модули и система сборки. Спасибо за комментарии!

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

Начал читать эту статью.

fn double_arg(mut argv: env::Args) -> Result<i32, String> {
    argv.nth(1)
        .ok_or("Please give at least one argument".to_owned())
        .and_then(|arg| arg.parse::<i32>().map_err(|err| err.to_string()))
}
Мне вот интересно, как в реальной жизни работать с таким кодом? Можно ли, читая его со стек трейсом на руках, понять, где упало? Будет ли пошаговый отладчик, чтобы прошагать? Как поставить брекпойнт в конкретную точку кода?

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

Доставило объявление функции чтения файла. Не обошлось без двухэтажного шаблона:

fn file_double<P: AsRef<Path>>(file_path: P)
Я примерно понял, зачем. Но опыт работы с С++ заставляет в этом месте насторожиться. Возможно, что в расте какие-то другие, лучшие шаблоны.

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

Мне нравится эта цитата. Она мне нравится и сама по себе, и особенно тем, что она расположена в самой середине статьи:

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

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

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

use std::error;
use std::fmt;

impl fmt::Display for CliError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            // Оба изначальных типа ошибок уже реализуют `Display`,
            // так что мы можем использовать их реализации
            CliError::Io(ref err) => write!(f, "IO error: {}", err),
            CliError::Parse(ref err) => write!(f, "Parse error: {}", err),
        }
    }
}

impl error::Error for CliError {
    fn description(&self) -> &str {
        // Оба изначальных типа ошибок уже реализуют `Error`,
        // так что мы можем использовать их реализацией
        match *self {
            CliError::Io(ref err) => err.description(),
            CliError::Parse(ref err) => err.description(),
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            // В обоих случаях просходит неявное преобразование значения `err`
            // из конкретного типа (`&io::Error` или `&num::ParseIntError`)
            // в типаж-обьект `&Error`. Это работает потому что оба типа реализуют `Error`.
            CliError::Io(ref err) => Some(err),
            CliError::Parse(ref err) => Some(err),
        }
    }
}
Как я понял, причина надобности в таком коде состоит в строгости системы типов и в отсутствии наследования. Ниже сказано, что если мы добавляем третий тип ошибок, то достаточно добавить к типу ошибок новый вариант и добавить реализацию для From, чтобы преобразовать частный вид ошибок к типу CliError. А про то, что нужно ещё пополнить description и cause, не написано. Это автор упустил или я не заметил магию, которая сделает это самостоятельно?

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

(говоря по-человечески - юнион)

Это говоря по С/С++, а не по-человечески.

Как я понял, причина надобности в таком коде состоит ...

В представлении читабельной информации об ошибке.

в строгости системы типов и в отсутствии наследования.

Вот код по этой причине ты не показал, это реализации трейта From для разных ошибок.

А про то, что нужно ещё пополнить description и cause, не написано. Это автор упустил или я не заметил магию, которая сделает это самостоятельно?

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

anonymous
()
Ответ на: комментарий от den73
use std::error;
use std::fmt;

impl fmt::Display for CliError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            // Оба изначальных типа ошибок уже реализуют `Display`,
            // так что мы можем использовать их реализации
            CliError::Io(ref err) => write!(f, "IO error: {}", err),
            CliError::Parse(ref err) => write!(f, "Parse error: {}", err),
            _ => write!(f, "error: {}", Error::description(self)),
        }
    }
}

impl error::Error for CliError {
    fn description(&self) -> &str {
        // Оба изначальных типа ошибок уже реализуют `Error`,
        // так что мы можем использовать их реализацией
        match *self {
            CliError::Io(ref err) => err.description(),
            CliError::Parse(ref err) => err.description(),
            _ => "error: internal error",
        }
    }

    fn cause(&self) -> Option<&error::Error> {
        match *self {
            // В обоих случаях просходит неявное преобразование значения `err`
            // из конкретного типа (`&io::Error` или `&num::ParseIntError`)
            // в типаж-обьект `&Error`. Это работает потому что оба типа реализуют `Error`.
            CliError::Io(ref err) => Some(err),
            CliError::Parse(ref err) => Some(err),
            None
        }
    }
}

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

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

Мне вот интересно, как в реальной жизни работать с таким кодом

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

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

А если бы это был язык не с такой дуболомной системой типов, не нужно было бы писать

CliError::Io(ref err) => err.description(),
CliError::Parse(ref err) => err.description(),
_ => "error: internal error"
А было бы достаточно просто err.description(). Хотя я понимаю, что description для разных классов ошибок могут быть совершенно не родня друг другу. Но на этот случай есть динамическая типизация (в Лиспе, во всяком случае).

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

Вот чем мне не нравятся анонимусы - так это отсутствием идентичности.

Моя точка зрения - пока отказ от исключений не выглядит совсем уж обоснованным. Во всяком случае, конструкция try..finally выглядит скорее полезной, чем вредной. Я не понял, есть ли она в Расте.

Что касается макроса try!, то это как раз то, что облегчило бы боль от обработки ошибок в C. Особенно, при наличии finally. Впрочем, имя макросы, наверное можно сделать себе и try..finally.

Макросы - это хорошая вещь для выразительности языка, независимо от того, что ещё в языке наворочено.

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

Так а что ж они не сделали try finally на своих крутых макросах? Или может эти макросы не так уж хороши? И мне уже не кажется обработка ошибок в C болью после этой статьи про раст. Тут просто фееричные возможности усложнить и запутать простые вещи.

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

Мне интересно, как на Расте написать аналог

Прямой аналог никак (хотя я хз, что именно тебе надо). Но есть catch_panic.

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

Мне вот интересно, как в реальной жизни работать с таким кодом?

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

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

Но опыт работы с С++ заставляет в этом месте насторожиться.

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

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

Кстати, замечаю, что писатели на расте в мастерстве обфускации уже и кресты, и перл задвинули. Что ни пример, то мозгодробилка. Не знаю, это специальная олимпиада, или по-другому просто не получается?

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

конструкция try..finally выглядит скорее полезной, чем вредной

В плюсах оно традиционно делается через RAII. В расте тоже так можно.

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

Так а что ж они не сделали try finally на своих крутых макросах?

Может оно просто нафиг не нужно? При наличии деструкторов.

после этой статьи про раст

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

Исключения могут выглядеть изящнее, но ты-то С упоминаешь.

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

Только слепому/глухому не понятно, что Go с нами надолго.

Огонь баттхертов подогревает сердца хейтеров и дает им силы на постоянное нытье, ничего не поделать

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

Так а что ж они не сделали try finally на своих крутых макросах?

Так оно никому не надо. Весь код покрыт тестами, если не исключений, то не проблем и с деструкторами.

run

macro_rules! try_finally {
    (try $e:block finally $f:block) => ({
        struct Finally;
        
        impl Drop for Finally {
            fn drop(&mut self) {
                $f
            }
        }
        
        let _finally = Finally;
        let _result = $e;
        _result
    })
}

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

В плюсах оно традиционно делается через RAII. В расте тоже так можно.

RAII, наверное, заменяет try..finally, хотя я бы не сказал, что я хотел бы на каждый чих создавать новый тип. В некоторых случаях это выглядит костылём. Почему-то в лиспе я легко обхожусь без RAII за счёт макросов with-что-то. А вот без unwind-protect было бы туго. Т.е., unwind-protect как-то более первичен, что ли. С другой стороны, там, где RAII уже сделано, оно вроде как надёжнее (до тех пор, пока не создано две версии - одна защищённая, другая нет, и не появилась возможность их перепутать).

Мне интересно, как на Расте написать аналог

Прямой аналог никак (хотя я хз, что именно тебе надо).

Я намекаю всего лишь на то, что при желании и в Расте можно игнорировать ошибку. Хотя, наверное, это и правда несколько сложнее, чем просто игнорировать код возврата, как это легко делается в С. Игнорирование нужно будет записать словами. И это не относится к вопросу «что лучше - исключения или коды».

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

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

Вроде рабочий вариант, хотя мне не нравится, что для возврата надо использовать из блока макрос.

run

enum Finally<T> {
	Early(T),
	Regular(T),
}

macro_rules! finally_return {
	($t:expr) => ( return Finally::Early($t) );
}

macro_rules! finally {
	(try $t:block finally $f:block) => ({
		let try_block = || Finally::Regular($t);
		let result = try_block();
		
		$f;
		
		match result {
			Finally::Early(t) => return t,
			Finally::Regular(t) => t,
		}
	});
}

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

А вот это уже то, что надо.

run

enum Finally<T, E> {
	Early(T),
	Regular(E),
}

macro_rules! finally_return {
	($t:expr) => ( return Finally::Early($t) );
}

macro_rules! finally {
	(try $t:block finally $f:block) => ({
		let result = (|| Finally::Regular($t))();
		
		$f;
		
		match result {
			Finally::Early(t) => return t,
			Finally::Regular(e) => e,
		}
	});
}
anonymous
()
Ответ на: комментарий от anonymous

Не сказать, что понял, но верю, что ты сам понял. Хорошо. Это, кстати, для тех, кто вопил, что полноценные макросы возможны только в гомоиконном синтаксисе.

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

Да и что там такого ужасного?

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

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

Наркомания продолжается...

run

try(0)
catch(0) = Error::A
finally(0)
result(0) = Err(A)
------------------------------
try(1)
finally(1)
result(1) = Err(B)
------------------------------
try(2)
finally(2)
result(2) = Ok(16)
------------------------------

Program ended.
fn add_2(i: i32) -> Result<i32, Error> {
    if i != 1 {
        Ok(i + 2)
    } else {
        Err(Error::B)
    }
}

fn do_work(x: i32) -> Result<i32, Error> {
    finally! {
        try {
            println!("try({})", x);
            
            if x == 0 {
                fret!(Err(Error::A));
            }
            
            let n = ftry!(add_2(x));
            
            Ok(n * n)
        } catch Error::A => {
            println!("catch({}) = Error::A", x);
        } finally {
            println!("finally({})", x);
        }
    }
}

fn main() {
    for i in 0..3 {
        println!("result({}) = {:?}", i, do_work(i));
        for _ in 0..30 {
            print!("-");
        }
        println!("");
    }
}

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

RAII, наверное, заменяет try..finally, хотя я бы не сказал, что я хотел бы на каждый чих создавать новый тип.

В плюсах есть вещи типа BOOST_SCOPE_EXIT - то есть (руками) создавать новые типы не приходится. И это вполне аналог try/finally.

Почему-то в лиспе я легко обхожусь без RAII за счёт макросов with-что-то.

Дык, что тут является первопричиной? Точно так же джавист скажет, что он обходится без RAII и with-... макросов. Привычка.

Не знаю правда применимо ли это к with-... макросам, но относительно finally и using «для ресурсов» у деструкторов есть одно преимущество - если добавить в класс поле, в деструкторе которого надо что-то освобождать (то есть и внешний обьект становится ресурсом), то всё заработает автоматически, без изменения кода. А finally/using придётся дописывать.

до тех пор, пока не создано две версии - одна защищённая, другая нет

Как это?

Я намекаю всего лишь на то, что при желании и в Расте можно игнорировать ошибку.

Ну да, но ведь нет способа заставить обработать её «осмысленно». Да и если бы способ был, то это не всегда нужно.

Про шаблоны - плохо то, что разные реализации шаблона не родственники даже в С++.

Если хочется «родственности», то взаимодействовать с шаблонами надо через шаблонные функции:

template <typename T>
void foo(std::vector<T> v) { ... }
Достаточно одной функции для разных реализаций.

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