
Size: a a a
Fn(i32) -> i32
, то можно сделать замыкание и передавать в качестве аргумента ссылку на него. В некоторых случаях сама функция требует ссылку на замыкание (хотя это, вообще говоря, странно). В таком случае можно взять ссылку непосредственно от литерала замыкания. Выглядит это несколько странно, но работает.Fn(i32) -> i32
, то можно сделать замыкание и передавать в качестве аргумента ссылку на него. В некоторых случаях сама функция требует ссылку на замыкание (хотя это, вообще говоря, странно). В таком случае можно взять ссылку непосредственно от литерала замыкания. Выглядит это несколько странно, но работает.Fn(Foo) -> Bar
, как обычно. Вторая — unboxed_closures — позволяет определять функции с ABI rust-call
, которое имеют методы Fn*-трейтов. Если мы их используем, то мы можем сделать то, что невозможно на текущей стабильной версии 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
?make_counter
не будет компилироваться: аргумент counter
— фактически, локальная переменная — захватывается по ссылке, время жизни которой ограничено скоупом make_counter
, поэтому вернуть такое замыкание за пределы порождающей его функции нельзя.counter
захватывается по ссылке или по значению?Fn(i32) -> i32
, то можно сделать замыкание и передавать в качестве аргумента ссылку на него. В некоторых случаях сама функция требует ссылку на замыкание (хотя это, вообще говоря, странно). В таком случае можно взять ссылку непосредственно от литерала замыкания. Выглядит это несколько странно, но работает.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
реализует целое семейство трейтов, параметризованных временем жизни, и это время жизни может выбрать вызывающий код по месту применения.for
использовать тип, а не время жизни?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`
Fn(i32) -> i32
, то можно сделать замыкание и передавать в качестве аргумента ссылку на него. В некоторых случаях сама функция требует ссылку на замыкание (хотя это, вообще говоря, странно). В таком случае можно взять ссылку непосредственно от литерала замыкания. Выглядит это несколько странно, но работает.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;Фух, это было довольно много. Это всё?
#..#Видите ли вы тут проблему? Я вот понял её благодаря своему отладочному выводу. Перерисуем этот пример, показав, какой номер территории приписывается каждой ячейке на очередном шаге:
#.##
###.
0..1Во время обработки второй строки появляется территория с id = 2, которая сразу же становится частью территории с id = 1. После обработки второй строки связи между территориями выглядят следующим образом (стрелочкой показано отношение "является частью"):
0.21
000.
[ 0 | 1 | 2 ]С другой стороны, во время обработки третьей строки эта же территория становится частью территории с id = 0, и в итоге связи становятся такими:
^ |
| |
\---/
[ 0 | 1 | 2 ]Здесь и кроется проблема: так как у территории с id = 1 нет никаких связей, она считается отдельным островом, а не частью острова, образованного территорией с id = 0. В итоге алгоритм ошибочно считает, что на карте два острова вместо одного. В принципе, тут примерно понятно, что нужно исправить: нужно менять не только связь у территории 2, но и у территории 1, на которую первая указывает...
^ |
| |
\-------/