BufWriter<Master<'_>>
105 subscribers
451 photos
28 videos
34 files
1.71K links
https://www.patreon.com/alxe_master

Видео/статьи. Конспект и мои вольные комментарии по инженерии. тут только то, что считаю полезным для себя или других =)

#os, #cloud, #rust, #golang, #python, #javaScript, #cpp, etc
Download Telegram
Forwarded from Блог*
#prog #rust #моё

В Rust в некоторых случаях можно использовать анонимный лайфтайм ('_). Практика показывает, что эта фича некоторым не до конца понятна, поэтому я решил написать об этом подробнее.

Времена жизни — пожалуй, именно та фича, которая больше всего делает Rust непохожим на мейнстримные языки программирования. В частности, их синтаксис ('identifier) — это то, что приходит первым на ум тому, кто уверяет, что у Rust нечитаемый синтаксис. Тем не менее, если взглянуть на код программы на Rust, то этих времён жизни можно увидеть очень мало. Казалось бы, как так может быть, если учесть, что каждая ссылка параметризована временем жизни, а ссылки в Rust используются достаточно активно? Дело в том, что бо́льшая часть вариантов использования времён жизни подпадает под один из достаточно простых паттернов, для которых компилятор в состоянии вывести отсутствующие времена жизни сам. Это называется lifetime elision, и правила, по которым оно происходит, перечислены в растономиконе.

Раскрытие сокращённой записи начинается с введения нового явного параметра для каждого аргумента, тип которого параметризован временем жизни (далее ВЖ), но для которого конкретное значение ВЖ не указано. Например, если у нас функция

fn do_something(a: &mut u32, b: (&u32, &u32), c: Cow<str>) { ... }

, то после первого шага преобразования она выглядит так:

fn do_something<'lt1, 'lt2, 'lt3, 'lt4>(a: &'lt1 mut u32, b: (&'lt2 u32, &'lt3 u32), c: Cow<'lt4, str>) { ... }

Обратите внимание, это работает не только с ссылками.

Далее компилятор пытается приписать времена жизни возвращаемому типу. В обычном коде произвольные времена жизни не материализуются из ничего, они появляются из ссылок на имеющиеся значения. Логично предположить, что если у тебя есть функция с ВЖ, то ВЖ возвращаемого типа должно быть связано с аргументами. Если аргументов нет вовсе, то компилятор откажется компилировать функцию. Если аргумент есть только один и с одним обобщенным параметром ВЖ, то возвращаемый тип параметризуется этим ВЖ для всех возможных обобщённых параметров. Например, если есть функция

fn first_and_second(arg: &(u32, u32, u32))- > (&u32, &u32) {
(&arg.0, &arg.1)
}

, то её развёрнутый тип будет

fn first_and_second<'a>(arg: &'a (u32, u32, u32))- > (&'a u32, &'a u32) {
(&arg.0, &arg.1)
}

Что же делать, если аргументов несколько? В случае, когда функция принимает &self или &mut self, ВЖ возвращаемого типа приравнивается ВЖ self. Из этого, кстати, следует несколько неожиданный результат, что нижеприведённый код не компилируется:

struct Foo;

impl Foo {
fn use_str(&self, s: &str) -> &str {
s
}
}

Если расписать тип полностью, то станет понятно, почему:

struct Foo;

impl Foo {
fn use_str<'foo, 's>(&'foo self, s: &'s str) -> &'foo str {
s
}
}

Действительно, lifetime elision приводит к тому, что возвращаемая строка имеет то же ВЖ, что и self, но в теле функции используется строка с другим ВЖ 's, которое никак не связано с 'foo. Для того, чтобы решить эту проблему, нужно явно ввести обобщённый параметр времени жизни и указать, что он один и тот же у аргумента и возвращаемого значения:

struct Foo;

impl Foo {
fn use_str<'s>(&self, s: &'s str) -> &'s str {
s
}
}

Такой код уже компилируется.
блинский. только сейчас заметил что мне выдает компилятор при попытке скомпилировать асинхронный трэйт в #rust... и этот пакет прямо рекламируется! и действительно весь геморой с ними решаются макросами и афигенно рботает !

https://crates.io/crates/async-trait

рубрика "надо чаще обновлять компилятор раста" много нового можно черпануть)))
Unterstanding Rust’s Vec and its capacity for fast and efficient programs
https://markusjais.com/unterstanding-rusts-vec-and-its-capacity-for-fast-and-efficient-programs/
про выделение памяти для вектора в #rust
Thesis.pdf
748.3 KB
Zero-copy #rust k-v store
Forwarded from Блог*
#prog #rust

