# future: making R code parallel by changing an assignment operator

> future is an Apache-2.0 R package that evaluates R expressions asynchronously, so the same code runs sequentially or in parallel depending on a backend you select with `plan()`. The judgement is that the implicit `%<-%` form is the package's best idea and its biggest trap, and that the honest alternative is writing the loop yourself and dispatching it to a cluster you control.

**futureverse/future** — :rocket: R package: future: Unified Parallel and Distributed Processing in R for Everyone

- Repository: https://github.com/futureverse/future
- Website: https://future.futureverse.org
- Stars: 1,015 · Forks: 94
- Language: R
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/futureverse-future

## One operator changes where your expression runs

The central trick of the future package is that parallel evaluation is a change of assignment operator rather than a rewrite of the program.

Plain R assigns with `<-`. A future assigns with `%<-%`:

```r
> library(future)
> v %<-% {
+   cat("Hello world!\n")
+   3.14
+ }
> v
Hello world!
[1] 3.14
```

The output ordering is the tell. In the plain version the message `Hello world!` prints while the expression is evaluated; in the future version it is relayed after the future is resolved, when the value is queried. The documentation points at a vignette called 'Outputting Text' for that behaviour, and it is the first thing that surprises people.

What makes it useful is that the evaluation site is decoupled from the consumption site. The same snippet under a different backend runs in a separate R process in the background, and the main process stays free for other work:

```r
> library(future)
> plan(multisession)
> v %<-% {
+   cat("Hello world!\n")
+   3.14
+ }
> v
Hello world!
[1] 3.14
```

The `plan()` call is the whole configuration surface for the common case. It sets which backend resolves futures, and the package ships backends built on the `parallel` package that is part of R itself, so a plain installation has something to work with. The homepage for the documentation is `future.futureverse.org`.

There is a second layer above this package. The `future` package is the core, and most users meet the ecosystem through higher-level packages such as `futurize`, which adds a map-reduce style wrapper that works with base R `apply`, `purrr` `map` and `foreach` loops.

## futurize turns lapply, map and foreach into the same shape

The `futurize` examples in the README are the fastest way to understand the ecosystem, because the same transformation is applied to three different ecosystems and the result reads identically.

Base R `lapply` becomes:

```r
library(futurize)

## Sequential and parallel base R apply
y <- lapply(X, slow_fcn)
y <- lapply(X, slow_fcn) |> futurize()
```

`purrr` becomes:

```r
library(purrr)
y <- X |>  map(slow_fcn)
y <- X |>  map(slow_fcn) |> futurize()
```

And `foreach` becomes:

```r
library(foreach)
y <- foreach(x = X) %do% slow_fcn(x)
y <- foreach(x = X) %do% slow_fcn(x) |> futurize()
```

The pattern in each case is append `|> futurize()` to a call that already works sequentially, and the sequential version stays in the file next to it. That is the design goal stated in the README: parallelize existing R code, often with only a minor change, lowering the barrier so anyone can speed up existing code without additional setup or technical skills.

The alternative to this approach is to learn the `parallel` package's own `mclapply` and `makeCluster` and `clusterApply` functions, or to use `foreach` with a parallel backend registered through `doParallel` or `doSNOW`. Those routes give you more direct control over how work is divided, at the cost of writing a different code path for the parallel case and maintaining both. `futurize` is explicitly a convenience layer for people who do not want that, and the trade is that the underlying division of work is not yours to specify.

## Implicit futures are convenient and, the documentation admits, deceiving

The package offers two styles and is unusually candid about the difference.

The implicit style is `v %<-% { expr }`, which creates a future and a promise to its value in one step. The explicit style is two steps:

```r
> library(future)
> f <- future({
+   cat("Hello world!\n")
+   3.14
+ })
> v <- value(f)
Hello world!
> v
[1] 3.14
```

The documentation's summary of the two is worth quoting in spirit because it is the whole design debate in two sentences. `f <- future({ expr })` creates a future. `v <- value(f)` gets the value of the future and blocks if it is not yet resolved. `v %<-% { expr }` creates a future and a promise to its value.

The argument for implicit futures is that they are most similar to how regular R code is written, and in principle you replace `<-` with `%<-%`. The argument against is stated plainly: this simplicity can be deceiving, particularly when asynchronous futures are used, and the explicit style makes it much clearer that futures are involved, which lowers the risk of mistakes and communicates the design better to other people reading the code.

There is also a documented constraint. The README notes that there are cases where `%<-%` cannot be used without some small modifications, and points to a section called 'Constraints when using Implicit Futures' near the end of the document. So the minor change is sometimes a slightly larger change.

For a codebase you maintain, the explicit form is probably the right default. For a script you run once to get a result, the implicit form is the reason to adopt the package at all.

## A future is a value that may not be there yet

The conceptual definition in the introduction is the part that makes the API click.

A future is an abstraction for a value that may be available at some point in the future. Its state is either unresolved or resolved. As soon as it is resolved, the value is available instantaneously. If the value is queried while the future is still unresolved, the current process blocks until the future is resolved. It is also possible to check whether a future is resolved without blocking.

Where and when a future gets resolved depends on which backend is set. The sequential backend resolves it in the current R session, which is the default behaviour people get without configuring anything. Other backends resolve it asynchronously, in parallel on the current machine or on a compute cluster.

That default is worth understanding because it is the safety property of the package. The sequential backend means an unconfigured script behaves exactly as it did before, which is what makes the `%<-%` substitution safe to try. The moment you call `plan(multisession)`, the same code runs somewhere else.

