【问题标题】:Can you clone a closure?你能克隆一个闭包吗?
【发布时间】:2015-03-09 03:05:04
【问题描述】:

FnMut 闭包不能被克隆,原因很明显,但 Fn 闭包具有不可变范围;有什么方法可以创建 Fn 闭包的“副本”吗?

尝试克隆它会导致:

error[E0599]: no method named `clone` found for type `std::boxed::Box<std::ops::Fn(i8, i8) -> i8 + std::marker::Send + 'static>` in the current scope
  --> src/main.rs:22:25
   |
22 |             fp: self.fp.clone(),
   |                         ^^^^^
   |
   = note: self.fp is a function, perhaps you wish to call it
   = note: the method `clone` exists but the following trait bounds were not satisfied:
           `std::boxed::Box<std::ops::Fn(i8, i8) -> i8 + std::marker::Send> : std::clone::Clone`

以某种方式将原始指针传递给Fn 是否安全,例如:

let func_pnt = &mut Box<Fn<...> + Send> as *mut Box<Fn<...>>

从技术上讲,上述方法可行,但看起来很奇怪。

这是我正在尝试做的一个示例:

use std::thread;

struct WithCall {
    fp: Box<Fn(i8, i8) -> i8 + Send>,
}

impl WithCall {
    pub fn new(fp: Box<Fn(i8, i8) -> i8 + Send>) -> WithCall {
        WithCall { fp: fp }
    }

    pub fn run(&self, a: i8, b: i8) -> i8 {
        (self.fp)(a, b)
    }
}

impl Clone for WithCall {
    fn clone(&self) -> WithCall {
        WithCall {
            fp: self.fp.clone(),
        }
    }
}

fn main() {
    let adder = WithCall::new(Box::new(|a, b| a + b));
    println!("{}", adder.run(1, 2));

    let add_a = adder.clone();
    let add_b = adder.clone();

    let a = thread::spawn(move || {
        println!("In remote thread: {}", add_a.run(10, 10));
    });

    let b = thread::spawn(move || {
        println!("In remote thread: {}", add_b.run(10, 10));
    });

    a.join().expect("Thread A panicked");
    b.join().expect("Thread B panicked");
}

playground

我有一个带有盒装闭包的结构,我需要将该结构传递给多个线程。我不能,但我也不能克隆它,因为你不能克隆Box&lt;Fn&lt;&gt;&gt;,也不能克隆&amp;Fn&lt;...&gt;

【问题讨论】:

  • 你想用克隆的闭包做什么?
  • 你的完整代码是什么?
  • @shepmaster 我特别想克隆一个没有可变状态的闭包,以便同时将其移动到多个任务中。请参阅我附上的示例。
  • "你不能克隆 &Fn<...>"。你can,特别是?Sized,意味着它适用于特征对象。
  • @dbaupp Box::new(*(&(*self.fp)).clone()) -> 错误:特征core::marker::Sized 没有为core::ops::Fn(i8, i8) -&gt; i8 + Send 类型实现跨度>

标签: closures rust


【解决方案1】:

锈 1.26

如果所有捕获的变量都实现,闭包实现CopyClone。您可以重写代码以使用泛型而不是盒装 trait 对象来克隆它:

use std::thread;

#[derive(Clone)]
struct WithCall<F> {
    fp: F,
}

impl<F> WithCall<F>
where
    F: Fn(i8, i8) -> i8,
{
    pub fn new(fp: F) -> Self {
        WithCall { fp }
    }

    pub fn run(&self, a: i8, b: i8) -> i8 {
        (self.fp)(a, b)
    }
}

fn main() {
    let adder = WithCall::new(|a, b| a + b);
    println!("{}", adder.run(1, 2));

    let add_a = adder.clone();
    let add_b = adder;

    let a = thread::spawn(move || {
        println!("In remote thread: {}", add_a.run(10, 10));
    });

    let b = thread::spawn(move || {
        println!("In remote thread: {}", add_b.run(10, 10));
    });

    a.join().expect("Thread A panicked");
    b.join().expect("Thread B panicked");
}

Rust 1.26 之前

请记住,闭包会捕获它们的环境,因此它们有自己的生命周期,基于环境。但是,您可以引用 Fn* 并进一步传递它们,或者将它们存储在结构中:

fn do_more<F>(f: &F) -> u8
where
    F: Fn(u8) -> u8,
{
    f(0)
}

fn do_things<F>(f: F) -> u8
where
    F: Fn(u8) -> u8,
{
    // We can pass the reference to our closure around,
    // effectively allowing us to use it multiple times.
    f(do_more(&f))
}

fn main() {
    let val = 2;
    // The closure captures `val`, so it cannot live beyond that.
    println!("{:?}", do_things(|x| (x + 1) * val));
}

我会说,由于生命周期问题,将Fn* 转换为原始指针并传递它并不是普遍安全的。

【讨论】:

  • Shep, WithCall 实际上什么也没做; you can clone without it。没有什么能阻止F: Fn(i8) -&gt; i8 支持Clone。但是尝试克隆Box&lt;Fn(i8) -&gt; i8&gt;;我don't think这是(安全地)可能的!
  • @dhardy 是的,它不是需要,但它的存在是为了反映 OP 的原始结构,其中它们具有包含闭包的类型,而不仅仅是闭包。您可以在上面使用derive(Clone) 的事实表明闭包是可克隆的,这也是我的第一句话试图传达的内容:如果所有捕获的变量都实现,则闭包实现CopyClone。不,you cannot clone a boxed trait object because Clone is not object safe
  • 那么根本就没有办法复制Fn(...) trait 对象吗?当然 &amp;Fn(...)Copy 但这有生命周期限制。
