Комментарии 46
В начале года они пообещали, что этот год пройдёт под эгидой упрощения разработки и снижения порога входа, и они держат свои обещания.
Очень жаль, что impl Trait находится в неопределенном состоянии (на то имеются веские причины). А остальное вполне движется. Я уже несколько месяцев пользуюсь VSCode + RLS и оно «просто работает».
Не уверен что я прав, но похоже RLS используюет инкрементальную компиляцию, что в последнее время здорово ускорило анализ изменений. Это помимо того, что они запилили специальный режим работы компилятора, который выполняет только проходы, необходимые для сбора информации о коде (без лишней кодогенерации).
А с учетом того что все это теперь распространяется через rustup, то начать пользоваться можно в пару команд.
Кстати есть вопрос. Допустим у меня происходит парсинг html-кода, который должен вернуть какое-то число.
fn parse_page_count(document : kuchiki::NodeRef) -> i32
{
let parent = document.select(".bottom_info #pagination p").unwrap().nth(0).unwrap();
let firstBold = parent.as_node().select("b").unwrap().nth(1).unwrap();
let result = firstBold.as_node().as_text().unwrap().borrow().parse::<i32>();
match result
{
Err(_) => panic!("Failed to parse page count"),
Ok(pagesCount) => pagesCount
}
}
Как вообще правильно и принято в Rust оборачивать все это в проверкии и возвращение ошибки? Как использовать оператор "?" вместо кучи unwrap? Какой возвращаемый тип должен быть у функции (понимаю, что Result, но как объединить для всего).
Еще местами не понятны, когда в цепочке вызовов как выше компилятор бросается ошибкой, что переменная уже borrowed, поэтому приходится создавать временную переменную и использовать ее дальше в цепочке. Неясно почему компилятор сам не может этого сделать/понять. В общем после обычных языков первое время очень долго думаешь :)
Как вообще правильно и принято в Rust оборачивать все это в проверкии и возвращение ошибки?
С помощью Result.
Описание самого принципа обработки ошибок (тут требуется поправка — вместо макроса try! используется сахар ?, принцип действия точно такой же)
impl ServerState {
pub fn parse(&mut self, message: &str) -> Result<(), &'static str> {
for (k, v) in parse_table(&message) {
match k {
"frame" => self.frame = Rc::new(Frame::parse_from(v)?),
"deaths" => self.deaths = v[1 .. v.len()-1] // strip quotes
.split(',')
.filter(|e| e.len() > 0) // a,,b
.map(|x| x.parse().map_err(|_| "parse error"))
.collect::<Result<_,_>>()?,
// TODO
_ => unimplemented!()
}
}
Ok(())
}
// ...
}
Из функции я выбрасываю колхозную ошибку
&'static str
, ибо лень и прототип. Фукнции парсинга возвращают нормальные ошибки, которые надо привести к строке.Самое интересное происходит в обработке «deaths»: с помощью функциональной магии и трансформации монад мы преобразуем Collection<Result<T, E>> в Result<Collection, E>>, который потом разворачиваем с помощью ?.
Если у вас есть операция, которая возвращает к примеру io::Result, а ваша функция определяет свой тип ошибки, то к вашим услугам метод map_err().
fn create_player(player: &Player, tattva: Alb<Tattva>, storage: Al<Storage<Player, PlayerParams>>, idf: Al<IdFactory>) -> Result<Player, PlayerInitializeError> {
let universe = get_universe(player.uid)?; // <- get_universe возвращает Result<Universe, PlayerInitializeError>. Если она вернула error, create_player тоже его возвращает тут же. Похоже на проброс исключения в Java.
let mut player = player.clone();
player.id = idf.write().unwrap().player();
let params = PlayerParams {
universe: universe.name.clone(),
lookup: PlayerParamsLookup::Id(player.id),
};
storage.write().unwrap().save(&player, ¶ms).map_err(|e| {
error!("[{}] Error saving player {} '{}': {}", SCENE_NAME, player.id, player.name, e);
PlayerInitializeError::CannotSave
})?; // save возвращает Result<(), StorageError>. StorageError нельзя вернуть вместо PlayerInitializeError, поэтому мы его преобразовываем с помощью map_err, а заодно и логгируем. Можно враппер написать, но обычно незачем, если все логгируется.
Ok(player)
}
Вот пока не совсем понимаю где можно использовать clone, где вот эти into. Даже в вашем примере, насколько правильно создавать копию игрока.
А так крутой язык, всем советую попробовать. Сначала казался бесполезным и не стоящим своего изучения.
Но вот если разработчики еще собираются работать над упрощением порога входа, то должно быть вообще круто.
Им бы еще поработать в направлении веба, чтобы было удобно писать бекенд. Понимаю, что это не ниша языка изначально, но сейчас, например, переписываю для своего сайта с аудиокнигами парсеры на Rust, все это мультипоточно в виде демона, было бы удобно использовать одни и те же структуры парсеров и бекенда сайта.
let parent = document.select(".bottom_info #pagination p").unwrap().nth(0).unwrap();
let firstBold = parent.as_node().select("b").unwrap().nth(1).unwrap();
let result = firstBold.as_node().as_text().unwrap().borrow().parse::<i32>();
Почему код выше работает, а если объединить в одну строчку:
let firstBold = parent.as_node().select("b").unwrap().nth(1).unwrap().as_node().as_text().unwrap().borrow().parse::<i32>();
То матерится вот так
Формально rust прав. Если временный объект не доживает до конца области видимости, можно получить подвисший указатель и долго искать в чем плавающий баг.
Получается, без знания сигнатур функций и их возвращаемых значений нереально писать код, который будет компилироваться с первого раза.
В общем случае понятно. Раст заставляет вас пострадать, при написании кода, зато в результате получаем гарантию, что не придется дебажить потом непонятные вылеты и проблемы с памятью.
Да, и чем дальше в лес, чем крупнее проект, тем это качество всё важнее.
При этом попутно происходит ещё и обучение — почему так или иначе делать нельзя или просто плохо в других языках, у которых нет такого строгого контроля.
А с опытом эта «борьба» с компилятором проходит, начинаешь чётко представлять как всё будет работать, и эти ошибки из-за непонимания перестают отнимать время.
Кстати, очень рекомендовал бы сразу изучить вдоль и впоперек стандартную библиотеку — она минималистична и это не отнимет много времени, зато многие "тупиковые" вопросы, когда непонятно что делать, будут сняты.
let owned; //
let maybe_foo = if some_condition {
thing.get_ref()
} else {
owned = thing.get_owned();
owned.as_ref()
};
http://manishearth.github.io/blog/2015/05/17/the-problem-with-shared-mutability/
http://manishearth.github.io/blog/2015/05/27/wrapper-types-in-rust-choosing-your-guarantees/
http://manishearth.github.io/blog/2015/05/30/how-rust-achieves-thread-safety/
http://manishearth.github.io/blog/2017/04/13/prolonging-temporaries-in-rust/
http://manishearth.github.io/blog/2017/01/10/rust-tidbits-box-is-special/
Если я правильно понял то он нуждается в плагинах для кодогенерации, да?
А вообще есть какие-то планы у Rust по стабилизации данных фич?
https://github.com/SergioBenitez/Rocket/blob/30fac3297/lib/src/lib.rs#L1-L10
довольно много всего:
#![feature(specialization)]
#![feature(conservative_impl_trait)]
#![feature(drop_types_in_const)]
#![feature(associated_consts)]
#![feature(const_fn)]
#![feature(type_ascription)]
#![feature(lookup_host)]
#![feature(plugin)]
#![feature(never_type)]
#![feature(concat_idents)]
Into/From — паттерн Adapter из ООП, преобразует одни типы в другие, заодно является полу-сахаром для компилятора, если надо использовать (вернуть из функции, передать в функцию) объект, несовместимый с сигнатурой, но зато имеющий реализацию Into или From. Но тоже явно, методы из типажей надо вызывать вручную.
При чтении можно мысленно заменять try!() на ?, смысл будет тот же.
Не обязательно делать map_err, оператор? умеет автоматически конвертировать типы ошибок если для них определен трейт From.
Насколько я понял, была годная реализация корутин, но она оказалась несовместимой со стандартной библиотекой из-за того, что последняя активно использует TLS. И на данный момент корутины в принципе не в приоритете. Было бы хорошо, если бы кто-то внес уточнения, вдруг я что-то упустил.
Для начала, std::async из с++ это просто потоковая функция с гардом, на котором можно подождать окончания и получить результат. Полный аналог https://doc.rust-lang.org/std/thread/fn.spawn.html. Если хотите полноценной асинхронности — https://tokio.rs/
За наводку на https://tokio.rs/ — спасибо.
Вот в этом и проблема, что смешали в кучу 2 разных инструмента, добавив столь любимую Комитетом горстку implementation-defined. Нет, даже 3. Потому что запуск в эксклюзивном потоке и на тред пуле — две большие разницы.
Хотя нет, если http://en.cppreference.com/w/cpp/thread/async ничего не упускает — всегда или новый поток, или ленивое значение. Однако это всё равно крайне плохое решение. Во-первых, это таки смешивание двух совершенно разных вещей в одну кучу. Во-вторых, что гораздо хуже, выбор производится флагами (!), да к тому же если указаны оба флага, выбор implementation-defined (!!!). В общем грусть-печаль от такой стандартной библиотеки.
Выпуск Rust 1.17