The two things that change when a future is resolved elsewhere are the ones that catch people out. The first is output, which is relayed after resolution rather than during it. The second is side effects: an expression that writes to a file, modifies a global, or prints will do so in the worker process, not in the session that created the future. The `value()` call is also the point at which errors from the worker surface, which is where debugging a parallel version of working sequential code usually begins.

## The test matrix in the Makefile names every backend

The repository's Makefile is short, and one target is more informative than the rest.

```make
future.tests_built_in: future.tests/sequential future.tests/multicore future.tests/multisession future.tests/cluster

future.tests: future.tests_built_in
```

The built-in test set runs the test plan against four backends: sequential, multicore, multisession and cluster. There are additional targets for the extension backends, with `future.tests_future.mirai` covering `future.mirai::mirai_multisession` and `future.mirai::mirai_cluster`, `future.tests_future.callr` covering `future.callr::callr`, and `future.tests_future.batchtools` covering `future.batchtools::batchtools_local`.

The pattern is that the package's own test suite is expected to pass against every backend, which is a much stronger guarantee than most parallel libraries make. It also tells you the shape of the ecosystem: beyond the built-in backends, test targets exist for the `mirai`, `callr` and `batchtools` integrations, which lines up with the repository's `hpc` and `hpc-clusters` topic tags.

The rest of the Makefile is documentation and packaging work. A `vignettes` target renders the overview vignette from a `.rsp` file using the `R.rsp` package and regenerates `README.md` from it, which explains why the README contains R console transcripts. There is a `spelling` target that runs `spelling::spell_check_package()` and checks the vignettes against a word list in `inst/WORDLIST`, which is a small sign of the level of polish a CRAN package carries.

The repository also holds `revdep/` for reverse dependency checks, `pkgdown/` for the documentation site, `tests/` and `vignettes/`, and a `CITATION.cff` file. The badges show CRAN check status, R CMD check status, a top reverse-dependency check, the future test suite, and a Codecov coverage badge.

## Apache-2.0, a develop branch, and no release tags

The project is a CRAN package, and the publishing story is visible in the repository.

The default branch is `develop`, not `main` or `master`, and the CI workflows in the badges all point at that branch. The package version and the CRAN release are therefore decoupled from the source tree you see on GitHub, which is the normal arrangement for an R package that develops on GitHub and publishes to CRAN from a stable state.

The `DESCRIPTION` file, `NAMESPACE`, `NEWS.md` and `CITATION.cff` at the root are the standard CRAN package furniture, and there is a `revdep/` directory for the reverse dependency checks that a CRAN submission requires. The `Makefile` includes a `.make/Makefile`, so the build logic is split across a directory rather than living in one file.

The licence is Apache-2.0, with the text in `LICENSE.md` at the root. That is a permissive grant with an explicit patent clause, so a company can embed `future` in an internal or commercial pipeline without an obligation to publish anything, which is a licensing observation rather than legal advice, and `LICENSE.md` governs.

The repository has no GitHub releases, which is consistent with a CRAN-published R package, since the canonical distribution channel is CRAN and the version there is what `install.packages` gives you. The last push was on 2026-10-18, and the repository is not archived.

There is a Japanese README, `README_ja.md`, alongside the English one, and the topic tags cover asynchronous evaluation, promises, parallel and distributed computing, HPC and clusters, which is a good summary of what the package is for.

## Conclusion

future fits an R user with an existing script whose bottleneck is a slow per-element function, who wants to try parallel execution without restructuring the code and without leaving R. It does not fit a user who needs deterministic scheduling, who must reproduce a result exactly on a cluster, or who has a workflow built around `parallel` or `foreach` and expects the same semantics. Verify first that your function is side-effect free apart from its return value, because with `plan(multisession)` the expression runs in a separate R process and side effects on the calling session do not happen, that your globals are exported to the workers rather than captured by reference, and that the backend you choose actually exists on your platform, since the built-in set includes a sequential backend, `multicore`, `multisession` and `cluster`.

## FAQ

### How do I run an R expression in parallel with the future package?

Set a backend with `plan(multisession)` and use the future assignment operator `v %<-% { expr }`, or create an explicit future with `f <- future({ expr })` and retrieve it with `v <- value(f)`. The `futurize` package wraps this for `lapply`, `purrr::map` and `foreach` calls with a `|> futurize()` pipe.

### What is the difference between implicit and explicit futures in R?

An implicit future is created with `v %<-% { expr }` and looks like ordinary R assignment. An explicit future is created with `f <- future({ expr })` and read with `v <- value(f)`. The documentation notes that the implicit style is closest to regular R but can be deceiving with asynchronous futures, while the explicit style makes the intent clearer.

### Why is my output printed later than expected when I use futures in R?

With futures, output is relayed after the future is resolved rather than during evaluation, and the value is queried at that point. The documentation points at the 'Outputting Text' vignette for the details of this behaviour.

### Which backends does the future package support in R?

The built-in backends are sequential, multicore, multisession and cluster, all built on the `parallel` package that ships with R. The Makefile also runs test plans against extension backends including `future.mirai::mirai_multisession`, `future.mirai::mirai_cluster`, `future.callr::callr` and `future.batchtools::batchtools_local`.

### Does future work on Windows and what licence does it use?

The package is cross-platform and its built-in backends build on the `parallel` package that is part of R itself, with no additional setup required. The licence is Apache-2.0, recorded in `LICENSE.md` at the repository root.

## Sources

- [futureverse/future on GitHub](https://github.com/futureverse/future)
- [Issues](https://github.com/futureverse/future/issues)
- [License: Apache-2.0](https://github.com/futureverse/future/blob/develop/LICENSE)
- [Project website](https://future.futureverse.org)
- [README](https://github.com/futureverse/future/blob/develop/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/futureverse-future
