Size: a a a

2020 October 14
Блог*
Какое покрытие полезнее?
Анонимный опрос
19%
Кода тестами
81%
Антипригарное
Проголосовало: 358
источник
2020 October 15
Блог*
Беды с баш #rust-ом (#prog)
источник
Блог*
Переслано от Vlad Beskrovnyy
Из-за отсутствия внятной спецификации, в расте то и дело возникают вопросы "'это баг или фича?". И когда все-таки решат, что баг, может выясниться, что он уже всеми повсюду используется, как фича. И вроде и людей в этом не обвинишь, ведь нигде не написано, что это баг - ведь ланг дезигнеры и сами внятно сказать не могли, баг или не баг! Приходится что-то выкостыливать. Вот напр я репортил - https://github.com/rust-lang/rust/issues/74556
Тут сначала не могли понять, баг или не баг. Потом все же решили, что баг. Потом выяснили, что он уже есть в нескольких стабильных релизах. Потом исправили, но выяснили, что кто-то баг уже использует, так что на новой найтли что-то перестало компилиться. Вроде решили, что и фиг с ним
источник
2020 October 16
Блог*
#meme #bio
источник
Блог*
источник
2020 October 18
Блог*
#prog #computergraphics

Статья (перевод) о попытке сделать рендеринг поверх не чисел с плавающей точкой, а поверх рациональных чисел. Спойлер: для получения приемлемых результатов автору пришлось реализовать арифметику с "плавающей дробной чертой"
источник
Блог*
Collection of articles on good practices and tools to improve C code quality

Интересно тем, что от автора LZ4 и ZSTD (можно сказать лучшие компрессоры данных, да, на Си)

https://github.com/Cyan4973/Writing_Safer_C_code
источник
2020 October 19
Блог*
источник
Блог*
#prog #rust #моё

Рассмотрим следующий код на Rust:

// код опущен для краткости

fn main() {
   let tricky = <Tricky as Default>::default();
   assert_eq!(tricky.field, 7);
   assert_eq!(tricky.field, 4);
   assert_eq!(tricky.field, 13);
   
   let mut nice_try = <NiceTry as Default>::default();
   assert_eq!(nice_try.field, 0);
   nice_try.field += 12;
   assert_eq!(nice_try.field, 0);
   
   let (mut a, mut b) = <(Unstable, Unstable) as Default>::default();
   assert_eq!((a.field, b.field), (0, 0));
   std::mem::swap(&mut a.field, &mut b.field);
   assert_eq!((a.field, b.field), (10, 10));
}


Очевидно, этот код не работает. И действительно, если мы запустим эту программу, то один из ассертов запустит панику. В данном случае это будет... Будет... Погодите-ка, оно работает? Что?! Но ведь в Rust нет свойств!

Окей, признаю, я вас немного обдурил, вся фишка кроется в коде, который я не показал. Разумеется, я его покажу, а попутно затрону две темы в Rust: deref coercion и interiour mutability.

Но сначала давайте поговорим о слайсах. Они достаточно удобны. Их можно индексировать и снова получать слайсы. Для них определено множество полезных методов: сортировка, поворот, обращения порядка, линейный и бинарный поиски... Однако, все эти методы можно также вызвать и на массивах, и на Vec. Как это работает? Неужели реализация этих методов скопирована с методов для слайсов? Конечно, нет.

Давайте посмотрим на метод Vec::sort. Этот метод описан в блоке "Methods from Deref<Target = [T]>". Что это означает? В стандартной библиотеке Rust (на самом деле core) есть трейт Deref, описанный следующим образом:

trait Deref {
   type Target: ?Sized;
   fn deref(&self) -> &Self::Target;
}

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

trait DerefMut: Deref {
   fn deref_mut(&mut self) -> &mut Self::Target;
}

