Comments 15
Динамические языки программирования, такие как JavaScript или Python, позволяют объявлять функции с необязательными параметрами или переменным числом аргументов.
Динамичность языка не имеет к этому ни малейшего отношения.
Замыкание в Rust похоже на стрелочную функцию в JavaScript или лямбда-функцию в Python. Если вы не знакомы с этим понятием, разберём его подробнее.
Вот зачем, интересно, было всех запутывать и называть лямбда-выражение замыканием? Хотя это две совершенно разные вещи.
А что, действительно анонимная функция и лямбда-выражение - это синонимы?
В Rust используется термины "Anonymous functions" и "Closures". В статье показано и то, и другое. Но при чем здесь лямбда-выражение?
Если строго, то лямбда-выражение – это способ записи тела функции. Если значение лямбда-выражения вычислить и оставить без имени, то получится анонимная функция.
Но замечу, что в тексте статьи нет ни одной анонимной функции в общепринятом понимании. Автор свои лямбда-выражения сразу присваивает некоторым переменным, что в традиционных понятиях (языков, поддерживающих функциональный стиль) полностью эквивалентно обычному объявлению именованной функции. Зачем называть анонимной функцию, к которой обращаются по имени, тоже, конечно, не вполне ясно.
Однако я хотел обратить внимание на тот факт, что замыкание в традиционном понимании работает независимо от того, каким образом записана функция. Это просто захват лексического контекста.
Я так понимаю, что автор придерживается терминологии, принятой в документации к Rust. А статья как раз раскрывает существенные различия между реализацией замыканий в Rust и в других языках, а именно - как осуществляется передача владения экземплярами захватываемых объектов. Например, в C# выброс исключения, описанный в примере "FnOnce trait", в принципе невозможен. Там лямбда-функция преобразуется в делегат. И если функция захватила ссылку на объект, то объект будет сохраняться в памяти, пока жива сама ссылка на делегат. Вариантов нет. И это известные источник возможных утечек памяти в C#.
Я так понимаю, что автор придерживается терминологии, принятой в документации к Rust
В данном случае моя претензия скорее к авторам Rust, чем к автору статьи. Хотя автор статьи уже по собственной инициативе не проводит разницу между замыканием и самой функцией.
И это известные источник возможных утечек памяти в C#.
В чём вы здесь видите утечку памяти? Естественно, что, если переменная используется в функции, то память под эту переменную распределена. Но в чём утечка? Представьте, что вектор v был бы описан внутри лямбды, тогда никакого замыкания не потребовалось бы, а управление памятью было бы точно таким же.
Утечкой памяти это может стать только в C++, благодаря отсутствию сборки мусора.
В чём вы здесь видите утечку памяти?
Это уже отходит от темы статьи и может быть расценено как флуд, так что постараюсь кратко. Как всегда, утечка может быть только при неправильно написанной программе. Например, если в ответ на событие вызываете лямбда-функцию:
class BadLogger{
private static n=42;
BadLogger(Form chieldWindow){
mainWindow.OnLoad+={Console.WriteLine($"Hi! Chield window loaded {n++} times.");
};
}
}
Это ужасный пример, извините..
Если вы хотите где-то сохранять количество загрузок, то память под переменную n ведь так или иначе должна быть выделена? Тут ничего не поделать.
Другое дело, зачем ради целого числа размещать в памяти целый экземпляр класса, но это не проблема лямбды. Ей-то плевать, откуда брать переменную n.
Суть в том, что здесь лямбда преобразуется в делегат и, как делегат, хранит ссылку на экземпляр chieldWindow, о чем легко забыть. Так что объект chieldWindow будет освобожден не ранее, чем будет освобожден сам экземпляр лямбды. Но пример и впрямь неудачный, вдобавок подписка на событие написана некорректно. Правильно так (напишу сам, пока народ носом не ткнул :=(:
mainWindow.OnLoad+=(o,e)=>{Console.WriteLine($"Hi! Chield window loaded {n++} times."); };
Нет, не хранит. Если локальная переменная не используется во вложенных функциях - то и в замыкание она не попадает.
Чтобы получить скрытую утечку, надо делать как-то так:
mainWindow.OnLoad += (o, e) => { Console.WriteLine($"Hi! Chield window loaded {n++} times."); };
foo.Bar += () => { childWindow.Show(); };
Вот так делегат, подписанный на OnLoad, и правда будет держать ссылку на childWindow.
PS Ну нету буквы "e" в слове "child"! Откуда вы её взяли?!
Справочник / занудство / спискота
А где же мотивационная часть?
Зачем мне каррирование или замыкание?
Я то знаю, но автор молчит как рыба об лёд.
При каррирование всего с двумя аргументами функции, аннотация возвращаемого типа громоздкая получается. То ли дело в Haskell.
Функции у вас бесплодные, а FnOnce потребляет переменную
Славный надмозг, чем переводили?
наконец, замыкание может полностью забрать владение переменными из своей окружающей среды. После того как переменные перемещены moved в замыкание, их нельзя повторно использовать за его пределами. Такие замыкания реализуют трейт FnOnce, который требует реализации метода call_once
Чушь, перемещение переменной в замыкание никак не должно влиять на возможность повторного вызова:
struct Foo;
impl Foo {
fn use_me(&self) { }
}
fn create_fn() -> impl Fn() {
let foo = Foo;
move || { // переменная foo перемещается в замыкание
foo.use_me(); // никаких проблем
}
}
Другое дело если замыкание разрушает переменную либо передаёт владение куда-то ещё.
Так вы в примере не используете foo
повторно вне замыкания. Речь именно об этом.
Функции, замыкания и функциональное программирование в Rust: полное руководство