- Предыдущая часть: Заменяем глобальный аллокатор
 - Начало и содержание: Владение
 
Трудности замыкания
Замыкания в Rust — это функции, которые используют переменные в своей области видимости, пример:
    let mut call_count = 0;
    let mut sum = | x, y | {
        call_count += 1;
        x + y
    };
    dbg!(sum(2, 2));
    dbg!(sum(3, 3));
    dbg!(sum(4, 4));
    dbg!(call_count);
Тема непростая, и не только для Rust. Казалось бы, что может быть проще JavaScript? Но ведь и там: What is a closure in JavaScript and why most people have the wrong idea?.
В другой статье рассматривается вот такой код:
let fns =[]
for(var i = 0; i < 5; i++) {
    var c = i * 2;
    fns.push( _ => console.log(c))
}
fns.forEach( f => f() )
Как бы, что будет? Код, с точки зрения автора, проблематичен и проблема названа "The For Loop little problem".
Схожий по неочевидности сценарий для Go (подробное обсуждение здесь):
fns := make([]func(),0)
for i := 0; i < 5; i++ {
    f := func(){fmt.Println(i)}
    fns = append(fns, f)
}
for _, f := range(fns){
    f()
}
Для Rust все это многократно усложняется заимствованиями, временами жизни, контролем за изменяемостью (mut), дополнительным способом определения замыканий через move и необходимостью в некоторых случаях использовать Box для возврата замыканий. Количество вариантов огромно, ранее полученные по Rust знания помогают плохо (что особенно досадно и внезапно). В общем, краткая и емкая статья по теме замыканий долго не получалась, норовя обернуться многостраничным (а то и многостатейным) монстром. 
В конечном итоге вроде бы нашел подход, он заключается в следующем. Компиляторы для замыканий делают много неявной работы, если эту работу смоделировать и показать в условно эквивалентном коде, все станет намного яснее. Наверное. Попробуем.
Базовые случаи использования внешних переменных в замыканиях Rust
- Использование значения non-Copy переменной
 - Использование значения Copy-переменной
 - Использование ссылки на Copy или non-Copy
 - move + использование значения Copy-переменной
 - move + использование ссылки на Copy или non-Copy
 
В реальности, конечно, могут встретиться разнообразные комбинации указанных сценариев (добро пожаловать в ад), но это уже оставляю читателю на самостоятельный разбор.
Использование значения non-Copy переменной
    {
        let s = String::from("hello");
        let closure = || {
            dbg!(s);
        };
        // dbg!(s); // error[E0382]: use of moved value: `s`
        closure();
        // closure(); // error[E0382]: use of moved value: `closure`
    }
