sipper 0.0.1

A future that can notify progress
Documentation

Sipper

Crates.io License Downloads Test Status

A sipper is a Future that can notify progress.

Effectively, a Sipper combines a Future and a Sink together to represent an asynchronous task that produces some Output and notifies of some Progress, without both types being necessarily the same.

Sipper should be chosen over Stream when the final value produced—the end of the task—is important and inherently different from the other values.

An example

An example of this could be a file download. When downloading a file, the progress that must be notified is normally a bunch of statistics related to the download; but when the download finishes, the contents of the file need to also be provided.

The Uncomfy Stream

With a Stream, you must create some kind of type that unifies both states of the download:

use futures::Stream;

struct File(Vec<u8>);

struct Progress(u32);

enum Download {
   Running(Progress),
   Done(File)
}

fn download(url: &str) -> impl Stream<Item = Download> {
    // ...
}

If we now wanted to notify progress and—at the same time—do something with the final File, we'd need to juggle with the Stream:

use futures::channel::mpsc;
use futures::{SinkExt, StreamExt};

async fn example(mut on_progress: mpsc::Sender<Progress>) {
   let mut file_download = download("https://iced.rs/logo.svg").boxed();

   while let Some(download) = file_download.next().await {
       match download {
           Download::Running(progress) => {
               let _ = on_progress.send(progress).await;
           }
           Download::Done(file) => {
               // Do something with file...
               // We are nested, and there are no compiler guarantees
               // this will ever be reached. And how many times?
           }
       }
   }
}

While we could rewrite the previous snippet using loop, expect, and break to get the final file out of the Stream. We would still be introducing runtime errors and, simply put, working around the fact that a Stream does not encode the idea of a final value.

The Chad Sipper

A Sipper can precisely describe this dichotomy in a type-safe way:

use sipper::Sipper;

#[derive(Debug, PartialEq, Eq)]
struct File(Vec<u8>);

#[derive(Debug, PartialEq, Eq)]
struct Progress(u32);

fn download(url: &str) -> impl Sipper<File, Progress> {
    // ...
}

Which can then be easily used with any Sink:

use futures::channel::mpsc;

async fn example(on_progress: mpsc::Sender<Progress>) {
    let file = download("https://iced.rs/logo.svg").run(on_progress).await;

    // We are guaranteed to have a `File` here!
}