Пара слов о трейте Copy.

Во-первых, это один из "магических" трейтов, помечен атрибутом #[lang = "copy"]. Компилятор проверяет, что если тип тем или иным способом задекларирован как Copy, то и всего его поля должны быть Copy. Следующий код не компилируется:

#[derive(Clone, Copy)]
struct StringHolder(String);

Ошибка:

error[E0204]: the trait `Copy` may not be implemented for this type
--> src/lib.rs:1:17
|
1 | #[derive(Clone, Copy)]
| ^^^^
2 | struct StringHolder(String);
| ------ this field does not implement `Copy`

Во-вторых, один и тот же тип не может реализовывать Copy и Drop одновременно. Скажем, следующий код не компилируется:

#[derive(Clone, Copy)]
struct Primitive;

impl Drop for Primitive {
fn drop(&mut self) {}
}

Ошибка:

error[E0184]: the trait `Copy` may not be implemented for this type; the type has a destructor
--> src/lib.rs:1:17
|
1 | #[derive(Clone, Copy)]
| ^^^^ Copy not allowed on types with destructors

Так как полноценных negative trait bounds в Rust всё ещё нет, ограничение Copy можно использовать как более сильную замену ограничению !Drop.

В-третьих, как сказано в документации к Copy, "Copies happen implicitly, for example as part of an assignment y = x. The behavior of Copy is not overloadable; it is always a simple bit-wise copy.". С другой стороны, Clone является супертрейтом Copy, и наличие реализации Copy не мешает вызывать .clone() явно. Однако, в отличие от Copy, Clone::clone в принципе может вызывать любой код. Поэтому, в частности, технически замена в итераторной цепочке .cloned() на .copied() является ломающим изменением, даже если элементы итератора реализуют Copy.

Продемонстрирую на примере:

use std::cell::Cell;

struct Counter<'a>(&'a Cell<u32>);

impl Clone for Counter<'_> {
fn clone(&self) -> Self {
self.0.set(self.0.get() + 1);
Self(self.0)
}
}

fn main() {
let n = Cell::new(0);
let arr = [Counter(&n), Counter(&n), Counter(&n)];
arr.iter().cloned().for_each(drop);
println!("`Counter::clone` called {} times", n.get());
}

Если запустить этот код, то он напечатает `Counter::clone` called 3 times. Теперь добавим к определению Counter аннотацию #[derive(Copy)] и допишем в main тот же код, что там есть, но с заменой .cloned() на copied():

fn main() {
// ...

let n = Cell::new(0);
let arr = [Counter(&n), Counter(&n), Counter(&n)];
arr.iter().copied().for_each(drop); // изменение в этой строке
println!("`Counter::clone` called {} times", n.get());
}

Код теперь выводит:

`Counter::clone` called 3 times
`Counter::clone` called 0 times

Берегите себя и не пишите, пожалуйста, в Clone::clone что-то помимо собственно клонирования.
== High Velocity Kernel File Systems with Bento
https://www.usenix.org/system/files/fast21-miller.pdf
а-е. тут есть чему поучиться. как сделать фс на #rust
Forwarded from Блог*
#prog #rust #моё

Меня тут один Олег попросил коротко рассказать о афинных типах в Rust. Что ж, рассказываю.

Аффинные системы типов — это системы типов, в которых объявленные значения можно использовать не более одного раза. Как и прочие ти́повые навороты, это позволяет писать более корректные программы путём перекладывания бо́льшего числа проверок на компилятор.

Для демонстрации практической пользы приведу пару примеров из стандартной библиотеки Rust:

1. std::sync::Mutex. Для корректной работы многопоточной программы требуется, чтобы доступ к совместно разделяемым изменяемым данным был должным образом синхронизирован. Один из способов достичь его — это защитить изменяемое значение мьютексом. Простой способ, обладающий, однако, существенным недостатком: очень просто забыть захватить блокировку перед тем, как получить доступ к значению (особенно если мьютекс защищает несколько переменных). Какое решение предлагает Rust?

Посмотрим на то, как создать мьютекс. Единственный способ создать мьютекс — это передать ему защищаемое значение. После этого получить доступ к разделяемому значению можно, только попытавшись захватить блокировку или же разрушив мьютекс, причём последнее можно сделать только в том случае, если поток (в смысле thread) единолично владеет мьютексом. Таким образом, несинхронизированный доступ исключён.

