Futures is a lightweight, general-purpose library for asynchronous programming
in Swift, that provides a set of interoperable primitives to aid development of
highly concurrent, performant and safe programs, both on the server and the
desktop or mobile. Futures adopts a “pull”-based, demand-driven approach to
asynchronous programming which, besides offering astounding performance, makes
dealing with traditionally difficult to solve problems such as backpressure,
cancellation and threading, trivial.
There is a swath of libraries for asynchronous programming in Swift already.
Many are fine libraries that you should be seriously considering if you’re
looking for an abstraction that fits your needs. So why should you care about
Futures?
You will find that Futures…
exposes APIs that feel natural in the context of the language (there is no
Subject, thank you).
does not gloss over cancellation and backpressure; handling them is a core
concern.
approaches asynchronous programming holistically, exposing a wide range of
primitives: streams, sinks, channels and more, all built on top of the
fundamental abstraction of a future. (Kitchens coming soon!)
only cares about the completion of a computation and does not artificially
terminate streams on failure, but provides extensions to get the same semantics
if you so desire.
requires no memory allocations for task execution, coordination or
communication.
aims to be a solid foundation upon which other libraries covering
disparate use-cases are built and, to that end, is easily extensible.
can be used for UI programming but thrives on the server and supports all
platforms Swift itself supports.
Features
Low-cost abstractions: The pull-based runtime model of Futures, combined
with extensive use of value types and generics, assists the compiler in
producing extremely optimized code, effectively removing the abstraction
completely.
Scalability and performance: Futures can efficiently drive hundreds of
thousands of concurrent tasks on a single OS thread and scale almost linearly
with the number of CPU cores assigned to the system.
Safety and correctness: The simple runtime model and well-thought out APIs
ensure reasoning about the lifetime and the context in which your code executes
is straightforward.
Cancellation and backpressure: In Futures, dealing with cancellation and
backpressure is extremely easy, as their solution falls naturally out of the
adopted demand-driven approach.
Read on, there’s more in store:
Soft real-time safety: Futures encourages a clear separation of acquiring
the needed resources for a task, from actually performing the task. This
separation, combined with the fact that Futures itself requires no locks or
memory allocations for task execution, coordination or communication, allows
you to write asynchronous code in a soft real-time context such as a high-priority
audio thread, like you would everywhere else.
Debugability and testability: Seen in the abstract, Futures is a DSL for
building state machines, resulting in a system that can be easily inspected.
Combined with the demand-driven approach that puts the consumer in control,
testing and debugging Futures-based code is straightforward.
Flexibility and extensibility: Futures is not intrusive. You may adopt
it in your programs incrementally and even then you can opt to use only the
parts that best fit your use-case. In addition, you can extend the system by
providing custom implementations of core protocols.
Zero dependencies: Futures does not depend on anything other than
Swift’s standard library and compiler.
import Futures
let integers = Stream.sequence(0...)
let primes = integers.filter(isPrime)
var answer = primes.buffer(4)
.map { $0[0] * $0[1] * $0[3] }
.first(where: isPronic)
print(answer.wait()) // prints 42
Here’s the same program contrived enough to showcase several types and concepts
prevalent in Futures – segregating tasks into different execution contexts,
inter-task communication with channels, extracting the result from a remote
task, and more. Find out more in the documentation. Share and Enjoy.
import Futures
// Create *executors* to spawn *tasks* on. Each QueueExecutor
// is backed by a private serial DispatchQueue.
let deepThought = (
cpu0: QueueExecutor(label: "CPU 0"),
cpu1: QueueExecutor(label: "CPU 1"),
cpu2: QueueExecutor(label: "CPU 2")
)
// Create two *channels* via which values can be communicated
// between parts of the program.
let pipe1 = Channel.makeUnbuffered(itemType: Int.self)
let pipe2 = Channel.makeUnbuffered(itemType: Int.self)
// Spawn a task that produces positive integers on one
// executor and sends the values to one of the channels.
let integers = Stream.sequence(0...)
deepThought.cpu1.submit(integers.forward(to: pipe1))
// Spawn another task on the second executor that receives
// these values, filters out non-prime integers and sends
// the remaining down the second channel.
let primes = pipe1.makeStream().filter(isPrime)
deepThought.cpu2.submit(primes.forward(to: pipe2))
// Spawn a third task on the third executor that receives
// the prime numbers via the channel and performs the actual
// computation of the answer. For this task, we ask for a
// handle back, with which we can get the final result or
// cancel it.
var answer = deepThought.cpu0.spawn(
pipe2.makeStream()
.buffer(4)
.map { $0[0] * $0[1] * $0[3] }
.first(where: isPronic)
)
// At this point, everything happens asynchronously in
// secondary background threads, so the program would just
// exit. We need the answer first however, so we block the
// current thread waiting for the result of the computation
// which we just print to standard output.
print(answer.wait()) // prints "Result.success(42)"
You’ll need the following functions if you want to run the program yourself
and experiment:
import Foundation
func isPrime(_ n: Int) -> Bool {
return n == 2 || n > 2 && (2...(n - 1)).allSatisfy {
!n.isMultiple(of: $0)
}
}
func isPronic(_ n: Int) -> Bool {
let f = floor(Double(n).squareRoot())
let c = ceil(Double(n).squareRoot())
return n == Int(f) * Int(c)
}
Getting Started
Requirements
Futures requires Swift 5.0 (or newer) and can be deployed to any of the
following platforms:
macOS 10.12+
iOS 10+
tvOS 10+
watchOS 3+
Ubuntu 16.04+
Installation
To integrate Futures with the Swift Package Manager, add the
following line in the dependencies list in your Package.swift:
Note: Futures is in its early days and its public APIs are bound to change
significantly. Until version 1.0, breaking changes will come with minor version
bumps.
Then add Futures as a dependency of the targets you wish to use it in. Futures
exports two separate modules:
Futures: the core library.
FuturesSync: an assortment of thread synchronization primitives and
helpers. This module is currently highly experimental and its use is
discouraged.
Have a general questionorneed help with your code? Check out
questions tagged with #swift-futures in Stack Overflow.
Have a feature request? Have a look at the issue tracker for issues
tagged with #enhancement. Add a comment describing your
use-case on an existing issue if it’s already been reported, or open a new
one describing the feature and how you think it can help you.
Found a bug?Open an issue. Don’t forget to mention
the version of Futures you observed the bug with and include as much information
as possible. Bug reports with minimal code examples that reproduce the
issue are much appreciated.
Futures
Futures is a lightweight, general-purpose library for asynchronous programming in Swift, that provides a set of interoperable primitives to aid development of highly concurrent, performant and safe programs, both on the server and the desktop or mobile. Futures adopts a “pull”-based, demand-driven approach to asynchronous programming which, besides offering astounding performance, makes dealing with traditionally difficult to solve problems such as backpressure, cancellation and threading, trivial.
Documentation / A Quick Example / Requirements / Installation
Why Futures?
There is a swath of libraries for asynchronous programming in Swift already. Many are fine libraries that you should be seriously considering if you’re looking for an abstraction that fits your needs. So why should you care about Futures?
You will find that Futures…
Subject
, thank you).Features
Read on, there’s more in store:
A Quick Example
Here’s a small program that computes the Answer to the Ultimate Question of Life, the Universe, and Everything:
Here’s the same program contrived enough to showcase several types and concepts prevalent in Futures – segregating tasks into different execution contexts, inter-task communication with channels, extracting the result from a remote task, and more. Find out more in the documentation. Share and Enjoy.
You’ll need the following functions if you want to run the program yourself and experiment:
Getting Started
Requirements
Futures requires Swift 5.0 (or newer) and can be deployed to any of the following platforms:
Installation
To integrate Futures with the Swift Package Manager, add the following line in the dependencies list in your
Package.swift
:Note: Futures is in its early days and its public APIs are bound to change significantly. Until version 1.0, breaking changes will come with minor version bumps.
Then add Futures as a dependency of the targets you wish to use it in. Futures exports two separate modules:
Futures
: the core library.FuturesSync
: an assortment of thread synchronization primitives and helpers. This module is currently highly experimental and its use is discouraged.Here is an example
Package.swift
:Getting Help
References
License
Futures is licensed under the terms of the MIT license. See LICENSE for details.