首页 文章

你能克隆一个闭包吗?

提问于
浏览
16

由于显而易见的原因,无法克隆 FnMut 闭包,但 Fn 闭包具有不可变的范围;有没有办法创建一个 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

我有一个带有盒装闭包的结构,我需要将该结构传递给许多线程 . 我可以't, but I also can'克隆它,因为你无法克隆 Box<Fn<>> 而你无法克隆 &Fn<...> .

3 回答

  • 11

    你要做的是从多个线程调用一个闭包 . 也就是说,跨多个线程共享闭包 . 一旦短语"share across multiple threads"穿过我的脑海,我的第一个想法是to reach for 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");
    }
    

    playground


    旧答案(这仍然相关):拥有一个 &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));
    }
    

    (部分原因是 &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 <... >>
    从技术上讲,上述工作,但似乎很奇怪 .

    从技术上讲,如果你重新给自己增加负担,而不是让编译器帮助你,它就有效 . 对编译器错误的正确响应是使用 unsafe 代码是相对罕见的,而不是深入研究错误并调整代码以使其更有意义(对编译器来说,这通常会使对人类更有意义) ) .

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

  • 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>;
    

    结束在 WithCall 中的 Fp 附近添加 Arc

    Play rust : working code Gist : working code in 1.22.1

相关问题