Pull to refresh

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 повторно вне замыкания. Речь именно об этом.

Разумеется, я не использую foo повторно вне замыкания, она же была перемещена. Однако, перемещение foo не мешает замыканию реализовать Fn,в то время как процитированный фрагмент утверждает, что такое замыкание будет якобы FnOnce.

Sign up to leave a comment.

Articles