【解决方案2】:

您要做的是从多个线程调用闭包。也就是说,跨多个线程共享闭包。一想到“跨多个线程共享”这个短语,我首先想到的是to reach for Arc(至少在RFC 458 以某种形式实现之前,&amp; 将变得可以跨线程使用)。

这允许安全的共享内存(它实现Clone 而不要求其内部类型为Clone,因为Clone 只是创建了一个指向同一内存的新指针),因此您可以拥有一个@987654331 @对象在多个线程中使用,无需复制。

总而言之,将您的 WithCall 放入 Arc 并克隆它。

use std::sync::Arc;
use std::thread;

type Fp = Box<Fn(i8, i8) -> i8 + Send + Sync>;

struct WithCall {
    fp: Fp,
}

impl WithCall {
    pub fn new(fp: Fp) -> WithCall {
        WithCall { fp }
    }

    pub fn run(&self, a: i8, b: i8) -> i8 {
        (self.fp)(a, b)
    }
}

fn main() {
    let adder = WithCall::new(Box::new(|a, b| a + b));
    println!("{}", adder.run(1, 2));

    let add_a = Arc::new(adder);
    let add_b = add_a.clone();

    let a = thread::spawn(move || {
        println!("In remote thread: {}", add_a.run(10, 10));
    });
    let b = thread::spawn(move || {
        println!("In remote thread: {}", add_b.run(10, 10));
    });

    a.join().expect("thread a panicked");
    b.join().expect("thread b panicked");
}

playground


旧答案(这仍然相关):拥有&amp;mut Fn 特征对象是很不寻常的,因为Fn::call 需要&amp;selfmut 不是必需的,我认为它实际上增加了零额外功能。拥有&amp;mut Box&lt;Fn()&gt; 确实增加了一些功能,但也很不寻常。

如果您更改为&amp; 指针而不是&amp;mut,事情会更自然地工作(&amp;Fn&amp;Box&lt;Fn&gt;)。如果没有看到您正在使用的实际代码,就很难确切地知道您在做什么,但是

fn call_it(f: &Fn()) {
    (*f)();
    (*f)();
}

fn use_closure(f: &Fn()) {
    call_it(f);
    call_it(f);
}

fn main() {
    let x = 1i32;
    use_closure(&|| println!("x is {}", x));
}

(这部分是由于&amp;TCopy,部分是由于重新借用;它也适用于&amp;mut。)

或者,您可以关闭闭包,这可能适用于更多情况:

fn foo(f: &Fn()) {
    something_else(|| f())
}

由于显而易见的原因,无法克隆 FnMut 闭包。

FnMut 不能被克隆并没有内在的原因,它只是一个带有一些字段的结构(以及一个采用 &amp;mut self 的方法,而不是 &amp;selfself 就像 Fn 和 @ 987654355@ 分别)。如果您创建一个结构并手动实现FnMut,您仍然可以为它实现Clone

或者以某种方式将原始指针传递给 Fn 是否安全,例如:

let func_pnt = &mut Box<Fn<...> + Send> as *mut Box<Fn<...>>

从技术上讲,上述方法可行,但看起来很奇怪。

从技术上讲,如果你小心地确保满足 Rust 的别名和生命周期要求,它就可以工作......但是通过选择不安全的指针,你把这个负担放在了自己身上,而不是让编译器帮助你。对编译器错误的正确响应是使用unsafe 代码,而不是深入研究错误并调整代码以使其更有意义(对编译器而言,这通常会使其更有意义),这是相对罕见的对人类)。

【讨论】:

  • 对不起,这是一个有点垃圾的问题。我已经用我正在尝试做的示例对其进行了更新。关闭一个闭包可能是一个解决方案,但我不确定你会怎么做。
  • @Doug,是的,没有实际来源的问题很难以有用的方式回答。已更新。
  • @dhardy huon 并不是说​​你可以克隆 &amp;mut T,而是说你可以克隆一个结构体,该结构体的 function 接受 &amp;mut T还有一个采用&amp;mut self方法
  • @dhardy 没有理由需要Arc——How can I pass a reference to a stack variable to a thread? 涵盖了这一点。 std::thread 确实需要 Arc,因为所有权已授予线程。
  • @Shepmaster 我假设克隆的函数可能会在 same 数据上调用,但当然克隆 FnMut 也会克隆数据,所以 huon 是正确的。 (我删除了我之前的评论,因为现在不允许我更正它,并且没有必要在此处留下错误信息。)
【解决方案3】:

这是 1.22.1 中的工作代码

我们的目的是完成这项工作。

let x = |x| { println!("----{}",x)};

let mut y = Box::new(x);

y.clone();

使用了顶部建议的原始代码。

我从克隆 Fn 闭包开始。

type Fp = Box<Fn(i8, i8) -> i8 + Send + Sync>;

最终在结构体WithCall 中添加Arc 周围的Fp

Play rust : working code Gist : working code in 1.22.1

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-24
    • 2011-03-04
    • 2016-08-31
    相关资源
    最近更新 更多