Другая возможная проблема с мьютексом связана с тем, что в большинстве языков программирования значения неявно копируются: нужно прилагать специальные усилия для того, чтобы удостовериться, что каждый из thread-ов получает один и тот же мьютекс, а не свою собственную копию (тут была шутка про Go, но она была настолько толстой, что Telegram не давал загрузить пост). В Rust это получается автоматически: нет методов, позволяющих получить копию мьютекса, поэтому расшарить можно только тот или иной вид указателя на мьютекс.

2. std::fs::File. Сборщик мусора помогает освобождать занятую память, но он не очень помогает с внешними ресурсами, в частности, файлами: закрыть файл обычно нужно сразу после того, как работа с ним окончена, а сборщик мусора никаких гарантий по времени закрытий файла не даёт. В стандартной библиотеке большинства языков программирования (даже с GC) есть отдельная функция, которая закрывает файл. Тем не менее, присутствие этой функции обнажает серьёзный изъян в системе типов: файл невозможно использовать после закрытия (также, как и до открытия, но обычно это не является большой проблемой), но это состояние никак не отслеживается в системе типов. Более того, дважды закрывать файл может быть попросту опасно: например, на Linux файл описывается файловым дескриптором — фактически, просто числом. После закрытия файла это же числовое значение может быть переиспользованно для другого файла, поэтому второе закрытия того же файлового дескриптора может привести к закрытию файла в другой программе!

Как эти проблемы обходятся в Rust? Если вы проверите API File, то... Вы не найдёте там метода close! Когда File выходит из области видимости, для него вызывается деструктор, который и закрывает файл. Т. к. явного метода закрытия файла в публичном API нет, единственный способ форсировать закрытие файла — это дропнуть файл (например, вызовом std::mem::drop). В силу того, что после этого получить доступ к файлу нельзя, возможность двойного закрытия статически запрещается.

Очевидно, аффинные типы не являются серебряной пулей. Каковы же недостатки? Конкретно в случае с File недостаток очевиден: закрытие файла может завершиться ошибкой, но закрытие посредством вызова деструктора не позволяет об этом узнать. Более сильные линейные типы (в которых каждое значение используется ровно один раз) позволили бы решить эту проблему, требуя явно вызывать close и таким образом давать доступ к возможным ошибкам, но это уже тема для другого поста.
Forwarded from Блог*
#prog #rust #article

Очередная статья про обработку ошибок в Rust, да. Но полезная: ставит под сомнения и разбирает распространённые мнения об ошибках в Rust. В частности, разбирается совет "anyhow для приложений, thiserror для библиотек".
Forwarded from Sysadmin Tools 🇺🇦
PGCat PgBouncer rewritten in Rust, with sharding, load balancing and failover support.

https://github.com/levkk/pgcat

PS: thanks @nosingularity for the link

#postgresql #postgres #sql #rust
Forwarded from Блог*
#rust

Поддержку Rust в ядре Linux вмержил Линус 🎉
Forwarded from Блог*
#prog #rust #article

How (and why) nextest uses tokio, part 1

Или хороший пример того, как async может пригодиться в программе, которая вообще никак не связана с общением по сети.
Forwarded from Блог*
#prog #rust #article

How much does Rust's bounds checking actually cost?

What is the actual cost of all this extra bounds checking, though? There’s a little bit of prior art here <...> On the other hand, I wasn’t able to find an extensive analysis of the cost of pervasive bounds checking on a real, large, production Rust codebase with high performance sensitivity. I happen to work on one of those, so I figured it might be interesting to take a look at the cost of bounds checks in the hot path.

TL;DR: выкидывание всех этих проверок — даже с использованием запатченного rustc, который не генерирует инструкции с проверками индексации (что вообще-то unsound) — не делает программу быстрее и даже может сделать несколько медленнее.
Forwarded from Блог*
#prog #rust

Если ждать достаточно долго, то можно дождаться исполнения своего желания. В данном случае — желания разобраться с конкурентностью на низком уровне.

Небезызвестная Mara Bos опубликовала свою книгу: Rust Atomics and Locks: Low-Level Concurrency in Practice. Все главы можно прочитать бесплатно онлайн.
Forwarded from Блог*
#prog #rust #rustlib

schnellru — A fast and flexible LRU map

* Blazingly fast. Up to twice as fast as the lru crate, and with less memory overhead.
* Can be also used as an ordered map, with roughly the same performance as
indexmap, but with added support for O(1) removals without changing the element order (where indexmap only supports O(n) non-perturbing removals).
* Customizable. Out-of-box can be limited by length or by memory usage, but supports custom limiters which can be made to limit the map by whatever you want.
* Tested, miri-clean, clippy-clean and fuzzed.
* Supports
no_std.