Такое замыкание безвозвратно "кушает" переменную s, ее больше нельзя использовать, а само замыкание нельзя вызвать более одного раза.
Замыкание такого типа моделируется структурой Closure {s: String,} которая реализует FnOnce:
trait FnOnce {
    fn call_once(self);
}
Наш FnOnce является упрощенной моделью std::ops::FnOnce, через которую замыкание используется в реальности:
pub trait FnOnce<Args> {
    ...
    extern "rust-call" fn call_once(self, args: Args) -> Self::Output;
    ...
Тут надо заметить, что такое определение не есть "настоящий сварщик", это как бы intrinsic, который особым образом обрабатывается компилятором.
Данные условно-эквивалентного замыкания инициализируются следующим образом:
    let closure = Closure{s: s};
Ясно, что дальнейшее использование s исключено, так как Copy для строк не реализован и происходит передача владения значением в поле Closure.s.
Вызов происходит так:
    closure.call_once(1);    
    // ClosureFnOnce::call_once(closure, 1);
Во второй строке показан эквивалент вызова и по нему видно, что владение переменной closure переходит в метод call_once(), и closure далее использовать нельзя.
Такое замыкание без проблем можно вернуть из функции или передать в поток, тип возвращаемого значения описывается как impl std::ops::FnOnce() -> ():
fn new_closure() -> impl std::ops::FnOnce() -> () {
    let s = String::from("new_closure");
    || {
        dbg!(s);
    }
}
fn new_equivalent_closure() -> Closure {
    let s = String::from("new_equivalent_closure");
    Closure{s: s}
}
Итого:
- Захваченные non-Copy переменные далее использовать вне тела замыкания нельзя
 - Замыкание FnOnce можно использовать только один раз
 - При таком способе захвата замыкание можно возвращать из функций и передавать в потоки
 
Использование значения Copy-переменной
Теперь рассмотрим такой пример:
    // Capturing a variable whose type implements Copy
    {
        let mut p = Point{x: 10, y: 20};
        {
            let closure = || {
                dbg!(p);
            };
            // p.x = 11; // error[E0506]: cannot assign to `p.x` because it is borrowed
            dbg!(p);
            closure(); 
            closure();
        }
        p.x = 11;
        dbg!(p);
    }
В качестве типа захватываемой переменной будем использовать:
#[derive(Copy, Clone, Debug)]
struct Point {
    x: i32,
    y: i32,
}
Для такого случая компилятор "приготовит" Fn:
pub trait Fn<Args>: FnMut<Args> {
    ...
    extern "rust-call" fn call(&self, args: Args) -> Self::Output;
}
Обратим внимание, что теперь self передается по ссылке, а определение "эквивалента" содержит ссылку на переменную и пестрит временами жизни:
struct Closure<'a> {
    p: &'a Point,
}
impl<'a> Fn for Closure<'a> {
    fn call(&self) {
        dbg!(*self.p);
    }
}
Иными словами, использование в замыкании переменной такого типа по значению компилируется в использование по ссылке.
Это кардинально меняет свойства замыкания. Теперь его можно вызвать несколько раз но нельзя возвратить из функции, так как в Rust нельзя возвращать ссылки на локальные переменные:
fn new_equivalent_closure<'a>() -> Closure<'a> {
    let p = Point{x: 10, y: 20};
    Closure{p: &p}
}
error[E0515]: cannot return value referencing local variable `p`
Кроме того, такой захват переменных блокирует их на чтение, так что менять их в области видимости замыкания нельзя.
Замыкание может менять данные, при этом замыкание будет иметь форму FnMut:
pub trait FnMut<Args>: FnOnce<Args> {
    ...
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}
Из "эквивалента" видно, что надо везде тщательно проставить mut, плюс замыкание блокирует переменную на запись, так что читать ее в области видимости замыкания нельзя.
Итого:
- Захваченные на чтение Copy-переменные нельзя изменять в области видимости замыкания
 - Захваченные на запись Copy-переменные нельзя читать в области видимости замыкания
 - Замыкания Fn/FnMut можно использовать несколько раз
 - При таком способе захвата Fn/FnMut нельзя передавать в потоки и возвращать из функций
 
Использование ссылки на Copy или non-Copy
Получается то же, что и для случая использования Copy-переменной по значению, между Copy и non-Copy разницы нет.
move + использование значения Copy-переменной
Если требуется возвратить замыкание, которое захватывает локальную Copy-переменную, или передать его в поток, то на помощь придет move. При этом переменная будет скопирована в данные замыкания и использована по значению:
    let mut p = Point{x: 10, y: 20};
    {
        let mut closure = move || {
            p.y += 1;
            dbg!(p);
        };
    }
"Эквивалент" теперь выглядит так:
struct Closure {
    p: Point,
}
impl FnMut for Closure {
    fn call(&mut self) {
        self.p.x += 1;
        dbg!(self.p);
    }
}
Никаких ссылок, прекрасно! Теперь замыкание можно возвращать из функций и передавать в потоки:
fn new_closure() -> impl std::ops::FnMut() -> () {
    let mut p = Point{x: 300, y: 400};
    move || {
        p.y += 1;
        dbg!(p);
    }
}
fn new_equivalent_closure() -> Closure {
    let p = Point{x: 500, y: 660};
    Closure{p: p}
}
Изменения данных внутри замыкания никак не сказываются на "захваченной" переменной:
    // move + capturing a variable whose type implements Copy
    {
        let mut p = Point{x: 10, y: 20};
        let mut closure = move || {
            p.x += 1;
            dbg!(p);
        };
        dbg!(p); // 10, 20
        closure();  // 11, 20
        closure();  // 12, 20
        dbg!(p); // 10, 20
    }
