Go在Rust中对通道的选择案例范例相当于什么?

时间:2018-09-12 19:45:27

标签: rust channel

是否可以使用Rust的频道(例如Go)?我找不到任何东西。

对于那些不熟悉Go中的select语句(来自the documentation)的人:

  

“ select”语句选择一组可能的发送或接收操作中的哪一个进行。它看起来类似于“ switch”语句,但所有情况均涉及通信操作。   具有RecvStmt的案例可以将RecvExpr的结果分配给一个或两个变量,可以使用短变量声明来声明。 RecvExpr必须是一个(可能带有括号的)接收操作。默认情况下最多可以有一个案例,它可能出现在案例列表的任何地方。

     

“ select”语句的执行分几个步骤进行:

     
      
  1. 对于该语句中的所有情况,输入“ select”语句后,接收操作的通道操作数以及send语句的通道表达式和右侧表达式将按源顺序进行一次精确评估。结果是一组要从中接收或发送到的通道,以及要发送的相应值。不管选择进行哪个通信操作,都会发生该评估中的任何副作用。尚未评估带有简短变量声明或赋值的RecvStmt左侧的表达式。
  2.   如果可以进行一种或多种通信,则可以通过统一的伪随机选择来选择可以进行的单个通信。否则,如果存在默认情况,则选择该情况。如果没有默认情况,则“ select”语句将阻塞,直到可以进行至少一种通信为止。   
  3. 除非选择的案例为默认案例,否则将执行相应的通信操作。
  4.   
  5. 如果所选案例是带有简短变量声明或赋值的RecvStmt,则将评估左侧表达式并分配接收值(或多个值)。
  6.   
  7. 执行所选案例的陈述列表。
  8.   
     

由于零通道上的通信永远无法进行,因此只有零通道且没有默认情况下的选择永远不会阻塞。

例如,如何在Rust中编写此代码?

func search(ctx context.Context, result chan IResult, q string) error {
    // googleSearch and bingSearch will return IResult interface channel
    google := googleSearch(q)
    bing := bingSearch(q)
    t := time.After(time.Second)

    for {
        select {
           // at any point if caller cancel the operation we return
           case <- ctx.Done():
               return nil
           case r, ok := <- google:
               if !ok { // check if channel is closed
                  google = nil
                  if bing == nil { // we are done
                     return nil
                  }
                  continue
               }
               // sending the google search result to result channel. ( channel to channel )
               result <- r
           case r, ok := <- bing:
               if !ok {
                  bing = nil
                  if google == nil {
                     return nil
                  }
                  continue
               }
               result <- r
           case <- t:
               // this function never lives for more then 1 second
               return fmt.Errorf("timeout")
        }
    }
    return nil
}

2 个答案:

答案 0 :(得分:5)

如果您只想超时接收,recv_timeout方法就是您想要的。

如果您想不加干扰地接收,还有一个try_recv方法。

如果要混合使用多个通道,则@Shepmaster's answer描述了我在标准库中了解的唯一方法。

然而,Rust的标准库比Go的轻巧得多,在Rust中,通常使用包装箱来进行这种处理。 crossbeam_channel板条箱确实有一个select稳定,可以用来在不同的频道上等待。

答案 1 :(得分:4)

对于标准库中的频道,最有用的答案是“没有一个”。

技术上正确的答案是select!宏:

select! {
    r = result.recv() => {
        // do something
    }
    _ = something_that_waits_a_second.recv() => {
        // timeout
    }
}

(请注意,这与OP's original example等效,之前已被彻底更改)。

它很不稳定,这就是为什么我将其归类为无用。

除了select!的稳定性之外,还有其他问题。例如,在您的Go示例中,您创建了一些将在一段时间后{time.After(time.Second)神奇地提交到频道的内容。 Rust没有始终运行的运行时来驱动这些事情。这意味着您需要产生一个OS级线程来等待一段时间,然后将一个值推入通道以执行该超时!这是非常低效的。


如果您确实在寻找与Go的绿色路线更接近的东西,我建议您研究一下期货:

extern crate futures; // 0.1.23
extern crate tokio; // 0.1.8

use futures::{future::Either, prelude::*, stream};
use std::time::{Duration, Instant};
use tokio::timer::{Delay, Interval, Timeout};

fn example() -> impl Stream<Item = String, Error = &'static str> {
    let goog = google_search();
    let bing = bing_search();

    let combined = goog.select(bing);

    let deadline = Delay::new(Instant::now() + Duration::from_secs(1))
        .map_err(drop)
        .into_stream();

    let combined = combined.map(Either::A);
    let deadline = deadline.map(Either::B);
    combined.select(deadline).then(|r| match r {
        Ok(Either::A(r)) => Ok(r),
        Ok(Either::B(_)) => Err("timeout"),
        Err(_) => Err("unexpected error"),
    })
}

fn main() {
    tokio::run({
        example()
            .map_err(|e| println!("Got an error: {}", e))
            .for_each(|r| {
                println!("Search result: {}", r);
                Ok(())
            })
    });
}

fn google_search() -> impl Stream<Item = String, Error = ()> {
    let results = (0..10).map(|x| format!("goog{}", x));
    let results = stream::iter_ok(results);

    // Fake some delay between results
    let delay = Interval::new_interval(Duration::from_millis(75));
    results.zip(delay).map(|(r, _)| r).map_err(drop)
}

fn bing_search() -> impl Stream<Item = String, Error = ()> {
    let results = (0..10).map(|x| format!("bing{}", x));
    let results = stream::iter_ok(results);

    // Fake some delay between results
    let delay = Interval::new_interval(Duration::from_millis(200));
    results.zip(delay).map(|(r, _)| r).map_err(drop)
}

在每个Go程序中都隐含了对tokio::run的调用;这将启动一个异步反应堆并“完成”运行。