Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Stream vs Effect — When to Use Each

The choice is about cardinality.

Effect<A, E, R>  -> produces exactly one A or fails
Stream<A, E, R>  -> produces zero or more A values or fails

Concrete Examples

fn get_user(id: u64) -> Effect<User, DbError, Db>
fn all_users() -> Stream<User, DbError, Db>

fn count_orders() -> Effect<u64, DbError, Db>
fn export_orders() -> Stream<Order, DbError, Db>

If you fetch 10 million rows into a Vec and return it as an Effect, you can run out of memory. A Stream lets consumers process chunks incrementally.

Stream Transformations

all_users()
    .filter(Box::new(|u: &User| u.is_active()))
    .map(UserSummary::from)
    .take(100)

Current element operators include map, filter, take, take_while, drop_while, map_effect, and map_par_n.

Collecting a Stream into an Effect

When all results fit in memory, use run_collect.

let users: Effect<Vec<User>, DbError, Db> = all_users().run_collect();

For large results, prefer a fold or a sink.

let count: Effect<usize, DbError, Db> = all_users().run_fold(0, |acc, _| acc + 1);

Converting Effect to Stream

Stream::from_effect expects an effect that produces a Vec<A>.

use effectful::Stream;

let one_user: Effect<Vec<User>, DbError, Db> = get_user(1).map(|user| vec![user]);
let stream: Stream<User, DbError, Db> = Stream::from_effect(one_user);

For pure finite streams, use Stream::from_iterable.

let numbers = Stream::from_iterable([1, 2, 3]);

The Rule

NeedUse
One resultEffect
Many resultsStream
All stream results in memorystream.run_collect()
Aggregated resultstream.run_fold(init, f) or Sink::fold_left
Custom consumersink.run(stream)