Интересный нюанс — компилятор требует изменяемости p, хотя она не меняется. Здесь налицо костыль в языке — изменяемость копии в теле замыкания приходится декларировать через изменяемость внешней переменной. Фу так делать. В "эквиваленте" все работает без mut p:
    // Equivalent
    {
        let p = Point{x: 100, y: 200};
        let mut closure = Closure{p: p};
        dbg!(p);
        closure.call(); 
        closure.call();
        dbg!(p);
    }
Итого:
- 
moveразрывает связь между замыканием и "захваченной" переменной, переменная используется по значению - При таком способе захвата получается замыкание типа Fn/FnMut, его можно возвратить из функций или передать в поток
 - Внутри 
move-замыканий нельзя использовать non-Copy переменные по значению 
Формально, кстати (PS: только согласно приводимому ниже определению, есть и другие), такая конструкция не является замыканием, так как в ней отсутствуют ссылки на переменные, объявленные вне тела этой функции:
Замыкание (англ. closure) в программировании — функция первого класса, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции в окружающем коде и не являющиеся её параметрами. Говоря другим языком, замыкание — функция, которая ссылается на свободные переменные в своей области видимости.
Замыкание (программирование)
move + использование ссылки на Copy или non-Copy
При использовании ссылки на внешнюю переменную эта переменная будет скопирована в данные замыкания и ссылка будет уже на локальную копию.
При помощи этой штуки легко "наступить на грабли":
fn move_by_x(p: &mut Point, delta: i32) {
    p.x += delta;
}
fn main() {
    let mut p = Point{x: 10, y: 20};
    let mut closure = move || {
        move_by_x(&mut p, 1);
        dbg!(&p);
    };
    ...
Т.е. передаем в move_by_x() ссылку, компилятор требует изменяемости p (mut p), можно наивно ожидать, что move_by_x() изменит оригинальное значение, но нет. 
Понятно, что вариантом использования по ссылке является вызов метода, у которого получателем (receiver) является &self. Пример:
impl Point {
    fn move_by_x(&mut self, delta: i32) {
        self.x += delta;
    }
}
fn main() {
    let mut p = Point{x: 10, y: 20};
    let mut closure = move || {
        p.move_by_x(1);
        dbg!(&p);
    };
    ...
Свойства замыкания такие же, как и для "move + значение", т.е. получается Fn/FnMut, его можно вызывать много раз, возвращать из функций и передавать в потоки.
Но один интересный нюанс таки есть. Обращаясь к non-Copy переменной по ссылке при помощи move можно сделать возвращаемый (или "передаваемый в потоки") Fn/FnMut:
fn new_closure() -> impl FnMut(){
    let mut s = String::from("Back");
    move || {
        s.push_str(" in the");
        s.push_str(" U.S.S.R");
        dbg!(&s);
    }    
}
fn main() {   
    let mut closure = new_closure();
    closure();
    closure();
    closure();
}
Напротив, вариант без move позволяет вернуть для такой переменной только одноразовый FnOnce:
fn new_closure() -> impl FnOnce(){
    let mut s = String::from("Back");
    || {
        s.push_str(" in the");
        s.push_str(" U.S.S.R");
        dbg!(s);
    }    
}
fn main() {
    let closure = new_closure();
    closure();
    // closure(); // error[E0382]: use of moved value: `closure`
}
Здесь "возращаемость" обеспечивается использованием по значению (dbg!(s)), одновременно это превращает замыкание в FnOnce.
С использованием внешних переменных внутри замыканий разобрались, теперь посмотрим, как замыкания передавать в функции.
Передача замыканий в функции при помощи параметров типа
В Rust есть три встроенных типа, которые соответствуют замыканиям, иерархия такова: Fn -> FnMut -> FnOnce. Т.е. если функция требует FnOnce, вместо него можно подать Fn или FnMut и так далее.
Допустим, у нас есть замыкание:
    let mut call_count = 0;
    let sum = |x, y| {
        call_count += 1;
        x + y
    };
Есть ажно три способа принять его при помощи параметров типа.
Классика:
fn call_sum_way1<F: FnMut(i32, i32) -> i32>(mut sum: F) {
Фастфуд:
fn call_sum_way2(mut sum: impl FnMut(i32, i32) -> i32) {
Стильно, модно, молодежно, рекомендовано:
fn call_sum_way3<F>(mut sum: F)
where
    EXISTS (SELECT FROM Items WITH (NOLOCK) WHERE Name = F.Name AND Type = "FnMut" AND ParamsCount = 2  AND ResultsCount = 1) 
    AND EXISTS (SELECT FROM Params WHERE FuncName = F.Name AND Idx = 0 and Type = "i32") 
    AND EXISTS (SELECT FROM Params WHERE FuncName = F.Name AND Idx = 1 and Type = "i32")     
    AND EXISTS (SELECT FROM Results WHERE FuncName = F.Name AND Idx = 0 and Type = "i32")     
Шутка, вот так на самом деле:
fn call_sum_way3<F>(mut sum: F)
where
    F: FnMut(i32, i32) -> i32,
Все три способа в песочнице.
Когда замыкания передаются при помощи параметров типа имеет место т.н. "параметрический полиморфизм", т.е. на каждый вариант используемых при вызове параметров типа генерируется мономорфный вариант принимающей функции. Пример:
fn main() {
    let sum = |x, y| {
        x + y
    };
    call_sum(sum);
    let sum = |x, y| {
        x + y + 20
    };
    call_sum(sum);
}
fn call_sum<F: FnMut(i32, i32) -> i32>(mut sum: F) {
    sum(2, 2);
    sum(3, 3);
    sum(4, 4);
}   
Каждое замыкание имеет свой собственный неявный тип, так что компилятор сгенерирует два варианта функции call_sum() под каждый из них, несмотря на то, что сигнатуры замыканий идентичны. Чтобы в этом убедиться опять посмотрим в ассемблер, выключив в настройках Symbol Demangling:
_ZN10playground8call_sum17h27c8971da28680efE:
    sub rsp, 56
    mov dword ptr [rsp + 16], 2
    mov dword ptr [rsp + 20], 2
    mov esi, dword ptr [rsp + 16]
    mov edx, dword ptr [rsp + 20]
    lea rdi, [rsp + 8]
    call    _ZN10playground4main28_$u7b$$u7b$closure$u7d$$u7d$17h1ec7382e3c1e9c39E
    jmp .LBB17_1
    ...
_ZN10playground8call_sum17h6c6a460f1c9cf47aE:
    sub rsp, 56
    mov dword ptr [rsp + 16], 2
    mov dword ptr [rsp + 20], 2
    mov esi, dword ptr [rsp + 16]
    mov edx, dword ptr [rsp + 20]
    lea rdi, [rsp + 8]
    call    _ZN10playground4main28_$u7b$$u7b$closure$u7d$$u7d$17h9220f028b950d2f4E
    jmp .LBB18_1
    ...    
Передача замыканий при помощи умных указателей
Принимающая функция может быть скомпилирована в одном варианте ("динамический полиморфизм" или "полиморфизм подтипов"), для этого нужно передавать замыкание через "кучу":
fn main() {
    let sum = |x, y| {
        x + y
    };
    call_sum(Box::new(sum));
    let sum = |x, y| {
        x + y + 20
    };
    call_sum(Box::new(sum));
}
fn call_sum(mut sum: Box<dyn FnMut(i32, i32) -> i32>) {
    sum(2, 2);
    sum(3, 3);
    sum(4, 4);
}
- Данные перемещаются в кучу при помощи 
Box::new(sum) - Таким образом в кучу можно помещать много чего, не только замыкания
 - В описании параметров нужно использовать волшебное слово 
dyn(динамический же полиморфизм) - 
call_sum()при этом компилируется в одном экземпляре 
Кому-то покажется более изящным такой способ определения параметров:
type MyClosure = dyn FnMut(i32, i32) -> i32;
fn call_sum(mut sum: Box<MyClosure>) {
    sum(2, 2);
    sum(3, 3);
    sum(4, 4);
}
Возврат замыканий из функций
Из функций и методов замыкание можно возвращать таким образом:
fn main() {
    dbg!(get_sum(1)(10, 20));
    dbg!(get_sum(2)(10, 20));
}
fn get_sum(mult: i32) -> impl FnMut(i32, i32) -> i32 {
    return move |x, y| {
        mult * (x + y)
    };
}
Без move тут не получится, так как иначе мы вернем замыкание, которое ссылается на локальную переменную — параметр mult. Так дело не пойдет, опасно, нужно "замести" все переменные в данные замыкания, что и делает move.
Надо заметить, что вернуть замыкание можно только из одного места, например, вот такой пример не компилируется:
fn get_sum2(mult: i32, minus: bool) -> impl FnMut(i32, i32) -> i32 {
    if minus {
        return move |x, y| {
            mult * (x + y)
        };
    } else {
        return move |x, y| {
            mult * (x - y)
        };        
    }
}
Сообщения компилятора шикарны:
  = note: to return `impl Trait`, all returned values must be of the same type
  = note: no two closures, even if identical, have the same type
  = help: consider boxing your closure and/or using it as a trait object
Возврат через impl не работает для интерфейсов:
trait Summer {
    fn get_sum(mult: i32) -> impl FnMut(i32, i32) -> i32;
}
error[E0562]: `impl Trait` not allowed outside of function and method return types
 --> src/lib.rs:2:30
  |
2 |     fn get_sum(mult: i32) -> impl FnMut(i32, i32) -> i32;
  |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^
Интересно, почему? Рассмотрим вот такой пример:
fn call_closure() {
    get_closure()();
}
fn call_closure2() {
    get_closure2()();
}
fn get_closure() -> impl Fn() {
    let array: [i32; 50] = [0; 50];
    return move || {
        dbg!(array);
    };
}
fn get_closure2() -> impl Fn() {
    let array: [i32; 100] = [0; 100];
    return move || {
        dbg!(array);
    };
}
В этом примере мы возвращаем два замыкания с данными разного размера. Включим для ассемблера Symbol Demangling и посмотрим, что получается:
playground::call_closure:
    sub rsp, 200
    mov rdi, rsp
    call    playground::get_closure
    mov rdi, rsp
    call    playground::get_closure::{{closure}}
    add rsp, 200
    ret
playground::call_closure2:
    sub rsp, 408
    lea rdi, [rsp + 8]
    call    playground::get_closure2
    lea rdi, [rsp + 8]
    call    playground::get_closure2::{{closure}}
    add rsp, 408
    ret
Вон оно что, данные замыкания возвращается через стек и вызывающая сторона должна подготовить место для этого (sub rsp, ...). Размер данных замыкания известен компилятору только если он имеет возможность "увидеть" что происходит внутри вызываемой функции. В случае, когда замыкание возвращается из интерфейса, "внутрь" не посмотреть, размер данных неизвестен, может возвращаться что угодно, так что в кучу, товарищи:
trait Summer {
    fn get_sum(mult: i32) -> Box<dyn FnMut(i32, i32) -> i32>;
}
Передача замыканий в потоки
use std::thread;
fn main() {
    let s = String::from("Hello");
    let handle = thread::spawn(|| {
        dbg!(s);
    });
    handle.join().unwrap();
}
В данном случае все просто — мы готовим FnOnce, thread::spawn() принимает именно такой тип, все работает (unwrap() требует отдельного рассмотрения, не в этой статье).
Определенные "тонкости" в процессе передачи замыканий в потоки, конечно, есть. Чтобы в них разобраться, рассмотрим сигнатуру thread::spawn:
pub fn spawn<F, T>(f: F) -> JoinHandle<T> 
where
    F: FnOnce() -> T,
    F: Send + 'static,
    T: Send + 'static, 
F: Send + 'static означает, что данные замыкания должны уметь безопасно передаваться между потоками (Send), и все ссылки в данных замыкания (если они есть) должны иметь время жизни static, т.е. ссылаться на локальные переменные внутри замыкания нельзя. Рассмотрим Send и 'static более подробно.
Передача замыканий в потоки::Send
Send это такой маркерный интерфейс типаж, который указывает, что значение может передаваться между потоками. Согласно Книге:
Почти каждый тип Rust является типом Send, но есть некоторые исключения, вродеRc: он не может быть Send, потому что если вы клонировали значениеRcи попытались передать владение клоном в другой поток, оба потока могут обновить счётчик ссылок одновременно.
Разрешение передачи во владение между потоками с помощью Send
Проверим. Возьмем Box, его цель — просто хранить значение в куче. Работает:
    let p = Box::new(Point { x: 10, y: 20 });
    let handle = thread::spawn(|| {
        dbg!(p);
    });
    handle.join().unwrap();
Мы сохранили значение в куче и передали его через данные замыкания в поток. Теперь очередь Rc. Его задача — хранить значение в куче и вести счетчик ссылок на него, когда все держатели ссылок выйдут из области видимости, память освобождается:
    let p1 = Rc::new(Point { x: 10, y: 20 });
    let p2 = p1.clone();
    let p3 = p1.clone();
    dbg!(p1);
    dbg!(p2);
    dbg!(p3);
- p1, p2 и p3 ссылаются на единственное значение в куче
 - p1, p2 и p3 можно передать в разные функции и там использовать
 - Менять значение за p1, p2 и p3 нельзя.
 
Rc быстр, но потокоопасен, поэтому замыкание, которое его использует, лишается почетного значка Send:
    let p = Rc::new(Point { x: 10, y: 20 });
    let handle = thread::spawn(|| {
        dbg!(p);
    });
    handle.join().unwrap();
error[E0277]: `Rc<Point>` cannot be sent between threads safely
   --> src/main.rs:13:18
    |
13  |       let handle = thread::spawn(|| {
    |  __________________^^^^^^^^^^^^^_-
    | |                  |
    | |                  `Rc<Point>` cannot be sent between threads safely
Передача замыканий в потоки::'static
Значения, на которые ссылаются замыкания, передаваемые в потоки, должны жить долго, гарантированно не меньше, чем поток — т.е. то самое 'static. Вот такое не пройдет:
use std::thread;
fn main() {
    let x = 10;
    let handle = thread::spawn(|| {
        dbg!(x);
    });
    handle.join().unwrap();
}
6 |     let handle = thread::spawn(|| {
  |                                ^^ may outlive borrowed value `x`
7 |         dbg!(x);
  |              - `x` is borrowed here
  |
note: function requires argument type to outlive `'static`
Тут-то и пригодится move ||:
use std::thread;
fn main() {
    let x = 10;
    let handle = thread::spawn(move || {
        dbg!(x);
    });
    handle.join().unwrap();
}
В данных замыкания теперь хранится не ссылка, а копия значения, так что требование 'static удовлетворяется — ссылок-то вообще нет. Вроде просто, но есть нюансы. Например, вот так — можно:
use std::thread;
fn main() {
    let s = String::from("hello");
    let handle = thread::spawn(|| {
        dbg!(s);
    });
    handle.join().unwrap();
}
Отличие тут в том, что String не реализует интерфейс Copy, выражение dbg!(s) "съедает" переменную s по значению, поэтому для компилятора нет нужды в данных замыкания хранить ссылку на значение, можно это значение сразу переместить в данные замыкания — вне замыкания изменить s, в отличие от x, нельзя.
Иными словами, ключевое слово move незаменимо, если замыкание передается в поток (или возвращается из функции) и использует локальное значение типа, который умеет в Copy.
Как-то так, пора заканчивать.
Некоторые размышления
В прекрасном языке программирования будущего я бы кардинально упростил замыкания путем их усложнения. Указывать способ "захвата" и изменяемость следует явно, напрямую использовать переменные из окружающей среды нельзя:
    let x = 10;
    // Используем копию x
    let captureByValue = {closurex: x} || {
        dbg!(closurex)
        // dbg!(x) // error[E0425]: cannot find value `x` in this scope
    });
    // Используем изменяемую копию x
    let mutCaptureByValue = {mut closurex: x} || {
        // Изменяем локальную копию
        closurex = 20;
    });    
    // Используем ссылку на x
    let captureByReference = {closurex: &x} || {
        ...
        // Изменяем оригинальное значение
        *closurex += 1;
    });
И все, не нужно было бы писать добрую половину этой статьи, а "проблематичные" конструкции, приведенные в начале, в таком синтаксисе мигом потеряли бы всю проблематичность.
Конечно, будут определенные сложности с определением, приведенным ранее:
"Замыкание (англ. closure) в программировании — функция первого класса, в теле которой присутствуют ссылки на переменные, объявленные вне тела этой функции в окружающем коде и не являющиеся её параметрами"
Тут непросто, имеем дело с традициями, корни которых уходят в прошлое тысячелетие. Замечу, что "замыкания" в Rust, определенные с ключевым словом move, также не являются "замыканиями" в приведенном выше смысле, так у них в теле нет ссылок на внешние переменные.
На этом все по замыканиям.
          
 
mayorovp
Формально следует смотреть только лексические ссылки, то есть использование идентификаторов из внешней области видимости. А приведёт ли это к созданию ссылки или к копированию/перемещению — вопрос реализации. Кстати, как раз замыкания с копированием (т.е. захватом по значению) считаются "более настоящими".
Вы забыли про такую возможность как type alias, которая как раз и даёт возможность "посмотреть" внутрь возвращаемого типа:
Правда, реализовать такой типаж не так-то просто, и если нет желания раскрывать реализацию замыкания вручную — то самое "умное" что можно сделать — это вернуться к
Box<dyn>:Но вот в "ночной" ветке доступна фича type_alias_impl_trait:
maxim_ge Автор
Здесь не просто type alias, еще нужны associated types, которые требуют отдельного описания. Это в планах.
Если это возможно, приведите пример?
mayorovp
Я же их привёл, аж два примера.
maxim_ge Автор
После слов:
я ожидал, что таки в рамках Stable channel можно предложить сложную реализацию ("замыкания вручную"), которая возвращает замыкание не через кучу. Cобственно именно это и интересует.
Вы же предложили (для Stable channel) вернуться к Box+dyn. Ну т.е. таки "в кучу, товарищи".
В общем, смысл Вашего комментария по отношению к стабильной ветке (про что я, собственно, рассказываю) не очень понятен мне.
К слову, можно ведь и так определить тип, подающий надежды на то, что можно вернуть замыкание:
Но толку-то...
mayorovp
А, вот вы про что. Ну, никто же не мешает определить самому структуру Closure и реализовать нужный трейт, как вы это сами и показывали. Долго и нудно, но позволит избежать использования кучи в стабильной ветке.
maxim_ge Автор
Я показывал упрощенные модели оригинальных трейтов замыканий (FnOnce и т.д.), которые тоже не "настоящие" (как мне кажется). "Не настоящие" в том смысле, что они обрабатываются компилятором особым образом. Я считаю, что реализовать их самостоятельно нельзя, компилятор не поймет, ибо это его сугубо "внутреннее дело". А если и поймет, то нельзя будет вернуть, а если можно вернуть, то что-то еще пойдет не так.
Вполне могу ошибаться, и любопытно было бы увидеть пример, опровергающий эту точку зрения.
mayorovp
А, ну да, эта штука тоже ещё не стабильна...