< 返回版块

viruscamp 发表于 2021-03-27 12:41

Rust 闭包目前的自动类型推断,大部分情况下都好用。 但有的时候,我希望能手动,明确的标出类型,要捕获那些变量,如何捕获。 而现在的 rust 语法能标明的只有闭包参数类型和返回类型。

比如说,我写这个闭包时就知道,这个闭包一定会调用多次,而且可能跨线程调用

let fn1: impl Fn() -> i32 + Sync = || { TODO };

明确标出类型,就不会在调用的地方报错,而是把错误标记显示在类型处和闭包内部不满足此类型的地方。

明确的写出捕获列表,及其捕获方式, 类似下面,但有更明确的语法

    let fn1 = {
        let i = &i; // borrow i32
        let f = &mut f; // borrow mut f64
        let rc = rc; // move Rc<i32>
        move || {
            i + f.into() + rc
        }
    };

下面是当前能写出来的,最接近目标的写法,但确实是过于啰嗦。

    // 函数参数是捕获列表, 函数返回类型明确的标出了闭包类型
    fn closure1 (i: &i32, f: &mut f64, rc: Rc<i32>) -> impl FnMut() -> i32 + Sync {
        move || { i + f.into() + rc }
    }
    let fn1 = closure1(&i, &mut f, rc);

一种不可行的写法

    // 闭包套闭包是不行的,因为 impl 不让出现在闭包返回类型
    let fn1 = (|i, f, rc| -> impl FnMut() -> i32 + Sync {
        move || { i + f.into() + rc }
    })(&i, &mut f, rc);

评论区

写评论
作者 viruscamp 2021-03-30 20:16

两件事
1.显式标注闭包类型 最终还是写了个宏。stable 可用。

use std::rc::Rc;
macro_rules! assert_type {
    ($t: ty, $x: expr) => {
        {
            #[inline(always)]
            fn _assert_type(_: &$t) {}
            _assert_type(&$x);
        }
    };
    ($x: expr, $t: ty) => {
        assert_type!($t, $x)
    };
}

#[macro_export]
macro_rules! ensure_type {
    ($t: ty, $x: expr) => {
        {
            let x = $x;
            assert_type!($t, x);
            x
        }
    };
    ($x: expr, $t: ty) => {
        ensure_type!($t, $x)
    };
}

fn main() {
    let fn3 = ensure_type!(impl FnMut() -> i32 + Sync, || { 4 });
    let fn3x = ensure_type!(|| { 4 }, impl FnMut() -> i32 + Sync); // 确认类型无误
    let fn3c = fn3.clone(); // 保持原有类型 仍然可以 clone
    
    let rc = Rc::new(4);
    let fn4 = ensure_type!(move || { *rc }, impl Fn() -> i32 + Send); // 报错
}
  1. 捕获列表 大概只有 closure! 可用。但是它无法防止没写在捕获列表的变量被 move 进去。
Bai-Jinlin 2021-03-30 19:21

这么整有什么必要吗,反正闭包的用途90%都是当作参数传递,那样的话函数的bounds直接就可以限制闭包类型了,要么下面哥们推荐的closure这个crate都比你这样做强呀。

--
👇
viruscamp: 显式标注闭包的类型, 我之前写的时候竟然没有发现有这个 #![feature(impl_trait_in_bindings)]
相对于不写类型,限制比较多。我想了下, 这些限制对主楼写函数返回闭包的方法也是存在的。

#![feature(fn_traits)]
#![feature(unboxed_closures)]
#![feature(impl_trait_in_bindings)]

fn main() {
    let mut f1: impl FnMut() -> i32 + Clone = || { 3 }; // 闭包本身还实现了 Fn Send
    let mut f2 = f1.clone(); // 类型上写了 Clone 才能用
    //std::thread::spawn(move || { f1() }); // 类型上没写 Send 所以不能用

    let mut f3: impl FnMut<()> + Clone = || { 4 }; // 可以不写返回类型, 但也不能自动推导出来
    let f3z = f3(); // 值存在, 类型未知, 不能当 i32 用
}
作者 viruscamp 2021-03-30 14:41

显式标注闭包的类型, 我之前写的时候竟然没有发现有这个 #![feature(impl_trait_in_bindings)]
相对于不写类型,限制比较多。我想了下, 这些限制对主楼写函数返回闭包的方法也是存在的。

#![feature(fn_traits)]
#![feature(unboxed_closures)]
#![feature(impl_trait_in_bindings)]

fn main() {
    let mut f1: impl FnMut() -> i32 + Clone = || { 3 }; // 闭包本身还实现了 Fn Send
    let mut f2 = f1.clone(); // 类型上写了 Clone 才能用
    //std::thread::spawn(move || { f1() }); // 类型上没写 Send 所以不能用

    let mut f3: impl FnMut<()> + Clone = || { 4 }; // 可以不写返回类型, 但也不能自动推导出来
    let f3z = f3(); // 值存在, 类型未知, 不能当 i32 用
}
wfxr 2021-03-29 15:33

第一种方式其实就是现在惯用的写法。如果不排斥宏可以用这个crate

let closure = closure!(move string, ref x, ref mut y, clone rc, |arg: i32| {
    ...
});

语法和C++11比较接近。

1 共 4 条评论, 1 页