Возникает закономерный вопрос: а чем они отличается от AsRef и AsMut, которые также выполняют преобразования между ссылками? Ну, во-первых, в силу того, что целевой тип преобразования у Deref и DerefMut является ассоциированным типом, а не обобщённым параметром, у любого типа может быть не более одной реализации Deref и DerefMut. А во-вторых — и это уже куда как более существенное отличие — это один из "магических" (известных компилятору) трейтов, методы которого часто вызываются неявно. Конкретно методы Deref{, Mut} вызываются для кастомных реализаций операторов разыменовывания, а также ещё неявно в некоторых контекстах, в частности, при вызове методов через точку. Это, в том числе, позволяет эргономично пользоваться умными указателями. Если на минуту забыть о том, что Vec может менять свою длину, мы можем считать его ссылкой на лежащий в куче массив с длиной, известной лишь на этапе исполнения. То есть... Указатель на слайс. И действительно, Vec реализует Deref{, Mut}<Target = [T]>, что позволяет вызывать на нём методы, определённые для слайса. И при этом без каких-либо дополнительных телодвижений с вызывающей стороны!

Ладно, это было поучительно, но какое оно имеет отношение к безобразию, показанному в начале? Дело в том, что одна из (почему-то малоизвестных) ситуаций, когда методы этих трейтов могут вызваны — это... Доступ к полю. Если в коде есть foo.field и foo имеет тип Foo, у которого нету поля field, но Foo реализует Deref<Target = Bar>, где у типа Bar есть поле field, то такое обращение к полю будет корректно и будет вызывать deref (или deref_mut). Все те странные структуры, которые я показал, так или иначе оборачивают структуру Simple:

#[derive(Default)]
struct Simple {
   field: u32,
}
источник
Блог*
Давайте сначала разберём NiceTry. Вот её определение:

#[derive(Default)]
struct NiceTry {
   first: Simple,
   second: Simple,
}

У нас есть два поля одного типа. Как же работает трюк с подменой? Очень просто: в Deref::deref мы возвращаем ссылку на одно поле, а в DerefMut::deref_mut — на другое:

impl Deref for NiceTry {
   type Target = Simple;
   fn deref(&self) -> &Self::Target {
       &self.first
   }
}

impl DerefMut for NiceTry {
   fn deref_mut(&mut self) -> &mut Self::Target {
       &mut self.second
   }
}

Неудивительно, что мы не могли поменять field — мы не могли получить к нему мутабельный доступ!

Unstable устроен несколько сложнее. Если у нас есть мутабельный доступ к полю внутри метода, то мы можем его... Мутировать. То есть поменять. Собственно, именно это и происходит в реализации deref_mut:

#[derive(Default)]
struct Unstable(Simple);

impl Deref for Unstable {
   type Target = Simple;
   fn deref(&self) -> &Self::Target {
       &self.0
   }
}

impl DerefMut for Unstable {
   fn deref_mut(&mut self) -> &mut Self::Target {
       self.0.field += 10;
       &mut self.0
   }
}

"Ладно" — может сказать кто-то из моих читателей — "я понимаю, как работает трюк с NiceTry и Unstable, но что, чёрт побери, происходит с Tricky? Мы ведь даже не используем мутабельный доступ к Tricky, так что мы не можем что-то там поменять!". Что ж, мой недоумённый читатель (а также другие), я вынужден раскрыть одну из самых грязных тайн Rust: &mut T на самом деле не означает "изменяемая ссылка"! Что же это тогда? Фактически, &mut T означает уникальную ссылку на значение типа T — то есть такую, что в любой момент времени одновременно с этой ссылкой не существует никаких других. Почему это так важно? Дело, что одна из вещей, которая является в Rust неопределённым поведением — это гонка данных, ситуация, когда доступ к одному и тому же значению происходит из двух (или более) разных мест одновременно, и при этом как минимум один доступ — на запись. Простейший (концептуально, разумеется) доступ добиться отсутствия гонок данных — это убедиться, что в любой момент времени только кто-то один имеет изменяемый доступ к данным. Это именно то, что компилятор Rust проверяет, используя концепции владения и заимствования. Но это не единственный способ!

