Rust为什么这么难学?

在使用 Rust 的过程中,相信很多朋友都有过类似的吐槽:真不确定自己要掌握多少语言知识、多少独门编程技巧和多么旺盛的好奇心,才能坚持做完这项最最琐碎的工作。绝望之下,我们往往会去 rust/issues 寻找解决办法,然后突然发现了一种在理论上根本不成立的 API 设计思路。这种矛盾源自某种微妙的语言Bug,简直神奇。

我从四年前开始接触Rust。目前为止,我写过相关的书和文章,也翻译了不少语言发布的公告。我还设法用Rust 编写过一些生产代码,甚至有幸在一场关注 Rust 的在线研讨上发过言。

虽然也算是身经百战,但我还是动不动就会跟Rust的借用检查器和类型系统“闹出”些不愉快。现在的我,虽然已经慢慢理解了Rust “无法返回对临时值的引用”之类的错误,也设计出一些启发式的策略来处理生命周期问题,但最近一个意外再次打击了我的信心……

初次尝试:用来处理更新的函数

我正打算编写一个聊天机器人,来改善用户的使用体验。通过长轮询或 webhooks,我开始一个个获取服务器更新流。我有一个面向全体更新的处理程序向量,其中每个处理程序都会接收对更新的引用,再把后续解析返回至()。这个处理程序向量由Dispatcher所有,每次有更新传入时,Dispatcher都会按顺序执行各个处理程序。

下面,试试具体实现。这里省略掉处理程序的执行部分,只关注push_handler函数。初次尝试:省略处理程序的执行,只关注push_handler函数。第一次尝试:

use futures::future::BoxFuture;use std::future::Future;



#[derive(Debug)]struct Update;



type Handler = Box<dyn for<'a> Fn(&'a Update) -> BoxFuture<'a, ()> + Send + Sync>;



struct Dispatcher(Vec<Handler>);



impl Dispatcher {

    fn push_handler<'a, H, Fut>(&mut self, handler: H)

    where

        H: Fn(&'a Update) -> Fut + Send + Sync + 'a,

        Fut: Future<Output = ()> + Send + 'a,

    {

        self.0.push(Box::new(move |upd| Box::pin(handler(upd))));

    }

}



fn main() {

    let mut dp = Dispatcher(vec![]);





    dp.push_handler(|upd| async move {

        println!("{:?}", upd);

    });

}

在这里,我使用由HRTB生命周期for<'a>限制的动态类型Fn trait来表示每个更新处理程序。因为我希望返回的future由&'a Update函数参数中的'a部分决定。之后,我们又定义了拥有 Vec<Handler>的 Dispatcher类型。

在push_handler当中,我们接受一个静态类型的泛型H来返回Fut;为了将此类型的值推送至self.0,我们需要将处理程序打包至新的装箱处理程序当中,再使用Box::pin将返回的 future转换为来自futures箱的BoxFuture。

下面来看看这个解决思路行不行得通:

error[E0312]: lifetime of reference outlives lifetime of borrowed content...

  --> src/main.rs:17:58

   |17 |         self.0.push(Box::new(move |upd| Box::pin(handler(upd))));

   |                                                          ^^^

   |

note: ...the reference is valid for the lifetime `'a` as defined here...

  --> src/main.rs:12:21

   |12 |     fn push_handler<'a, H, Fut>(&mut self, handler: H)

   |                     ^^

note: ...but the borrowed content is only valid for the anonymous lifetime #1 defined here

  --> src/main.rs:17:30

   |17 |         self.0.push(Box::new(move |upd| Box::pin(handler(upd))));

很遗憾,这办法行不通。

问题在于push_handler会接收一个具体的生命周期'a ,也就是我们试图将HRTB的生命周期归结成for<'a>。在这种情况下,我们就需要证明for<'a, 'b> 'a: 'b(其中'b为来自push_handler 的'a),这显然不成立。

对于这个问题,我们可以尝试几种不同的处理方法:替换掉Fut泛型,转而强制要求user handler返回由 for<'a>限定的BoxFuture:

use futures::future::BoxFuture;



#[derive(Debug)]struct Update;



type Handler = Box<dyn for<'a> Fn(&'a Update) -> BoxFuture<'a, ()> + Send + Sync>;



struct Dispatcher(Vec<Handler>);



impl Dispatcher {

    fn push_handler<H>(&mut self, handler: H)

    where

        H: for<'a> Fn(&'a Update) -> BoxFuture<'a, ()> + Send + Sync + 'static,

    {

        self.0.push(Box::new(move |upd| Box::pin(handler(upd))));

    }

}



fn main() {

    let mut dp = Dispatcher(vec![]);





    dp.push_handler(|upd| {

        Box::pin(async move {

            println!("{:?}", upd);

        })

    });

}

现在编译部分没问题了,但最终得到的API还是有问题:理想情况下,我并不希望用户通过 Box::pin打包每个处理程序。毕竟push_handler才是专门干这个的,负责把静态类型的处理程序转换成动态类型空间中的等效形式。但如果我强行要求处理程序保持静态,又会如何?

要探究答案,我们可以用异构列表来试试。

第二次尝试:异构列表

异构列表这名称看着唬人,实际上就是大家熟悉的元组。也就是说,我们需要的是(H1, H2, H3, ...),其中每个H代表不同的处理程序类型。但同时,push_handler和execute 操作又要求我们

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值