#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