Тот же анализ заимствований, который компилятор Rust делает на этапе компиляции, можно делать во время исполнения. Именно этот паттерн и реализует RefCell. Этот тип подсчитывает количество ссылок доступов к данным во время исполнения и паникует, если в какой-то момент должны возникнуть одновременно изменяемый доступ и хоть сколько-то неизменяемых — или более одного изменяемого. На проблему гонок данных также можно взглянуть с другого конца. Гонка данных считается неопределённым поведением, поскольку для многих типов данных операция обновления не является атомарной, т. е. неделимой. Если два ядра будут менять значение одновременно, то это может привести к тому, что промежуточные операции обновления будут чередоваться друг с другом и привести к тому, что в памяти значение окажется в неожиданном состоянии — неожиданное для того, кто писал код в расчёте на атомарность изменений (на самом деле тут есть ещё более сложные противные вещи, связанные с кешами процессоров, но сегодня я не хочу в это углубляться). Однако для простых значений, умещающихся в одно машинное слово, процессор на самом деле может гарантировать атомарность изменений. Тогда, если два ядра одновременно изменяют атомарное значение, оба этих изменения будут учтены. В таком случае гонка данных просто не может причинить вред, а значит, атомарные значения вполне можно отдавать по разделяемым (&) ссылкам. И действительно, если мы откроем документацию к, скажем, AtomicU32, то мы увидим, что практически все методы принимают &self.
источник
Блог*
Но что же используется внутри Tricky? Какое-то атомарное значение? Нет, field — это просто u32, а сделать атомарной саму структуру Simple мы не можем. RefCell? Тоже нет. Метод RefCell::borrow возвращает значение особого типа, которое реализует Deref в оборачиваемые данные и которое в деструкторе уменьшает счётчик неизменяемых доступов. Но, напомним, в методе deref нам нужно вернуть именно ссылку. Единственный способ получить получить ссылку на внутреннее значение в RefCell — это вызвать borrow{, _mut}, который вернёт тот прокси-тип. Но Rust просто не даст нам вернуть ссылку на локальную переменную! Выходит, этот вариант тоже отпадает. Что же тогда используется в Tricky?

Ответ — Cell! Этот тип оборачивает значение и позволяет менять их по разделяемой ссылке. Почему же это не может привести к гонкам данных? На это есть две причины. Первая — API у Cell позволяет доставать (копию) внутреннего значения, переписывать значение, замещать его другим (в том числе и значением по умолчанию) и обмениваться значением с другим Cell, но не даёт возможности получить разделяемую ссылку на значение внутри — это дало бы возможность передать ссылку в другой поток и организовать чтение из одного потока и запись в другом (кстати, достать &mut ссылку можно, ибо её уникальность гарантирует компилятор). Вторая причина — и это, на мой взгляд, очень хорошо демонстрирует силу Rust — Cell не реализует Sync, то есть &Cell<T> нельзя пересылать из одного потока в другой. Это означает, что в любой момент времени ссылки на Cell, если таковые есть, все принадлежат одному потоку, то есть коду, который по отношению к Cell исполняется последовательно. Таким образом, даже не смотря на то, что доступ к Cell может быть из нескольких мест и каждый доступ может менять значение внутри, последовательность (в смысле "не параллельность") исполнения кода гарантирует, что они доступы не могут быть активны одновременно.

Вооружившись этим знанием, нетрудно понять, как реализована Tricky:

struct Tricky {
   choice: Cell<Choice>,
   first: Simple,
   second: Simple,
   third: Simple,
}

impl Deref for Tricky {
   type Target = Simple;
   fn deref(&self) -> &Self::Target {
       let choice = self.choice.get();
       self.choice.set(choice.next());
       match choice {
           Choice::First => &self.first,
           Choice::Second => &self.second,
           Choice::Third => &self.third,
       }
   }
}

Здесь Choice — это некоторое перечисление, которое являет собой выбор из трёх вариантов. В deref код обновляет выбор поля и в соответствии с предыдущим значением choice выбирает нужное поле.

Обычно значение в Rust можно поменять в том случае, если код им владеет или имеет &mut-ссылку на него, но нельзя через &-ссылку. Это называется inherited mutability (наследуемая изменяемость), поскольку значение "наследует" изменяемость обёртки. Однако, как мы видели, значение может быть изменяемым и через &-ссылку. Это называется interiour mutability (внутренняя изменяемость). Это нужная на практике вещь, поскольку Rc/Arc и мьютекс с безопасным интерфейсом без неё создать невозможно.

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

