你能克隆一个闭包吗?

Dou*_*oug 17 closures rust

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`
Run Code Online (Sandbox Code Playgroud)

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

let func_pnt = &mut Box<Fn<...> + Send> as *mut Box<Fn<...>>
Run Code Online (Sandbox Code Playgroud)

从技术上讲,上述工作,但似乎很奇怪.

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

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");
}
Run Code Online (Sandbox Code Playgroud)

操场

我有一个带有盒装闭包的结构,我需要将该结构传递给许多线程.我不能,但我也无法克隆它,因为你无法克隆一个Box<Fn<>>而你无法克隆一个&Fn<...>.

huo*_*uon 11

你要做的是从多个线程调用一个闭包.也就是说,跨多个线程共享闭包.一旦"跨越多个线程共享"这个短语贯穿我的脑海,我的第一个想法就是达到目标Arc(至少在RFC 458以某种形式实现,何时&可以跨线程使用).

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

总而言之,把你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");
}
Run Code Online (Sandbox Code Playgroud)

操场


旧的答案(这仍然是相关的):有一个&mut Fn特征对象是非常罕见的,因为Fn::call需要&self.这mut不是必需的,我认为它增加了额外的功能.有一个&mut Box<Fn()>确实添加了一些功能,但它也是不寻常的.

如果你改变一个&指针而不是一个&mut东西将更自然地工作(与两个&Fn&Box<Fn>).如果没有看到你正在使用的实际代码,很难准确地说出你在做什么,但是

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));
}
Run Code Online (Sandbox Code Playgroud)

(这部分是由于&T存在Copy而且部分原因是再借贷;它也适用&mut.)

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

fn foo(f: &Fn()) {
    something_else(|| f())
}
Run Code Online (Sandbox Code Playgroud)

一个FnMut封闭无法克隆,出于显而易见的原因.

有没有内在的原因FnMut不能被克隆,它只是一个带有某些字段(而这需要一个方法结构&mut self,而不是&selfself作为FnFnOnce分别).如果您创建一个结构并FnMut手动实现,您仍然可以实现Clone它.

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

let func_pnt = &mut Box<Fn<...> + Send> as *mut Box<Fn<...>>
Run Code Online (Sandbox Code Playgroud)

从技术上讲,上述工作,但似乎很奇怪.

从技术上讲它的工作原理,如果你很小心,以确保锈的混淆和寿命的要求得到满足......但是透过在不安全的指针,你把你自己说的负担,不会让你的编译器的帮助.这是比较罕见的,一个编译器错误正确答案是使用unsafe代码,而不是在深入研究的错误和调整的代码,使其更有意义(编译器,其结果往往是使更多的意义,人类) .


She*_*ter 5

Rust 1.26

闭包实现两者Copy,Clone如果所有捕获的变量都这样做.您可以重写代码以使用泛型而不是盒装特征对象来克隆它:

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");
}
Run Code Online (Sandbox Code Playgroud)

在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));
}
Run Code Online (Sandbox Code Playgroud)

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

  • @dhardy是的,它不是*需要的*,但它的存在是为了镜像OP的原始结构,其中它们有一个包含闭包的类型,而不仅仅是一个闭包。事实上,你可以“derive(Clone)”它表明闭包是可克隆的,这也是我的第一句话试图传达的内容:*如果所有捕获的变量都这样做,则闭包实现“Copy”和“Clone”*。不,[您无法克隆装箱特征对象,因为“Clone”不是对象安全的](/sf/ask/2124742371/)。 (2认同)