Блог*
Ох, постараюсь уложить это в голове. Есть ещё что-то, что нужно знать о замыканиях? Да. Иногда требуется одно и тоже замыкание передать в качестве аргумента в несколько функций. Клонировать в этом случае не получится, потому что замыкание является клонируемым…
> И будем надеяться, что его не придётся ждать ещё пару месяцев
АХАХАХХАХАХХА
АХАХАХХАХАХХА
Блог*
Ох, постараюсь уложить это в голове. Есть ещё что-то, что нужно знать о замыканиях? Да. Иногда требуется одно и тоже замыкание передать в качестве аргумента в несколько функций. Клонировать в этом случае не получится, потому что замыкание является клонируемым…
#prog #rust #моё
Хроники замыканий
Есть ещё кое-что, что можно сказать про замыкания.
Пожалуй, первое, с чего надо начать — так это с того, что, несмотря на то, что замыкания — это автоматически генерируемые структуры, реализующие определённые трейты, реализовать их самому для своих типов нельзя — по крайней мере, на стабильной версии Rust. Для того, чтобы определить один из Fn*-трейтов для своего типа, нужно активировать две фичи. Первая — fn_traits — позволяет написать определение трейта в обычном виде, а не в засахаренном
Стоп, в Rust есть одноместный кортеж?!
Да. Он объявляется одинаково на уровне термов и на уровне типов:
Надо сказать, что замыкания являются, в сущности, обычными типами, которые отличаются в основном анонимностью. И на них, в частности, нужно в некоторых ситуациях накладывать нужные ограничения. Рассмотрим в качестве примера функцию, которая принимает некоторый счётчик и возвращает замыкание, которое инкрементирует этот счётчик на каждый вызов. Для простоты пусть это будет просто
А разве компилятор не может сам догадаться, как надо?
Нет. Компилятор проверяет правильность расстановки времён жизни, анализируя лишь сигнатуру функции, а не её тело.
Хорошо. Так как нам заставить этот код компилироваться?
Указать явно, что возвращаемое замыкание не может пережить захваченную ссылку. Мы можем сделать это, введя для времени жизни имя и приписав его ссылке и возвращаемому замыканию:
Это связано с тем, как замыкания в Rust работают с захваченными значениями (т. е. со значениями, объявленными снаружи самого замыкания). По умолчанию замыкания захватывают переменные по ссылке (то есть сгенерированный анонимный тип содержит ссылки на эти значения) — мутабельной или иммутабельной, в зависимости от того, как они используются внутри тела замыкания. Использование ключевого слова move переопределяет это поведение по умолчанию и заставляет замыкание захватывать переменные по значению (т. е. перемещает их в замыкание). Если его убрать, то
Так, стоп, так
В правильном или неправильном варианте?
...В правильном
По значению.
Но...
Просто в данном случае само захватываемое значение является ссылкой.
А, я понял. Кажется...
Ничего, это действительно поначалу немного сбивает с толку.
Что ж, это всё, что ты хотел рассказать про замыкания?
Всё? ВСЁ? АХАХХАХАХХАХХА
О_о
Хроники замыканий
Есть ещё кое-что, что можно сказать про замыкания.
Пожалуй, первое, с чего надо начать — так это с того, что, несмотря на то, что замыкания — это автоматически генерируемые структуры, реализующие определённые трейты, реализовать их самому для своих типов нельзя — по крайней мере, на стабильной версии Rust. Для того, чтобы определить один из Fn*-трейтов для своего типа, нужно активировать две фичи. Первая — fn_traits — позволяет написать определение трейта в обычном виде, а не в засахаренном
Fn(Foo) -> Bar
, как обычно. Вторая — unboxed_closures — позволяет определять функции с ABI rust-call
, которое имеют методы Fn*-трейтов. Если мы их используем, то мы можем сделать то, что невозможно на текущей стабильной версии Rust: перегрузку функции по количеству аргументов. Причина, по которой эти трейты не стабилизированы, видимо, состоит в том, что им в качестве ти́пового параметра требуется не произвольный тип, а кортеж типов аргументов. Это выглядит несколько неловко на фоне отсутствия вариадических обобщённых типов, особенно для функции от одного аргумента — его приходится заключать в одноместный кортеж.Стоп, в Rust есть одноместный кортеж?!
Да. Он объявляется одинаково на уровне термов и на уровне типов:
(x,)
Ужас какой. А что ещё можно сказать про замыкания?Надо сказать, что замыкания являются, в сущности, обычными типами, которые отличаются в основном анонимностью. И на них, в частности, нужно в некоторых ситуациях накладывать нужные ограничения. Рассмотрим в качестве примера функцию, которая принимает некоторый счётчик и возвращает замыкание, которое инкрементирует этот счётчик на каждый вызов. Для простоты пусть это будет просто
u32
. Кажется, что можно написать вот так:fn make_incrementer(counter: &mut u32) -> impl FnMut() {, однако этого недостаточно. Дело в том, что возвращаемое замыкание хранит мутабельную ссылку, которая имеет своё время жизни, но это никак не отражено в сигнатуре. Трейты в сигнатурах без явно указанных ограничений времени получают ограничение
move || *counter += 1
}
'static
, что в данном случае явно неверно.А разве компилятор не может сам догадаться, как надо?
Нет. Компилятор проверяет правильность расстановки времён жизни, анализируя лишь сигнатуру функции, а не её тело.
Хорошо. Так как нам заставить этот код компилироваться?
Указать явно, что возвращаемое замыкание не может пережить захваченную ссылку. Мы можем сделать это, введя для времени жизни имя и приписав его ссылке и возвращаемому замыканию:
fn make_incrementer<'a>(counter: &'a mut u32) -> impl FnMut() + 'a { // ......или же мы можем побыть ленивыми и, последовав совету компилятора, навернуть анонимное время жизни:
fn make_incrementer(counter: &mut u32) -> impl FnMut() + '_ { // ...Ясно. А для чего там
move
?Это связано с тем, как замыкания в Rust работают с захваченными значениями (т. е. со значениями, объявленными снаружи самого замыкания). По умолчанию замыкания захватывают переменные по ссылке (то есть сгенерированный анонимный тип содержит ссылки на эти значения) — мутабельной или иммутабельной, в зависимости от того, как они используются внутри тела замыкания. Использование ключевого слова move переопределяет это поведение по умолчанию и заставляет замыкание захватывать переменные по значению (т. е. перемещает их в замыкание). Если его убрать, то
make_counter
не будет компилироваться: аргумент counter
— фактически, локальная переменная — захватывается по ссылке, время жизни которой ограничено скоупом make_counter
, поэтому вернуть такое замыкание за пределы порождающей его функции нельзя.Так, стоп, так
counter
захватывается по ссылке или по значению?В правильном или неправильном варианте?
...В правильном
По значению.
Но...
Просто в данном случае само захватываемое значение является ссылкой.
А, я понял. Кажется...
Ничего, это действительно поначалу немного сбивает с толку.
Что ж, это всё, что ты хотел рассказать про замыкания?
Всё? ВСЁ? АХАХХАХАХХАХХА
О_о
Блог*
Ох, постараюсь уложить это в голове. Есть ещё что-то, что нужно знать о замыканиях? Да. Иногда требуется одно и тоже замыкание передать в качестве аргумента в несколько функций. Клонировать в этом случае не получится, потому что замыкание является клонируемым…
Прошу прощения. В общем, я хотел рассказать про одну продвинутую фичу системы типов Rust...
Так, мне уже это не нравится
...Которая формально не связана с замыканиями, но фактически была введена ради них. Я говорю про higher-ranked trait bounds. В качестве демонстрационного примера я возьму несколько переработанный пример из rustonomicon. Пусть у нас есть структура, в которой лежат какие-то данные и замыкание, которое можно к эти данным применить:
А почему первый пример не требовал этой страхолюдины?
На самом деле требовал, просто это очередной синтаксический сахар: для ограничений Fn*-трейтов Rust подставляет HRTB автоматически, при этом он следует обычным правилам lifetime elision.
А можно в
Увы, нет.
Ясно. А есть пример, когда этот синтаксический сахар ломается?
Ну, он ломается примерно тогда же, когда ломается lifetime elision. Скажем, если взять
Конечно. Да и лайфтаймы можно расставить по другому. Но в других, более сложных, случаях обойтись без HRTB невозможно.
Так, мне уже это не нравится
...Которая формально не связана с замыканиями, но фактически была введена ради них. Я говорю про higher-ranked trait bounds. В качестве демонстрационного примера я возьму несколько переработанный пример из rustonomicon. Пусть у нас есть структура, в которой лежат какие-то данные и замыкание, которое можно к эти данным применить:
struct Point {Попробуем написать для
x: u32,
y: u32,
z: u32,
}
struct Closure<F> {
data: Point,
func: F,
}
Closure
метод, который применяет функцию func
к point
:impl<F> Closure<F>Пока вроде всё нормально, но мы знаем, что в данном случае Rust вывел времена жизни за нас, в том числе и в ограничении на
where
F: Fn(&Point) -> &u32,
{
fn apply(&self) -> &u32 {
(self.func)(&self.data)
// ^---- эти скобки необходимы, потому без них это будет означать
// вызов метода func
}
}
F
. Как это выглядит в развёрнутом виде? Попробуем написать:impl<F> Closure<F>Непонятно, что писать вместо
where
F: Fn(&'??? Point) -> &'??? u32,
{
fn apply<'a>(&'a self) -> &'a u32 {
(self.func)(&self.data)
}
}
???
: тут должно быть какое-то время жизни, но мы не можем написать 'a
, потому что это время жизни объявлено на методе, а не на impl-блоке целиком. Собственно, снаружи это время жизни 'a
может быть, в сущности, любым, его выбирает вызывающий код. Нам нужно как-то выразить тот факт, что F
реализует трейт функции с произвольным временем жизни, а не каким-то фиксированным жизни. Higher-ranked trait bounds aka HRTB (ограничение трейтов высшего ранга) позволяют выразить именно это:impl<F> Closure<F>Тем самым мы выражаем тот факт, что
where
F: for<'a> Fn(&'a Point) -> &'a u32
// далее без изменений
F
реализует целое семейство трейтов, параметризованных временем жизни, и это время жизни может выбрать вызывающий код по месту применения.А почему первый пример не требовал этой страхолюдины?
На самом деле требовал, просто это очередной синтаксический сахар: для ограничений Fn*-трейтов Rust подставляет HRTB автоматически, при этом он следует обычным правилам lifetime elision.
А можно в
for
использовать тип, а не время жизни?Увы, нет.
Ясно. А есть пример, когда этот синтаксический сахар ломается?
Ну, он ломается примерно тогда же, когда ломается lifetime elision. Скажем, если взять
example_hof
из вот этого моего поста и попробовать переписать его на Rust, то в лоб это сделать не удастся:fn example_hof<F>(n: u32, s: &str, func: F) -> &strНа этот код ругается компилятор, причём примерно так же, как на функцию с сигнатурой
where
F: FnOnce(&str, &str) -> &str,
{
let n = n.to_string();
func(&n, s)
}
fn(&str, &str) -> &str
: здесь не хватает информации о том, как результат F
зависит от её аргументов. Один из способов поправить этот код — переписать ограничение так:F: for<'a> FnOnce(&str, &'a str) -> &'a str,Но здесь вроде можно обойтись без это higher-ranked хренотени, добавив `'a` в число обобщённых параметров `example_hof`
Конечно. Да и лайфтаймы можно расставить по другому. Но в других, более сложных, случаях обойтись без HRTB невозможно.
doc.rust-lang.org
Higher-Rank Trait Bounds - The Rustonomicon
The Dark Arts of Advanced and Unsafe Rust Programming
Блог*
Ох, постараюсь уложить это в голове. Есть ещё что-то, что нужно знать о замыканиях? Да. Иногда требуется одно и тоже замыкание передать в качестве аргумента в несколько функций. Клонировать в этом случае не получится, потому что замыкание является клонируемым…
Так что же, эти HRTB ограничены одними лишь замыканиями?
Отнюдь. Например, в библиотеке serde есть трейт
Да. Во всяком случае, пока что.
Спасибо, после общения с тобой я чувствую себя... Мудрее.
Что, правда?
Нет, конечно, лол
Ну и зараза
Отнюдь. Например, в библиотеке serde есть трейт
Deserialize<'de>
и DeserializeOwned
. Первый трейт параметризован временем жизни входных данных 'de
, его реализуют типы, которые заимствуют из входа (скажем, строки) какие-то данные. Второй же, как следует из его названия, реализуется типами, которые не заимствуют данные из входа и являются владеющими. Очевидно, если тип реализует DeserializeOwned
, то он реализует и Deserialize<'de>
, причём для произвольного 'de
. И действительно, DeserializeOwned
объявлен следующим образом:pub trait DeserializeOwned: for<'de> Deserialize<'de> { }Также эту можно применять и для обычных функций, не являющихся замыканиями. Например, если мы почему-то хотим в
example_hof
выше принимать только функции-не-замыкания, то мы могли бы написать её тип так:fn example_hof<F>(n: u32, s: &str, func: for<'a> fn(&str, &'a str) -> &'a str) -> &str;Фух, это было довольно много. Это всё?
Да. Во всяком случае, пока что.
Спасибо, после общения с тобой я чувствую себя... Мудрее.
Что, правда?
Нет, конечно, лол
Ну и зараза
Блог*
Мне прилетел случай, когда моё решение давало ответ, на единицу отличающийся от правильного. Это выглядело странно, потому что это не могло быть эффектом ошибки на единицу — в противном случае более простые примеры давали бы ту же ошибку. Я внимательно изучил…
#prog #моё
НЕ ЧИТАЙТЕ, ЕСЛИ ВЫ ЕЩЁ НЕ РЕШАЛИ СЕГОДНЯШНЮЮ ЗАДАЧУ AUGUST LEETCODING CHALLENGE, НО ХОТИТЕ РЕШИТЬ ЕЁ САМИ
А вот в сегодняшней задаче union-find таки пригодилась. В задаче дан массив чисел, и требуется для графа, в котором вершинами являются числа из этого массива, а рёбра соединяют числа, не являющиеся взаимно простыми, найти размер наибольшей (по числу вершин) связной компоненты. Я, конечно, сначала сделал поиск в глубину, но тест на наличие ребра оказался довольно дорогим даже при предварительном разложении чисел на множители, что приводило к тому, что мои решения отваливались по таймауту на больших тесткейсах (100 000 чисел в диапазоне от 1 до 20 000). Объединение чисел по общему простому множителю в разложении дало куда более хорошую асимптотику и, как следствие, производительность. Единственное, на что нужно было обратить внимание при решении — это изменить процедуру union так, чтобы она возвращала размер вновь образованного множества (естественно, в этом случае нужно объединение по размеру, а не по рангу) и своевременно обновлять текущее максимальное значение.
НЕ ЧИТАЙТЕ, ЕСЛИ ВЫ ЕЩЁ НЕ РЕШАЛИ СЕГОДНЯШНЮЮ ЗАДАЧУ AUGUST LEETCODING CHALLENGE, НО ХОТИТЕ РЕШИТЬ ЕЁ САМИ
А вот в сегодняшней задаче union-find таки пригодилась. В задаче дан массив чисел, и требуется для графа, в котором вершинами являются числа из этого массива, а рёбра соединяют числа, не являющиеся взаимно простыми, найти размер наибольшей (по числу вершин) связной компоненты. Я, конечно, сначала сделал поиск в глубину, но тест на наличие ребра оказался довольно дорогим даже при предварительном разложении чисел на множители, что приводило к тому, что мои решения отваливались по таймауту на больших тесткейсах (100 000 чисел в диапазоне от 1 до 20 000). Объединение чисел по общему простому множителю в разложении дало куда более хорошую асимптотику и, как следствие, производительность. Единственное, на что нужно было обратить внимание при решении — это изменить процедуру union так, чтобы она возвращала размер вновь образованного множества (естественно, в этом случае нужно объединение по размеру, а не по рангу) и своевременно обновлять текущее максимальное значение.
Хочется найти новую работу, но на hh и linkedin предлагают работать администратором БД в Пятёрочке или двигать формочки на электроне за 2 косаря в месяц? Можешь зайти на @profunctor_jobs, там публикуют нормальные вакансии, которых больше нет нигде.
Пост проплачен обитателями Нибиру
Пост проплачен обитателями Нибиру
Forwarded from rusta::mann
Опытный разработчик ударился в религию и создал не только образ докера, но и подобие
Блог*
Хочется найти новую работу, но на hh и linkedin предлагают работать администратором БД в Пятёрочке или двигать формочки на электроне за 2 косаря в месяц? Можешь зайти на @profunctor_jobs, там публикуют нормальные вакансии, которых больше нет нигде. Пост проплачен…
Вау, у меня много новых подписчиков! Интересно, откуда?
Раз уж тут так много новых людей — и особенно много тех, с кем я совершенно не знаком — пожалуй, стоит рассказать немного о себе и об этом канале.
Меня зовут Антон, ястудент человек студенческого возраста и в настоящий момент я работаю программистом, по работе пишу в основном... Да, на Rust, а как вы догадались? Я люблю Rust и ненавижу примерно все остальные языки программирования. Круг моих интересов относительно широк, но на канал изливается в основном программирование (серьёзно, около половины постов с хештегом #prog). Этот канал изначально планировался как удобная свалка ссылок, материалов и #meme-ов, и... Он таковым, в сущности, и остался. В своё оправдание я могу сказать, что все статьи (выкладываемые с хештегом #article) я всегда читаю перед тем, как выложить, так что делюсь я тем, что считаю стоящим своей аудитории.
Также ведение своего канала сподвигнуло меня на написание своих постов (и иногда даже перевод чужих статей), которые я выкладываю под хештегом #моё (как, впрочем, и созданные мною мемы). В их числе я хотел бы отметить:
- написание трейта, гарантирующего нулевой размер типа
- оптимизация размера итератора из стандартной библиотеки Rust (увы, не принятая)
- реализация zero-cost форматировщиков даты
- разбор различных способ реализации полиморфизма, с их достоинствами и недостатками
- эпические "Хроники замыканий" в 3 (пока что) частях: раз, два, три
- деликатный и глубокий анализ недостатков регулярных выражений
Для удобства привожу список всех хештегов на канале (может быть пополнен в будущем):
#3dcg
#abnormalprogramming
#algo
#amazingopensource
#anime
#art
#article
#bash
#bio
#blogrecommendation
#c
#cinema
#clojure
#cpp
#csharp
#db
#demoscene
#design
#dotnet
#erlang
#game
#go
#idris
#itsec
#haskell
#js
#java
#julia
#justrustaceanthings
#kbd
#life
#math
#mechanics
#meme
#menacingopensource
#ml
#mood
#music
#outoflinestorage
#parsing
#performancetrap
#php
#pixelart
#politota
#postgresql
#prog
#psy
#puzzle
#python
#quotes
#regex
#retroit
#r
#rust
#rustasync
#rustforlinux
#rustreleasenotes
#rustlib
#scala
#science
#serde
#shell
#soc
#softskills
#sql
#successstory
#suckassstory
#tips
#typescript
#video
#web
#zig
#бомбёжкипост
#культурнаяпрограмма
#лингво
#моё
#право
#трудовыебудни
Ну и традиционно — лайки, колокольчик, рассказывайте друзьям, бла бла бла, ну, вы знаете, как это обычно выглядит. Ну и раз уж я пока не могу поприветствовать каждого лично, я поприветствую всех новоприбывших скопом:
Добро пожаловать на канал!
Меня зовут Антон, я
Также ведение своего канала сподвигнуло меня на написание своих постов (и иногда даже перевод чужих статей), которые я выкладываю под хештегом #моё (как, впрочем, и созданные мною мемы). В их числе я хотел бы отметить:
- написание трейта, гарантирующего нулевой размер типа
- оптимизация размера итератора из стандартной библиотеки Rust (увы, не принятая)
- реализация zero-cost форматировщиков даты
- разбор различных способ реализации полиморфизма, с их достоинствами и недостатками
- эпические "Хроники замыканий" в 3 (пока что) частях: раз, два, три
- деликатный и глубокий анализ недостатков регулярных выражений
Для удобства привожу список всех хештегов на канале (может быть пополнен в будущем):
#3dcg
#abnormalprogramming
#algo
#amazingopensource
#anime
#art
#article
#bash
#bio
#blogrecommendation
#c
#cinema
#clojure
#cpp
#csharp
#db
#demoscene
#design
#dotnet
#erlang
#game
#go
#idris
#itsec
#haskell
#js
#java
#julia
#justrustaceanthings
#kbd
#life
#math
#mechanics
#meme
#menacingopensource
#ml
#mood
#music
#outoflinestorage
#parsing
#performancetrap
#php
#pixelart
#politota
#postgresql
#prog
#psy
#puzzle
#python
#quotes
#regex
#retroit
#r
#rust
#rustasync
#rustforlinux
#rustreleasenotes
#rustlib
#scala
#science
#serde
#shell
#soc
#softskills
#sql
#successstory
#suckassstory
#tips
#typescript
#video
#web
#zig
#бомбёжкипост
#культурнаяпрограмма
#лингво
#моё
#право
#трудовыебудни
Ну и традиционно — лайки, колокольчик, рассказывайте друзьям, бла бла бла, ну, вы знаете, как это обычно выглядит. Ну и раз уж я пока не могу поприветствовать каждого лично, я поприветствую всех новоприбывших скопом:
Добро пожаловать на канал!
Telegram
Блог*
#prog #rust #article #моё
Как я и обещал, я... Так, погодите, я не писал об этом в блоге? Впрочем, ладно, напишу сейчас: я планировал написать перевод этой статьи и выложить его на Хабре. Собственно, сегодня я это и сделал.
Как я и обещал, я... Так, погодите, я не писал об этом в блоге? Впрочем, ладно, напишу сейчас: я планировал написать перевод этой статьи и выложить его на Хабре. Собственно, сегодня я это и сделал.
👍8🤮1💩1🤡1
Блог*
Ради чего вы подписаны на канал?
Ну а как осмотритесь — пожалуйста, отметитесь в этом опросе (если что, там можно выбрать несколько вариантов)