你能克隆一个闭包吗?

时间:2015-01-11 02:52:34

标签: 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`

以某种方式将原始指针传递给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<Fn<>>而你无法克隆&Fn<...>

3 个答案:

答案 0 :(得分:11)

您要做的是从多个线程调用闭包。也就是说,跨多个线程共享闭包。一旦短语&#34;分享多个线程&#34;在我脑海中浮现,我的第一个想法是to reach for Arc(至少在以某种形式实现RFC 458之前,当&可以跨线程使用时)。

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

总之,请将WithCall放入Arc并克隆。{/ p>

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


旧答案(这仍然相关):拥有&mut Fn特质对象是非常不寻常的,因为Fn::call需要&selfmut不是必需的,我认为它增加了额外的功能。拥有&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));
}

(部分原因是&TCopy,部分原因是重新借款;它也适用于&mut。)

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

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

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

没有FnMut无法克隆的固有原因,它只是一个包含某些字段的结构(以及一个需要&mut self的方法,而不是&selfself分别为FnFnOnce。如果您手动创建结构并实施FnMut,则仍可以为其实现Clone

  

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

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

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

从技术上讲,如果你小心确保Rust的别名和生命周期要求得到满足,那么它是有效的...但是通过选择不安全的指针你会给自己带来负担,而不是让编译器帮忙您。对编译器错误的正确响应是使用unsafe代码,而不是深入研究错误并调整代码以使其更有意义(对于编译器来说,通常会导致编译错误)是相对罕见的。对人类更有意义)。

答案 1 :(得分:5)

Rust 1.26

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

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*转换为原始指针并传递它并不是普遍安全的。

答案 2 :(得分:0)

这是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>;

在结构Arc

Fp周围添加WithCall

Play rust : working code Gist : working code in 1.22.1