Что эквивалентно парадигме выбора регистра Go для каналов в Rust? - PullRequest
0 голосов
/ 12 сентября 2018

Есть ли способ использовать каналы Rust, такие как Go? Я не смог найти ни одного.

Для тех, кто не знаком с оператором select в Go (из документация ):

Оператор "select" выбирает, какой из набора возможных операций отправки или получения будет продолжен. Это похоже на оператор «switch», но в случаях, когда все относится к операциям связи. Случай с RecvStmt может присвоить результат RecvExpr одной или двум переменным, которые могут быть объявлены с помощью краткого объявления переменных. RecvExpr должен быть (возможно заключенным в скобки) операцией приема. Может быть не более одного случая по умолчанию, и он может появиться где угодно в списке случаев.

Выполнение оператора «select» выполняется в несколько этапов:

  1. Для всех случаев в операторе операнды канала операций приема и выражения канала и правой части операторов отправки вычисляются ровно один раз, в исходном порядке, после ввода оператора "выбор". Результатом является набор каналов для приема или отправки и соответствующие значения для отправки. Любые побочные эффекты в этой оценке будут иметь место независимо от того, какая (если таковая имеется) операция связи выбрана для продолжения. Выражения в левой части RecvStmt с кратким объявлением или присваиванием переменной еще не оценены.
  2. Если одно или несколько сообщений может продолжаться, то одно, которое может продолжаться, выбирается с помощью равномерного псевдослучайного выбора. В противном случае, если есть случай по умолчанию, этот случай выбирается. Если регистр по умолчанию отсутствует, оператор SELECT блокируется до тех пор, пока не будет продолжено хотя бы одно из сообщений.
  3. Если выбранный регистр не является регистром по умолчанию, выполняется соответствующая операция связи.
  4. Если выбранный случай представляет собой RecvStmt с коротким объявлением переменной или присваиванием, левые выражения вычисляются и присваивается полученное значение (или значения).
  5. Выполняется список выписок выбранного дела.

Поскольку обмен данными по нулевым каналам никогда не может быть продолжен, выбор только с нулевыми каналами и без учета регистра по умолчанию навсегда.

Например, как я могу написать это в 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 голосов
/ 13 сентября 2018

Если вы просто хотите получить с таймаутом, вам нужен метод recv_timeout.

Существует также метод try_recv, если вы хотите получать без блокировки.

Если вы хотите смешать несколько каналов, то @ ответ Шепмастера описывает единственный известный мне способ в стандартной библиотеке.

Однако стандартная библиотека Rust намного легче, чем Go, и в Rust для таких вещей принято использовать ящики. Ящик crossbeam_channel имеет select, который является стабильным и может использоваться для ожидания на разных каналах.

0 голосов
/ 12 сентября 2018

Для каналов в стандартной библиотеке самый полезный ответ - "нет ни одного".

Технически правильный ответ - макрос select!:

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

(Обратите внимание, что это эквивалент оригинального примера OP до того, как он был радикально изменен).

Это нестабильно, поэтому я классифицирую его как бесполезное.

Помимо стабильности select! есть и другие проблемы. Например, в вашем примере Go вы создаете что-то, что волшебным образом отправляется в канал через некоторое время (time.After(time.Second)). У Rust нет постоянно работающей среды для управления этими вещами. Это означает, что вам нужно будет создать поток уровня ОС, чтобы подождать некоторое время, и вставить значение в канал, чтобы выполнить этот тайм-аут! Это довольно неэффективно.


Если вы действительно ищете что-то ближе к зеленым нитям 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)
}

Этот вызов tokio::run подразумевается в каждой программе Go; это запускает асинхронный реактор и запускает его "до завершения".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...