PS: код.
источник
Блог*
Дальнейшее чтение:

- UnsafeCell — единственный тип, который можно легально (но, конечно, небезопасно) менять по разделяемой ссылке. Абсолютно все типы с interiour mutability в конечном счёте опираются именно на него.
- Focusing on ownership — старая (до выхода Rust 1.0) статья Никитоса Niko Matsakis, в которой он предлагает заменить &mut на &uniq, убедительно аргументируя, что это куда точнее отражает суть. Это предложение вызвало в своё время бурные обсуждения, но в итоге, как вы видите, так и не было принято. Возможно, и зря.
- Common Rust Lifetime Misconceptions (мой перевод) — вскользь касается темы уникальности ссылок в пункте 9.
- Deadlock empire — офигенная образовательная игра, в которой нужно взять на себя роль планировщика потоков и, манипулируя порядком исполнения шагов, сломать программу (завести в дедлок, заставить два потока одновременно зайти в критическую секцию и т. д.). Наглядно показывает, насколько сильно в многопотоке мешается неатомарность изменений. Если честно, даже странно, что я раньше её не выкладывал.
- Rustonomicon, конкретно раздел про параллельное исполнение. nuff said.
- Readonly — хтонический макрос от Толяна, который позволяет сделать помеченные поля публичными, но при этом неизменяемыми (извне крейта). Также абьюзит Deref.
источник
2020 October 21
Блог*
#prog #cpp #meme
источник
Блог*
источник
Блог*
#prog #haskell #meme
источник
Блог*
источник
Блог*
#prog #article #amazingopensource

Алгоритм валидации UTF-8 практически без условных переходов, использующий меньше одной процессорной инструкции на байт входных данных. Реализация доступна как часть библиотеки simdjson
источник
2020 October 22
Блог*
кстати, а ведь vi/vim хорошо звучит как нейтральная версия на замену множественному числу they/them
источник
Блог*
#prog #rust

Пара советов по строкам в Rust:

1) Если вам нужно разбить строку по одному из нескольких возможных символов — не спешите расчехлять регулярки, для это задачи вполне хватит стандартной библиотеки. Множество строковых методов навроде {, r}split{, _terminator}, trim{, _start, _end}_matches, find и прочие принимают в качестве аргумента для поиска значение, тип которого реализует пока нестабильный трейт Pattern. В настоящий момент его реализуют &str, &&str, &String, impl FnMut(char) -> bool и (почему-то малоизвестный) &[char]. Таким образом, разбить строку по нескольким символам легко:

let result = "Hello, world!".split(&['o', 'l'][..]).collect::<Vec<_>>();
assert_eq!(result, vec!["He", "", "", ", w", "r", "d!"]);


2) Если функция принимает на вход &mut std::str::Chars, что она может с ним сделать?

Вызвать `next`?

Нет.

Также вызвать `next_back`?

Нет.

Это всё неполные ответы. Если мы получаем мутабельную ссылку на Chars, мы можем редактировать произвольным образом, в том числе и поменять его целиком. Chars внутри себя содержит строки, символы которой он перебирает, и при помощи метода Chars::as_str эту строку можно достать. Таким образом, имея мутабельную ссылку на Chars, можно вытащить из него строку, вырезать из строки нужный кусок и переписать переданный итератор .chars() от этого кусочка.

Покажу на примере.

Вот первый способ вытащить строку из Chars (медленный, требующий аллокаций и не совсем корректный):

fn extract_line2(chars: &mut Chars) -> String {
   chars.take_while(|&ch| !matches!(ch, '\r' | '\n')).collect()
}


Второй способ (побыстрее и не требующий аллокаций):

fn extract_line<'s>(chars: &mut Chars<'s>) -> Option<&'s str> {
   let s = chars.as_str();
   let line = s.lines().next()?;
   *chars = s[line.len()..].chars();
   Some(line)
}
источник
Блог*
#prog #article

Статья об антипаттерне использования File.exists.

да, это статья десятилетней давности, ну и что?
источник