Как мне клонировать замыкание, чтобы их типы были одинаковыми?

#rust #closures

#Ржавчина #закрытие #замыкания

Вопрос:

У меня есть структура, которая выглядит примерно так:

 pub struct MyStruct<F>
where
    F: Fn(usize) -> f64,
{
    field: usize,
    mapper: F,
    // fields omitted
}
  

Как мне реализовать Clone эту структуру?

Один из способов, который я нашел для копирования тела функции, это:

 let mapper = |x| (mystruct.mapper)(x);
  

Но это приводит к mapper получению другого типа , чем у mystruct.mapper .

игровая площадка

Комментарии:

1. Что такое I ? Это не компилируется. F Однако, если ничего не вернулось, почему бы и нет #[derive(Clone)] ?

2. @ljedrz Ах! Извините, исправлено… Пожалуйста, взгляните на ссылку playpen, которую я добавил сейчас.

Ответ №1:

Начиная с версии Rust 1.26.0, замыкания реализуют оба Copy , и Clone если все захваченные переменные выполняют:

 #[derive(Clone)]
pub struct MyStruct<F>
where
    F: Fn(usize) -> f64,
{
    field: usize,
    mapper: F,
}

fn main() {
    let f = MyStruct {
        field: 34,
        mapper: |x| x as f64,
    };
    let g = f.clone();
    println!("{}", (g.mapper)(3));
}
  

Ответ №2:

Вы не можете Clone замыкать. Единственный, кто может реализовать Clone замыкание, — это компилятор… и это не так. Итак, вы вроде как застряли.

Однако есть один способ обойти это: если у вас есть замыкание без захваченных переменных, вы можете принудительно скопировать его с помощью unsafe кода. Тем не менее, более простой подход на этом этапе состоит в том, чтобы fn(usize) -> f64 вместо этого принять a , поскольку у них нет захваченной среды (любое замыкание нулевого размера может быть переписано как функция), и есть Copy .

Комментарии:

1. Спасибо! Теперь, когда я думаю об этом, мне не нужно будет захватывать переменные в замыкании, хотя было бы приятнее (более читабельно) использовать замыкания. Есть идеи, почему Clone не реализовано для замыканий в компиляторе?

2. @John Я считаю, что часть проблемы заключается в том, что для реализации Clone компилятор должен знать, что Clone есть. Исторически это не так. Кроме того, как насчет других характеристик? Должно ли замыкание также быть Copy ? Eq ? Где вы проводите черту? Я не думаю, что кто-то действительно уверен, что именно здесь следует делать.

3. fn(usize) -> f64 («тип fn») не имеет нулевого размера, это эквивалент указателя на функцию. fn(usize) -> f64 {::some::specific::function} («тип элемента fn») имеет нулевой размер, но вы не можете записать этот тип, поэтому единственный способ использовать это — принять общий тип F: Fn(u64) -> f64 Copy (т. Е. Просто Добавить Copy привязку к коду в вопросе).

4. @delnan Это случайно не означает, что «типы fn» не могут быть встроены, но использование «fn» F: Fn(..) -> .. Copy может сделать возможным встраивание?

5. @John В принципе, если у вас где-то есть постоянный указатель на функцию, компилятор может оптимизировать на основе значения этой константы (как и с любым другим значением). Но на практике вы, вероятно, будете передавать разные указатели на функции с разных сайтов вызовов, поэтому большинство обратных вызовов, указанных через fn , действительно вряд ли будут встроены.

Ответ №3:

Вы можете использовать Rc (или Arc !), Чтобы получить несколько дескрипторов с одним и тем же неклонируемым значением. Хорошо работает с Fn замыканиями (вызываемыми через общие ссылки).

 pub struct MyStruct<F> where F: Fn(usize) -> f64 {
    field: usize,
    mapper: Rc<F>,
    // fields omitted
}

impl<F> Clone for MyStruct<F>
    where F: Fn(usize) -> f64,
{
    fn clone(amp;self) -> Self {
        MyStruct {
            field: self.field,
            mapper: self.mapper.clone(),
           ...
        }
    }
}
  

Помните, что #[derive(Clone)] это очень полезный рецепт для Clone, но его рецепт не всегда подходит для конкретной ситуации; это один из таких случаев.

Комментарии:

1. Помимо части подсчета ссылок, это так же хорошо, как «сырое» замыкание? Будет ли встроено тело функции? (Я предполагаю, что оно встроено в «необработанные» замыкания, хотя я не уверен в необходимых условиях для этого.)

2. Оно находится за указателем, так что есть эта косвенность (но это все). Я думаю, это может быть встроено нормально, но вы должны проверить.

Ответ №4:

Вы можете использовать объекты признаков, чтобы иметь возможность реализовать Сlone для своей структуры:

 use std::rc::Rc;

#[derive(Clone)]
pub struct MyStructRef<'f>  {
    field: usize,
    mapper: amp;'f Fn(usize) -> f64,
}


#[derive(Clone)]
pub struct MyStructRc  {
    field: usize,
    mapper: Rc<Fn(usize) -> f64>,
}

fn main() {
    //ref
    let closure = |x| x as f64;
    let f = MyStructRef { field: 34, mapper: amp;closure };
    let g = f.clone();
    println!("{}", (f.mapper)(3));
    println!("{}", (g.mapper)(3));

    //Rc
    let rcf = MyStructRc { field: 34, mapper: Rc::new(|x| x as f64 * 2.0) };
    let rcg = rcf.clone();
    println!("{}", (rcf.mapper)(3));
    println!("{}", (rcg.mapper)(3));    
}
  

Комментарии:

1. Да, хотя это допустимый вариант, я хотел избежать динамической отправки. Я пишу симулятор, поэтому я хочу предоставить компилятору все возможности для встраивания замыканий…

2. Я думаю, что в этом случае встраивание невозможно. Хотя обычный вызов функции (как предложено @DK.) дешевле, чем вызов объекта trait