diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b92e50b4..27f03187 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,9 +4,13 @@ on: pull_request: push: branches: + - master - staging - trying +env: + RUSTFLAGS: -Dwarnings + jobs: build_and_test: name: Build and test @@ -14,7 +18,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - rust: [nightly] + rust: [nightly, beta, stable] steps: - uses: actions/checkout@master @@ -29,19 +33,37 @@ jobs: uses: actions-rs/cargo@v1 with: command: check - args: --all --benches --bins --examples --tests + args: --all --bins --tests - name: check unstable uses: actions-rs/cargo@v1 with: command: check - args: --features unstable --all --benches --bins --examples --tests + args: --features unstable --all --bins --examples --tests + - name: check bench + uses: actions-rs/cargo@v1 + if: matrix.rust == 'nightly' + with: + command: check + args: --benches + + - name: check std only + uses: actions-rs/cargo@v1 + with: + command: check + args: --no-default-features --features std + + - name: check attributes + uses: actions-rs/cargo@v1 + with: + command: check + args: --features attributes - name: tests uses: actions-rs/cargo@v1 with: command: test - args: --all --doc --features unstable + args: --all --features unstable attributes check_fmt_and_docs: name: Checking fmt and docs @@ -49,15 +71,12 @@ jobs: steps: - uses: actions/checkout@master - - id: component - uses: actions-rs/components-nightly@v1 - with: - component: rustfmt - - uses: actions-rs/toolchain@v1 with: - toolchain: ${{ steps.component.outputs.toolchain }} + profile: minimal + toolchain: nightly override: true + components: rustfmt - name: setup run: | @@ -74,20 +93,14 @@ jobs: - name: Docs run: cargo doc --features docs - clippy_check: - name: Clippy check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - id: component - uses: actions-rs/components-nightly@v1 - with: - component: clippy - - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ steps.component.outputs.toolchain }} - override: true - - run: rustup component add clippy - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} + # clippy_check: + # name: Clippy check + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v1 + # - name: Install rust + # run: rustup update beta && rustup default beta + # - name: Install clippy + # run: rustup component add clippy + # - name: clippy + # run: cargo clippy --all --features unstable diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bb3c836f..00000000 --- a/.travis.yml +++ /dev/null @@ -1,68 +0,0 @@ -language: rust - -env: - - RUSTFLAGS="-D warnings" - -# Cache the whole `~/.cargo` directory to keep `~/cargo/.crates.toml`. -cache: - directories: - - /home/travis/.cargo - -# Don't cache the cargo registry because it's too big. -before_cache: - - rm -rf /home/travis/.cargo/registry - - -branches: - only: - - master - - staging - - trying - -matrix: - fast_finish: true - include: - - rust: nightly - os: linux - - - rust: nightly - os: osx - osx_image: xcode9.2 - - - rust: nightly-x86_64-pc-windows-msvc - os: windows - - - name: fmt - rust: nightly - os: linux - before_script: | - if ! rustup component add rustfmt; then - target=`curl https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/rustfmt`; - echo "'rustfmt' is unavailable on the toolchain 'nightly', use the toolchain 'nightly-$target' instead"; - rustup toolchain install nightly-$target; - rustup default nightly-$target; - rustup component add rustfmt; - fi - script: - - cargo fmt --all -- --check - - - name: docs - rust: nightly - os: linux - script: - - cargo doc --features docs - - - name: book - rust: nightly - os: linux - before_script: - - test -x $HOME/.cargo/bin/mdbook || ./ci/install-mdbook.sh - - cargo build # to find 'extern crate async_std' by `mdbook test` - script: - - mdbook build docs - - mdbook test -L ./target/debug/deps docs - -script: - - cargo check --all --benches --bins --examples --tests - - cargo check --features unstable --all --benches --bins --examples --tests - - cargo test --all --doc --features unstable diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ab025a..7977be76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,333 @@ and this project adheres to [Semantic Versioning](https://book.async.rs/overview ## [Unreleased] +# [1.1.0] - 2019-11-21 + +[API Documentation](https://docs.rs/async-std/1.1.0/async-std) + +This patch introduces a faster scheduler algorithm, `Stream::throttle`, and +stabilizes `task::yield_now`. Additionally we're introducing several more stream +APIs, bringing us to almost complete parity with the standard library. + +Furthermore our `path` submodule now returns more context in errors. So if +opening a file fails, async-std will tell you *which* file was failed to open, +making it easier to write and debug programs. + +## Examples + +```rust +let start = Instant::now(); + +let mut s = stream::interval(Duration::from_millis(5)) + .throttle(Duration::from_millis(10)) + .take(2); + +s.next().await; +assert!(start.elapsed().as_millis() >= 5); + +s.next().await; +assert!(start.elapsed().as_millis() >= 15); + +s.next().await; +assert!(start.elapsed().as_millis() >= 25); +``` + +## Added + +- Added `Stream::throttle` as "unstable". +- Added `Stream::count` as "unstable". +- Added `Stream::max` as "unstable". +- Added `Stream::successors` as "unstable". +- Added `Stream::by_ref` as "unstable". +- Added `Stream::partition` as "unstable". +- Added contextual errors to the `path` submodule. +- Added `os::windows::symlink_dir` as "unstable". +- Added `os::windows::symlink_file` as "unstable". +- Stabilized `task::yield_now`. + +## Fixes + +- We now ignore seek errors when rolling back failed `read` calls on `File`. +- Fixed a bug where `Stream::max_by_key` was returning the wrong result. +- Fixed a bug where `Stream::min_by_key` was returning the wrong result. + +## Changed + +- Applied various fixes to the tutorial. +- Fixed an issue with Clippy. +- Optimized an internal code generation macro, improving compilation speeds. +- Removed an `Unpin` bound from `stream::Once`. +- Removed various extra internal uses of `pin_mut!`. +- Simplified `Stream::any` and `Stream::all`'s internals. +- The `surf` example is now enabled again. +- Tweaked some streams internals. +- Updated `futures-timer` to 2.0.0, improving compilation speed. +- Upgraded `async-macros` to 2.0.0. +- `Stream::merge` now uses randomized ordering to reduce overall latency. +- The scheduler is now more efficient by keeping a slot for the next task to + run. This is similar to Go's scheduler, and Tokio's scheduler. +- Fixed the documentation of the `channel` types to link back to the `channel` + function. + +# [1.0.1] - 2019-11-12 + +[API Documentation](https://docs.rs/async-std/1.0.1/async-std) + +We were seeing a regression in our fs performance, caused by too many +long-running tasks. This patch fixes that regression by being more proactive +about closing down idle threads. + +## Changes + +- Improved thread startup/shutdown algorithm in `task::spawn_blocking`. +- Fixed a typo in the tutorial. + +# [1.0.0] - 2019-11-11 + +[API Documentation](https://docs.rs/async-std/1.0.0/async-std) + +This release marks the `1.0.0` release of async-std; a major milestone for our +development. This release itself mostly includes quality of life improvements +for all of modules, including more consistent API bounds for a lot of our +submodules. + +The biggest change is that we're now using the full semver range, +`major.minor.patch`, and any breaking changes to our "stable" APIs will require +an update of the `major` number. + +We're excited we've hit this milestone together with you all. Thank you! + +## Added + +- Added `Future::join` as "unstable", replacing `future::join!`. +- Added `Future::try_join` as "unstable", replacing `future::try_join!`. +- Enabled `stable` and `beta` channel testing on CI. +- Implemented `FromIterator` and `Extend` for `PathBuf`. +- Implemented `FromStream` for `PathBuf`. +- Loosened the trait bounds of `io::copy` on "unstable". + +## Changed + +- Added a `Sync` bound to `RwLock`, resolving a memory safety issue. +- Fixed a bug in `Stream::take_while` where it could continue after it should've + ended. +- Fixed a bug where our `attributes` Cargo feature wasn't working as intended. +- Improved documentation of `Stream::merge`, documenting ordering guarantees. +- Update doc imports in examples to prefer async-std's types. +- Various quality of life improvements to the `future` submodule. +- Various quality of life improvements to the `path` submodule. +- Various quality of life improvements to the `stream` submodule. + +## Removed + +- Removed `future::join!` in favor of `Future::join`. +- Removed `future::try_join!` in favor of `Future::try_join`. + +# [0.99.12] - 2019-11-07 + +[API Documentation](https://docs.rs/async-std/0.99.12/async-std) + +This patch upgrades us to `futures` 0.3, support for `async/await` on Rust +Stable, performance improvements, and brand new module-level documentation. + +## Added + +- Added `Future::flatten` as "unstable". +- Added `Future::race` as "unstable" (replaces `future::select!`). +- Added `Future::try_race` as "unstable" (replaces `future::try_select!`). +- Added `Stderr::lock` as "unstable". +- Added `Stdin::lock` as "unstable". +- Added `Stdout::lock` as "unstable". +- Added `Stream::copied` as "unstable". +- Added `Stream::eq` as "unstable". +- Added `Stream::max_by_key` as "unstable". +- Added `Stream::min` as "unstable". +- Added `Stream::ne` as "unstable". +- Added `Stream::position` as "unstable". +- Added `StreamExt` and `FutureExt` as enumerable in the `prelude`. +- Added `TcpListener` and `TcpStream` integration tests. +- Added `stream::from_iter`. +- Added `sync::WakerSet` for internal use. +- Added an example to handle both `IP v4` and `IP v6` connections. +- Added the `default` Cargo feature. +- Added the `attributes` Cargo feature. +- Added the `std` Cargo feature. + +## Changed + +- Fixed a bug in the blocking threadpool where it didn't spawn more than one thread. +- Fixed a bug with `Stream::merge` where sometimes it ended too soon. +- Fixed a bug with our GitHub actions setup. +- Fixed an issue where our channels could spuriously deadlock. +- Refactored the `task` module. +- Removed a deprecated GitHub action. +- Replaced `futures-preview` with `futures`. +- Replaced `lazy_static` with `once_cell`. +- Replaced all uses of `VecDequeue` in the examples with `stream::from_iter`. +- Simplified `sync::RwLock` using the internal `sync::WakerSet` type. +- Updated the `path` submodule documentation to match std. +- Updated the mod-level documentation to match std. + +## Removed + +- Removed `future::select!` (replaced by `Future::race`). +- Removed `future::try_select!` (replaced by `Future::try_race`). + +# [0.99.11] - 2019-10-29 + +This patch introduces `async_std::sync::channel`, a novel asynchronous port of +the ultra-fast Crossbeam channels. This has been one of the most anticipated +features for async-std, and we're excited to be providing a first version of +this! + +In addition to channels, this patch has the regular list of new methods, types, +and doc fixes. + +## Examples + +__Send and receive items from a channel__ +```rust +// Create a bounded channel with a max-size of 1 +let (s, r) = channel(1); + +// This call returns immediately because there is enough space in the channel. +s.send(1).await; + +task::spawn(async move { + // This call blocks the current task because the channel is full. + // It will be able to complete only after the first message is received. + s.send(2).await; +}); + +// Receive items from the channel +task::sleep(Duration::from_secs(1)).await; +assert_eq!(r.recv().await, Some(1)); +assert_eq!(r.recv().await, Some(2)); +``` + +## Added +- Added `Future::delay` as "unstable" +- Added `Stream::flat_map` as "unstable" +- Added `Stream::flatten` as "unstable" +- Added `Stream::product` as "unstable" +- Added `Stream::sum` as "unstable" +- Added `Stream::min_by_key` +- Added `Stream::max_by` +- Added `Stream::timeout` as "unstable" +- Added `sync::channel` as "unstable". +- Added doc links from instantiated structs to the methods that create them. +- Implemented `Extend` + `FromStream` for `PathBuf`. + +## Changed +- Fixed an issue with `block_on` so it works even when nested. +- Fixed issues with our Clippy check on CI. +- Replaced our uses of `cfg_if` with our own macros, simplifying the codebase. +- Updated the homepage link in `Cargo.toml` to point to [async.rs](https://async.rs). +- Updated the module-level documentation for `stream` and `sync`. +- Various typos and grammar fixes. +- Removed redundant file flushes, improving the performance of `File` operations + +## Removed +Nothing was removed in this release. + +# [0.99.10] - 2019-10-16 + +This patch stabilizes several core concurrency macros, introduces async versions +of `Path` and `PathBuf`, and adds almost 100 other commits. + +## Examples + +__Asynchronously read directories from the filesystem__ +```rust +use async_std::fs; +use async_std::path::Path; +use async_std::prelude::*; + +let path = Path::new("/laputa"); +let mut dir = fs::read_dir(&path).await.unwrap(); +while let Some(entry) = dir.next().await { + if let Ok(entry) = entry { + println!("{:?}", entry.path()); + } +} +``` + +__Cooperatively reschedule the current task on the executor__ +```rust +use async_std::prelude::*; +use async_std::task; + +task::spawn(async { + let x = fibonnacci(1000); // Do expensive work + task::yield_now().await; // Allow other tasks to run + x + fibonnacci(100) // Do more work +}) +``` + +__Create an interval stream__ +```rust +use async_std::prelude::*; +use async_std::stream; +use std::time::Duration; + +let mut interval = stream::interval(Duration::from_secs(4)); +while let Some(_) = interval.next().await { + println!("prints every four seconds"); +} +``` + +## Added + +- Added `FutureExt` to the `prelude`, allowing us to extend `Future` +- Added `Stream::cmp` +- Added `Stream::ge` +- Added `Stream::last` +- Added `Stream::le` +- Added `Stream::lt` +- Added `Stream::merge` as "unstable", replacing `stream::join!` +- Added `Stream::partial_cmp` +- Added `Stream::take_while` +- Added `Stream::try_fold` +- Added `future::IntoFuture` as "unstable" +- Added `io::BufRead::split` +- Added `io::Write::write_fmt` +- Added `print!`, `println!`, `eprint!`, `eprintln!` macros as "unstable" +- Added `process` as "unstable", re-exporting std types only for now +- Added `std::net` re-exports to the `net` submodule +- Added `std::path::PathBuf` with all associated methods +- Added `std::path::Path` with all associated methods +- Added `stream::ExactSizeStream` as "unstable" +- Added `stream::FusedStream` as "unstable" +- Added `stream::Product` +- Added `stream::Sum` +- Added `stream::from_fn` +- Added `stream::interval` as "unstable" +- Added `stream::repeat_with` +- Added `task::spawn_blocking` as "unstable", replacing `task::blocking` +- Added `task::yield_now` +- Added `write!` and `writeln!` macros as "unstable" +- Stabilized `future::join!` and `future::try_join!` +- Stabilized `future::timeout` +- Stabilized `path` +- Stabilized `task::ready!` + +## Changed + +- Fixed `BufWriter::into_inner` so it calls `flush` before yielding +- Refactored `io::BufWriter` internals +- Refactored `net::ToSocketAddrs` internals +- Removed Travis CI entirely +- Rewrote the README.md +- Stabilized `io::Cursor` +- Switched bors over to use GitHub actions +- Updated the `io` documentation to match std's `io` docs +- Updated the `task` documentation to match std's `thread` docs + +## Removed + +- Removed the "unstable" `stream::join!` in favor of `Stream::merge` +- Removed the "unstable" `task::blocking` in favor of `task::spawn_blocking` + # [0.99.9] - 2019-10-08 This patch upgrades our `futures-rs` version, allowing us to build on the 1.39 @@ -183,7 +510,13 @@ task::blocking(async { - Initial beta release -[Unreleased]: https://github.com/async-rs/async-std/compare/v0.99.9...HEAD +[Unreleased]: https://github.com/async-rs/async-std/compare/v1.1.0...HEAD +[1.1.0]: https://github.com/async-rs/async-std/compare/v1.0.1...v1.1.0 +[1.0.1]: https://github.com/async-rs/async-std/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/async-rs/async-std/compare/v0.99.12...v1.0.0 +[0.99.12]: https://github.com/async-rs/async-std/compare/v0.99.11...v0.99.12 +[0.99.11]: https://github.com/async-rs/async-std/compare/v0.99.10...v0.99.11 +[0.99.10]: https://github.com/async-rs/async-std/compare/v0.99.9...v0.99.10 [0.99.9]: https://github.com/async-rs/async-std/compare/v0.99.8...v0.99.9 [0.99.8]: https://github.com/async-rs/async-std/compare/v0.99.7...v0.99.8 [0.99.7]: https://github.com/async-rs/async-std/compare/v0.99.6...v0.99.7 diff --git a/Cargo.toml b/Cargo.toml index 0c652988..9df05660 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-std" -version = "0.99.9" +version = "1.1.0" authors = [ "Stjepan Glavina ", "Yoshua Wuyts ", @@ -9,7 +9,7 @@ authors = [ edition = "2018" license = "Apache-2.0/MIT" repository = "https://github.com/async-rs/async-std" -homepage = "https://github.com/async-rs/async-std" +homepage = "https://async.rs" documentation = "https://docs.rs/async-std" description = "Async version of the Rust standard library" keywords = ["async", "await", "future", "std", "task"] @@ -21,35 +21,67 @@ features = ["docs"] rustdoc-args = ["--cfg", "feature=\"docs\""] [features] -docs = ["broadcaster"] -unstable = ["broadcaster"] +default = [ + "std", + "async-task", + "crossbeam-channel", + "crossbeam-deque", + "futures-timer", + "kv-log-macro", + "log", + "mio", + "mio-uds", + "num_cpus", + "pin-project-lite", +] +docs = ["attributes", "unstable"] +unstable = ["default", "broadcaster"] +attributes = ["async-attributes"] +std = [ + "async-macros", + "crossbeam-utils", + "futures-core", + "futures-io", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", +] [dependencies] -async-macros = "1.0.0" -async-task = "1.0.0" -cfg-if = "0.1.9" -crossbeam-channel = "0.3.9" -crossbeam-deque = "0.7.1" -futures-core-preview = "=0.3.0-alpha.19" -futures-io-preview = "=0.3.0-alpha.19" -futures-timer = "0.4.0" -lazy_static = "1.4.0" -log = { version = "0.4.8", features = ["kv_unstable"] } -memchr = "2.2.1" -mio = "0.6.19" -mio-uds = "0.6.7" -num_cpus = "1.10.1" -pin-utils = "0.1.0-alpha.4" -slab = "0.4.2" -kv-log-macro = "1.0.4" +async-attributes = { version = "1.1.1", optional = true } +async-macros = { version = "2.0.0", optional = true } +async-task = { version = "1.0.0", optional = true } broadcaster = { version = "0.2.6", optional = true, default-features = false, features = ["default-channels"] } +crossbeam-channel = { version = "0.4.0", optional = true } +crossbeam-deque = { version = "0.7.2", optional = true } +crossbeam-utils = { version = "0.7.0", optional = true } +futures-core = { version = "0.3.1", optional = true } +futures-io = { version = "0.3.1", optional = true } +futures-timer = { version = "2.0.2", optional = true } +kv-log-macro = { version = "1.0.4", optional = true } +log = { version = "0.4.8", features = ["kv_unstable"], optional = true } +memchr = { version = "2.2.1", optional = true } +mio = { version = "0.6.19", optional = true } +mio-uds = { version = "0.6.7", optional = true } +num_cpus = { version = "1.11.1", optional = true } +once_cell = { version = "1.2.0", optional = true } +pin-project-lite = { version = "0.1.1", optional = true } +pin-utils = { version = "0.1.0-alpha.4", optional = true } +slab = { version = "0.4.2", optional = true } [dev-dependencies] -femme = "1.2.0" -# surf = "1.0.2" +femme = "1.3.0" +rand = "0.7.2" +surf = "1.0.3" tempdir = "0.3.7" -futures-preview = { version = "=0.3.0-alpha.19", features = ["async-await"] } +futures = "0.3.1" + +[[test]] +name = "stream" +required-features = ["unstable"] -# These are used by the book for examples -futures-channel-preview = "=0.3.0-alpha.19" -futures-util-preview = "=0.3.0-alpha.19" +[[example]] +name = "tcp-ipv4-and-6-echo" +required-features = ["unstable"] diff --git a/README.md b/README.md index ae129aa1..34ebe6df 100644 --- a/README.md +++ b/README.md @@ -1,142 +1,139 @@ -# Async version of the Rust standard library - -[![Build Status](https://travis-ci.com/async-rs/async-std.svg?branch=master)](https://travis-ci.com/async-rs/async-std) -[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/async-rs/async-std) -[![Cargo](https://img.shields.io/crates/v/async-std.svg)](https://crates.io/crates/async-std) -[![Documentation](https://docs.rs/async-std/badge.svg)](https://docs.rs/async-std) -[![chat](https://img.shields.io/discord/598880689856970762.svg?logo=discord)](https://discord.gg/JvZeVNe) - -This crate provides an async version of [`std`]. It provides all the interfaces you -are used to, but in an async version and ready for Rust's `async`/`await` syntax. +

async-std

+
+ + Async version of the Rust standard library + +
+ +
+ +
+ + + Crates.io version + + + + Download + + + + docs.rs docs + + + + chat + +
+ +
+

+ + API Docs + + | + + Book + + | + + Releases + + | + + Contributing + +

+
+ +
+ +This crate provides an async version of [`std`]. It provides all the interfaces +you are used to, but in an async version and ready for Rust's `async`/`await` +syntax. [`std`]: https://doc.rust-lang.org/std/index.html -## Documentation +## Features -`async-std` comes with [extensive API documentation][docs] and a [book][book]. +- __Modern:__ Built from the ground up for `std::future` and `async/await` with + blazing fast compilation time. +- __Fast:__ Our robust allocator and threadpool designs provide ultra-high + throughput with predictably low latency. +- __Intuitive:__ Complete parity with the stdlib means you only need to learn + APIs once. +- __Clear:__ [Detailed documentation][docs] and [accessible guides][book] mean + using async Rust was never easier. [docs]: https://docs.rs/async-std [book]: https://book.async.rs -## Quickstart - -Add the following lines to your `Cargo.toml`: - -```toml -[dependencies] -async-std = "0.99" -``` - -Or use [cargo add][cargo-add] if you have it installed: - -```sh -$ cargo add async-std -``` - -[cargo-add]: https://github.com/killercup/cargo-edit - -## Hello world - -```rust -use async_std::task; +## Examples -fn main() { - task::block_on(async { - println!("Hello, world!"); - }) -} -``` - -## Low-Friction Sockets with Built-In Timeouts +All examples require the [`"attributes"` feature] to be enabled. This feature +is not enabled by default because it significantly impacts compile times. See +[`task::block_on`] for an alternative way to start executing tasks. ```rust -use std::time::Duration; - -use async_std::{ - prelude::*, - task, - io, - net::TcpStream, -}; - -async fn get() -> io::Result> { - let mut stream = TcpStream::connect("example.com:80").await?; - stream.write_all(b"GET /index.html HTTP/1.0\r\n\r\n").await?; - - let mut buf = vec![]; - - io::timeout(Duration::from_secs(5), async { - stream.read_to_end(&mut buf).await?; - Ok(buf) - }).await +async fn say_hello() { + println!("Hello, world!"); } -fn main() { - task::block_on(async { - let raw_response = get().await.expect("request"); - let response = String::from_utf8(raw_response) - .expect("utf8 conversion"); - println!("received: {}", response); - }); +#[async_std::main] +async fn main() { + say_hello().await; } ``` -## Features - -`async-std` is strongly commited to following semver. This means your code won't -break unless _you_ decide to upgrade. - -However every now and then we come up with something that we think will work -_great_ for `async-std`, and we want to provide a sneak-peek so you can try it -out. This is what we call _"unstable"_ features. You can try out the unstable -features by enabling the `unstable` feature in your `Cargo.toml` file: - -```toml -[dependencies.async-std] -version = "0.99" -features = ["unstable"] -``` - -Just be careful when using these features, as they may change between -versions. +More examples, including networking and file access, can be found in our +[`examples`] directory and in our [documentation]. -## Take a look around +[`examples`]: https://github.com/async-rs/async-std/tree/master/examples +[documentation]: https://docs.rs/async-std#examples +[`task::block_on`]: task/fn.block_on.html +[`"attributes"` feature]: https://docs.rs/async-std/#features -Clone the repo: +## Philosophy -``` -git clone git@github.com:async-rs/async-std.git && cd async-std -``` +We believe Async Rust should be as easy to pick up as Sync Rust. We also believe +that the best API is the one you already know. And finally, we believe that +providing an asynchronous counterpart to the standard library is the best way +stdlib provides a reliable basis for both performance and productivity. -Generate docs: +Async-std is the embodiment of that vision. It combines single-allocation task +creation, with an adaptive lock-free executor, threadpool and network driver to +create a smooth system that processes work at a high pace with low latency, +using Rust's familiar stdlib API. -``` -cargo +nightly doc --features docs --open -``` +## Installation -Check out the [examples](examples). To run an example: +With [cargo add][cargo-add] installed run: +```sh +$ cargo add async-std ``` -cargo +nightly run --example hello-world -``` - -## Contributing -See [our contribution document][contribution]. +We also provide a set of "unstable" features with async-std. See the [features +documentation] on how to enable them. -[contribution]: https://async.rs/contribute +[cargo-add]: https://github.com/killercup/cargo-edit +[features documentation]: https://docs.rs/async-std/#features ## License -Licensed under either of - - * Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. + +Licensed under either of Apache License, Version +2.0 or MIT license at your option. + -#### Contribution +
+ Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in the work by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. +for inclusion in this crate by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. + diff --git a/benches/mutex.rs b/benches/mutex.rs new file mode 100644 index 00000000..4f1910a6 --- /dev/null +++ b/benches/mutex.rs @@ -0,0 +1,40 @@ +#![feature(test)] + +extern crate test; + +use async_std::sync::{Arc, Mutex}; +use async_std::task; +use test::Bencher; + +#[bench] +fn create(b: &mut Bencher) { + b.iter(|| Mutex::new(())); +} + +#[bench] +fn contention(b: &mut Bencher) { + b.iter(|| task::block_on(run(10, 1000))); +} + +#[bench] +fn no_contention(b: &mut Bencher) { + b.iter(|| task::block_on(run(1, 10000))); +} + +async fn run(task: usize, iter: usize) { + let m = Arc::new(Mutex::new(())); + let mut tasks = Vec::new(); + + for _ in 0..task { + let m = m.clone(); + tasks.push(task::spawn(async move { + for _ in 0..iter { + let _ = m.lock().await; + } + })); + } + + for t in tasks { + t.await; + } +} diff --git a/benches/task.rs b/benches/task.rs new file mode 100644 index 00000000..b3144713 --- /dev/null +++ b/benches/task.rs @@ -0,0 +1,11 @@ +#![feature(test)] + +extern crate test; + +use async_std::task; +use test::Bencher; + +#[bench] +fn block_on(b: &mut Bencher) { + b.iter(|| task::block_on(async {})); +} diff --git a/docs/src/concepts/futures.md b/docs/src/concepts/futures.md index 67db720c..7d9cc636 100644 --- a/docs/src/concepts/futures.md +++ b/docs/src/concepts/futures.md @@ -24,11 +24,7 @@ To sum up: Rust gives us the ability to safely abstract over important propertie ## An easy view of computation -While computation is a subject to write a whole [book](https://computationbook.com/) about, a very simplified view suffices for us: - -- computation is a sequence of composable operations -- they can branch based on a decision -- they either run to succession and yield a result, or they can yield an error +While computation is a subject to write a whole [book](https://computationbook.com/) about, a very simplified view suffices for us: A sequence of composable operations which can branch based on a decision, run to succession and yield a result or yield an error ## Deferring computation @@ -136,11 +132,11 @@ When executing 2 or more of these functions at the same time, our runtime system ## Conclusion -Working from values, we searched for something that expresses *working towards a value available sometime later*. From there, we talked about the concept of polling. +Working from values, we searched for something that expresses *working towards a value available later*. From there, we talked about the concept of polling. A `Future` is any data type that does not represent a value, but the ability to *produce a value at some point in the future*. Implementations of this are very varied and detailed depending on use-case, but the interface is simple. -Next, we will introduce you to `tasks`, which we need to actually *run* Futures. +Next, we will introduce you to `tasks`, which we will use to actually *run* Futures. [^1]: Two parties reading while it is guaranteed that no one is writing is always safe. diff --git a/docs/src/concepts/tasks.md b/docs/src/concepts/tasks.md index d4037a3b..2142cac4 100644 --- a/docs/src/concepts/tasks.md +++ b/docs/src/concepts/tasks.md @@ -80,7 +80,7 @@ Tasks in `async_std` are one of the core abstractions. Much like Rust's `thread` ## Blocking -`Task`s are assumed to run _concurrently_, potentially by sharing a thread of execution. This means that operations blocking an _operating system thread_, such as `std::thread::sleep` or io function from Rust's `std` library will _stop execution of all tasks sharing this thread_. Other libraries (such as database drivers) have similar behaviour. Note that _blocking the current thread_ is not in and by itself bad behaviour, just something that does not mix well with the concurrent execution model of `async-std`. Essentially, never do this: +`Task`s are assumed to run _concurrently_, potentially by sharing a thread of execution. This means that operations blocking an _operating system thread_, such as `std::thread::sleep` or io function from Rust's `std` library will _stop execution of all tasks sharing this thread_. Other libraries (such as database drivers) have similar behaviour. Note that _blocking the current thread_ is not in and of itself bad behaviour, just something that does not mix well with the concurrent execution model of `async-std`. Essentially, never do this: ```rust,edition2018 # extern crate async_std; diff --git a/docs/src/overview/async-std.md b/docs/src/overview/async-std.md index 2b59ffb0..0086599f 100644 --- a/docs/src/overview/async-std.md +++ b/docs/src/overview/async-std.md @@ -4,4 +4,4 @@ `async-std` provides an interface to all important primitives: filesystem operations, network operations and concurrency basics like timers. It also exposes a `task` in a model similar to the `thread` module found in the Rust standard lib. But it does not only include I/O primitives, but also `async/await` compatible versions of primitives like `Mutex`. -[organization]: https://github.com/async-rs/async-std +[organization]: https://github.com/async-rs diff --git a/docs/src/overview/stability-guarantees.md b/docs/src/overview/stability-guarantees.md index 84bb68d7..8c14e20f 100644 --- a/docs/src/overview/stability-guarantees.md +++ b/docs/src/overview/stability-guarantees.md @@ -31,7 +31,7 @@ In general, this crate will be conservative with respect to the minimum supporte ## Security fixes -Security fixes will be applied to _all_ minor branches of this library in all _supported_ major revisions. This policy might change in the future, in which case we give at least _3 month_ of ahead notice. +Security fixes will be applied to _all_ minor branches of this library in all _supported_ major revisions. This policy might change in the future, in which case we give a notice at least _3 months_ ahead. ## Credits diff --git a/docs/src/overview/std-and-library-futures.md b/docs/src/overview/std-and-library-futures.md index 5c5f96f5..9b4801ed 100644 --- a/docs/src/overview/std-and-library-futures.md +++ b/docs/src/overview/std-and-library-futures.md @@ -4,11 +4,11 @@ Rust has two kinds of types commonly referred to as `Future`: - the first is `std::future::Future` from Rust’s [standard library](https://doc.rust-lang.org/std/future/trait.Future.html). -- the second is `futures::future::Future` from the [futures-rs crate](https://docs.rs/futures-preview/0.3.0-alpha.17/futures/prelude/trait.Future.html), currently released as `futures-preview`. +- the second is `futures::future::Future` from the [futures-rs crate](https://docs.rs/futures/0.3/futures/prelude/trait.Future.html). -The future defined in the [futures-rs](https://docs.rs/futures-preview/0.3.0-alpha.17/futures/prelude/trait.Future.html) crate was the original implementation of the type. To enable the `async/await` syntax, the core Future trait was moved into Rust’s standard library and became `std::future::Future`. In some sense, the `std::future::Future` can be seen as a minimal subset of `futures::future::Future`. +The future defined in the [futures-rs](https://docs.rs/futures/0.3/futures/prelude/trait.Future.html) crate was the original implementation of the type. To enable the `async/await` syntax, the core Future trait was moved into Rust’s standard library and became `std::future::Future`. In some sense, the `std::future::Future` can be seen as a minimal subset of `futures::future::Future`. -It is critical to understand the difference between `std::future::Future` and `futures::future::Future`, and the approach that `async-std` takes towards them. In itself, `std::future::Future` is not something you want to interact with as a user—except by calling `.await` on it. The inner workings of `std::future::Future` are mostly of interest to people implementing `Future`. Make no mistake—this is very useful! Most of the functionality that used to be defined on `Future` itself has been moved to an extension trait called [`FuturesExt`](https://docs.rs/futures-preview/0.3.0-alpha.17/futures/future/trait.FutureExt.html). From this information, you might be able to infer that the `futures` library serves as an extension to the core Rust async features. +It is critical to understand the difference between `std::future::Future` and `futures::future::Future`, and the approach that `async-std` takes towards them. In itself, `std::future::Future` is not something you want to interact with as a user—except by calling `.await` on it. The inner workings of `std::future::Future` are mostly of interest to people implementing `Future`. Make no mistake—this is very useful! Most of the functionality that used to be defined on `Future` itself has been moved to an extension trait called [`FuturesExt`](https://docs.rs/futures/0.3/futures/future/trait.FutureExt.html). From this information, you might be able to infer that the `futures` library serves as an extension to the core Rust async features. In the same tradition as `futures`, `async-std` re-exports the core `std::future::Future` type. You can actively opt into the extensions provided by the `futures-preview` crate by adding it to your `Cargo.toml` and importing `FuturesExt`. diff --git a/docs/src/tutorial/all_together.md b/docs/src/tutorial/all_together.md index dcc06616..8bb01e94 100644 --- a/docs/src/tutorial/all_together.md +++ b/docs/src/tutorial/all_together.md @@ -4,16 +4,15 @@ At this point, we only need to start the broker to get a fully-functioning (in t ```rust,edition2018 # extern crate async_std; -# extern crate futures_channel; -# extern crate futures_util; +# extern crate futures; use async_std::{ - io::{self, BufReader}, + io::BufReader, net::{TcpListener, TcpStream, ToSocketAddrs}, prelude::*, task, }; -use futures_channel::mpsc; -use futures_util::SinkExt; +use futures::channel::mpsc; +use futures::sink::SinkExt; use std::{ collections::hash_map::{HashMap, Entry}, sync::Arc, diff --git a/docs/src/tutorial/clean_shutdown.md b/docs/src/tutorial/clean_shutdown.md index 234067a3..bd112c93 100644 --- a/docs/src/tutorial/clean_shutdown.md +++ b/docs/src/tutorial/clean_shutdown.md @@ -22,16 +22,15 @@ Let's add waiting to the server: ```rust,edition2018 # extern crate async_std; -# extern crate futures_channel; -# extern crate futures_util; +# extern crate futures; # use async_std::{ # io::{self, BufReader}, # net::{TcpListener, TcpStream, ToSocketAddrs}, # prelude::*, # task, # }; -# use futures_channel::mpsc; -# use futures_util::SinkExt; +# use futures::channel::mpsc; +# use futures::sink::SinkExt; # use std::{ # collections::hash_map::{HashMap, Entry}, # sync::Arc, @@ -156,16 +155,15 @@ And to the broker: ```rust,edition2018 # extern crate async_std; -# extern crate futures_channel; -# extern crate futures_util; +# extern crate futures; # use async_std::{ # io::{self, BufReader}, # net::{TcpListener, TcpStream, ToSocketAddrs}, # prelude::*, # task, # }; -# use futures_channel::mpsc; -# use futures_util::SinkExt; +# use futures::channel::mpsc; +# use futures::sink::SinkExt; # use std::{ # collections::hash_map::{HashMap, Entry}, # sync::Arc, diff --git a/docs/src/tutorial/connecting_readers_and_writers.md b/docs/src/tutorial/connecting_readers_and_writers.md index c1ebe9b8..921cf90c 100644 --- a/docs/src/tutorial/connecting_readers_and_writers.md +++ b/docs/src/tutorial/connecting_readers_and_writers.md @@ -2,25 +2,24 @@ ## Connecting Readers and Writers So how do we make sure that messages read in `connection_loop` flow into the relevant `connection_writer_loop`? -We should somehow maintain an `peers: HashMap>` map which allows a client to find destination channels. +We should somehow maintain a `peers: HashMap>` map which allows a client to find destination channels. However, this map would be a bit of shared mutable state, so we'll have to wrap an `RwLock` over it and answer tough questions of what should happen if the client joins at the same moment as it receives a message. One trick to make reasoning about state simpler comes from the actor model. -We can create a dedicated broker tasks which owns the `peers` map and communicates with other tasks by channels. -By hiding `peers` inside such an "actor" task, we remove the need for mutxes and also make serialization point explicit. +We can create a dedicated broker task which owns the `peers` map and communicates with other tasks using channels. +By hiding `peers` inside such an "actor" task, we remove the need for mutexes and also make the serialization point explicit. The order of events "Bob sends message to Alice" and "Alice joins" is determined by the order of the corresponding events in the broker's event queue. ```rust,edition2018 # extern crate async_std; -# extern crate futures_channel; -# extern crate futures_util; +# extern crate futures; # use async_std::{ # net::TcpStream, # prelude::*, # task, # }; -# use futures_channel::mpsc; -# use futures_util::sink::SinkExt; +# use futures::channel::mpsc; +# use futures::sink::SinkExt; # use std::sync::Arc; # # type Result = std::result::Result>; @@ -93,9 +92,9 @@ async fn broker_loop(mut events: Receiver) -> Result<()> { } ``` -1. Broker should handle two types of events: a message or an arrival of a new peer. -2. Internal state of the broker is a `HashMap`. +1. The broker task should handle two types of events: a message or an arrival of a new peer. +2. The internal state of the broker is a `HashMap`. Note how we don't need a `Mutex` here and can confidently say, at each iteration of the broker's loop, what is the current set of peers 3. To handle a message, we send it over a channel to each destination -4. To handle new peer, we first register it in the peer's map ... +4. To handle a new peer, we first register it in the peer's map ... 5. ... and then spawn a dedicated task to actually write the messages to the socket. diff --git a/docs/src/tutorial/handling_disconnection.md b/docs/src/tutorial/handling_disconnection.md index a1f51d13..9db9abd2 100644 --- a/docs/src/tutorial/handling_disconnection.md +++ b/docs/src/tutorial/handling_disconnection.md @@ -19,11 +19,10 @@ First, let's add a shutdown channel to the `connection_loop`: ```rust,edition2018 # extern crate async_std; -# extern crate futures_channel; -# extern crate futures_util; +# extern crate futures; # use async_std::net::TcpStream; -# use futures_channel::mpsc; -# use futures_util::SinkExt; +# use futures::channel::mpsc; +# use futures::sink::SinkExt; # use std::sync::Arc; # # type Result = std::result::Result>; @@ -61,8 +60,8 @@ async fn connection_loop(mut broker: Sender, stream: Arc) -> R } ``` -1. To enforce that no messages are send along the shutdown channel, we use an uninhabited type. -2. We pass the shutdown channel to the writer task +1. To enforce that no messages are sent along the shutdown channel, we use an uninhabited type. +2. We pass the shutdown channel to the writer task. 3. In the reader, we create a `_shutdown_sender` whose only purpose is to get dropped. In the `connection_writer_loop`, we now need to choose between shutdown and message channels. @@ -70,17 +69,14 @@ We use the `select` macro for this purpose: ```rust,edition2018 # extern crate async_std; -# extern crate futures_channel; -# extern crate futures_util; +# extern crate futures; # use async_std::{net::TcpStream, prelude::*}; -use futures_channel::mpsc; -use futures_util::{select, FutureExt}; +# use futures::channel::mpsc; +use futures::{select, FutureExt}; # use std::sync::Arc; - # type Receiver = mpsc::UnboundedReceiver; # type Result = std::result::Result>; # type Sender = mpsc::UnboundedSender; - # #[derive(Debug)] # enum Void {} // 1 @@ -114,7 +110,7 @@ async fn connection_writer_loop( Another problem is that between the moment we detect disconnection in `connection_writer_loop` and the moment when we actually remove the peer from the `peers` map, new messages might be pushed into the peer's channel. To not lose these messages completely, we'll return the messages channel back to the broker. -This also allows us to establish a useful invariant that the message channel strictly outlives the peer in the `peers` map, and makes the broker itself infailable. +This also allows us to establish a useful invariant that the message channel strictly outlives the peer in the `peers` map, and makes the broker itself infallible. ## Final Code @@ -122,16 +118,16 @@ The final code looks like this: ```rust,edition2018 # extern crate async_std; -# extern crate futures_channel; -# extern crate futures_util; +# extern crate futures; use async_std::{ io::BufReader, net::{TcpListener, TcpStream, ToSocketAddrs}, prelude::*, task, }; -use futures_channel::mpsc; -use futures_util::{select, FutureExt, SinkExt}; +use futures::channel::mpsc; +use futures::sink::SinkExt; +use futures::{select, FutureExt}; use std::{ collections::hash_map::{Entry, HashMap}, future::Future, @@ -161,7 +157,7 @@ async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { spawn_and_log_error(connection_loop(broker_sender.clone(), stream)); } drop(broker_sender); - broker_handle.await; + broker_handle.await?; Ok(()) } diff --git a/docs/src/tutorial/implementing_a_client.md b/docs/src/tutorial/implementing_a_client.md index 3aac67f3..fd728555 100644 --- a/docs/src/tutorial/implementing_a_client.md +++ b/docs/src/tutorial/implementing_a_client.md @@ -16,14 +16,14 @@ With async, we can just use the `select!` macro. ```rust,edition2018 # extern crate async_std; -# extern crate futures_util; +# extern crate futures; use async_std::{ io::{stdin, BufReader}, net::{TcpStream, ToSocketAddrs}, prelude::*, task, }; -use futures_util::{select, FutureExt}; +use futures::{select, FutureExt}; type Result = std::result::Result>; diff --git a/docs/src/tutorial/index.md b/docs/src/tutorial/index.md index 99ddf8eb..aee0b3f4 100644 --- a/docs/src/tutorial/index.md +++ b/docs/src/tutorial/index.md @@ -1,11 +1,14 @@ # Tutorial: Writing a chat -Nothing is as simple as a chat server, right? Not quite, chat servers -already expose you to all the fun of asynchronous programming: how -do you handle clients connecting concurrently. How do you handle them disconnecting? +Nothing is simpler than creating a chat server, right? +Not quite, chat servers expose you to all the fun of asynchronous programming: -How do you distribute the messages? +How will the server handle clients connecting concurrently? -In this tutorial, we will show you how to write one in `async-std`. +How will it handle them disconnecting? + +How will it distribute the messages? + +This tutorial explains how to write a chat server in `async-std`. You can also find the tutorial in [our repository](https://github.com/async-rs/async-std/blob/master/examples/a-chat). diff --git a/docs/src/tutorial/receiving_messages.md b/docs/src/tutorial/receiving_messages.md index 213589c0..4f705294 100644 --- a/docs/src/tutorial/receiving_messages.md +++ b/docs/src/tutorial/receiving_messages.md @@ -10,14 +10,18 @@ We need to: ```rust,edition2018 # extern crate async_std; # use async_std::{ -# io::BufReader, -# net::{TcpListener, TcpStream, ToSocketAddrs}, +# net::{TcpListener, ToSocketAddrs}, # prelude::*, # task, # }; # # type Result = std::result::Result>; # +use async_std::{ + io::BufReader, + net::TcpStream, +}; + async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { let listener = TcpListener::bind(addr).await?; let mut incoming = listener.incoming(); @@ -46,7 +50,7 @@ async fn connection_loop(stream: TcpStream) -> Result<()> { Some(idx) => (&line[..idx], line[idx + 1 ..].trim()), }; let dest: Vec = dest.split(',').map(|name| name.trim().to_string()).collect(); - let msg: String = msg.trim().to_string(); + let msg: String = msg.to_string(); } Ok(()) } @@ -130,7 +134,7 @@ So let's use a helper function for this: # }; fn spawn_and_log_error(fut: F) -> task::JoinHandle<()> where - F: Future> + Send + 'static, + F: Future> + Send + 'static, { task::spawn(async move { if let Err(e) = fut.await { diff --git a/docs/src/tutorial/sending_messages.md b/docs/src/tutorial/sending_messages.md index b381aacb..3f426d02 100644 --- a/docs/src/tutorial/sending_messages.md +++ b/docs/src/tutorial/sending_messages.md @@ -13,14 +13,13 @@ if Alice and Charley send two messages to Bob at the same time, Bob will see the ```rust,edition2018 # extern crate async_std; -# extern crate futures_channel; -# extern crate futures_util; +# extern crate futures; # use async_std::{ # net::TcpStream, # prelude::*, # }; -use futures_channel::mpsc; // 1 -use futures_util::sink::SinkExt; +use futures::channel::mpsc; // 1 +use futures::sink::SinkExt; use std::sync::Arc; # type Result = std::result::Result>; diff --git a/docs/src/tutorial/specification.md b/docs/src/tutorial/specification.md index bda45766..7b1a0167 100644 --- a/docs/src/tutorial/specification.md +++ b/docs/src/tutorial/specification.md @@ -12,7 +12,7 @@ After that, the client can send messages to other clients using the following sy login1, login2, ... loginN: message ``` -Each of the specified clients than receives a `from login: message` message. +Each of the specified clients then receives a `from login: message` message. A possible session might look like this @@ -38,18 +38,10 @@ $ cargo new a-chat $ cd a-chat ``` -At the moment `async-std` requires Rust nightly, so let's add a rustup override for convenience: - -```bash -$ rustup override add nightly -$ rustc --version -rustc 1.38.0-nightly (c4715198b 2019-08-05) -``` - Add the following lines to `Cargo.toml`: ```toml [dependencies] -futures-preview = { version = "0.3.0-alpha.19", features = [ "async-await" ] } -async-std = "0.99" +futures = "0.3.0" +async-std = "1" ``` diff --git a/examples/a-chat/main.rs b/examples/a-chat/main.rs index ced7cac2..89e5e2b6 100644 --- a/examples/a-chat/main.rs +++ b/examples/a-chat/main.rs @@ -8,6 +8,6 @@ fn main() -> Result<()> { match (args.nth(1).as_ref().map(String::as_str), args.next()) { (Some("client"), None) => client::main(), (Some("server"), None) => server::main(), - _ => Err("Usage: a-chat [client|server]")?, + _ => Err("Usage: a-chat [client|server]".into()), } } diff --git a/examples/a-chat/server.rs b/examples/a-chat/server.rs index 77ebfd1e..e049a490 100644 --- a/examples/a-chat/server.rs +++ b/examples/a-chat/server.rs @@ -45,7 +45,7 @@ async fn connection_loop(mut broker: Sender, stream: TcpStream) -> Result let mut lines = reader.lines(); let name = match lines.next().await { - None => Err("peer disconnected immediately")?, + None => return Err("peer disconnected immediately".into()), Some(line) => line?, }; let (_shutdown_sender, shutdown_receiver) = mpsc::unbounded::(); diff --git a/examples/surf-web.rs b/examples/surf-web.rs index b3101d15..df139e5b 100644 --- a/examples/surf-web.rs +++ b/examples/surf-web.rs @@ -1,6 +1,3 @@ -/* TODO: Once the next version of surf released, re-enable this example. -//! Sends an HTTP request to the Rust website. - use async_std::task; fn main() -> Result<(), surf::Exception> { @@ -18,6 +15,3 @@ fn main() -> Result<(), surf::Exception> { Ok(()) }) } -*/ - -fn main() {} diff --git a/examples/tcp-ipv4-and-6-echo.rs b/examples/tcp-ipv4-and-6-echo.rs new file mode 100644 index 00000000..aef5e15e --- /dev/null +++ b/examples/tcp-ipv4-and-6-echo.rs @@ -0,0 +1,44 @@ +//! TCP echo server, accepting connections both on both ipv4 and ipv6 sockets. +//! +//! To send messages, do: +//! +//! ```sh +//! $ nc 127.0.0.1 8080 +//! $ nc ::1 8080 +//! ``` + +use async_std::io; +use async_std::net::{TcpListener, TcpStream}; +use async_std::prelude::*; +use async_std::task; + +async fn process(stream: TcpStream) -> io::Result<()> { + println!("Accepted from: {}", stream.peer_addr()?); + + let (reader, writer) = &mut (&stream, &stream); + io::copy(reader, writer).await?; + + Ok(()) +} + +fn main() -> io::Result<()> { + task::block_on(async { + let ipv4_listener = TcpListener::bind("127.0.0.1:8080").await?; + println!("Listening on {}", ipv4_listener.local_addr()?); + let ipv6_listener = TcpListener::bind("[::1]:8080").await?; + println!("Listening on {}", ipv6_listener.local_addr()?); + + let ipv4_incoming = ipv4_listener.incoming(); + let ipv6_incoming = ipv6_listener.incoming(); + + let mut incoming = ipv4_incoming.merge(ipv6_incoming); + + while let Some(stream) = incoming.next().await { + let stream = stream?; + task::spawn(async { + process(stream).await.unwrap(); + }); + } + Ok(()) + }) +} diff --git a/rustfmt.toml b/rustfmt.toml index 1082fd88..c6d404e2 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,2 @@ version = "Two" +format_code_in_doc_comments = true diff --git a/src/collections/binary_heap/extend.rs b/src/collections/binary_heap/extend.rs index 4503fe39..439bf139 100644 --- a/src/collections/binary_heap/extend.rs +++ b/src/collections/binary_heap/extend.rs @@ -2,10 +2,10 @@ use std::collections::BinaryHeap; use std::pin::Pin; use crate::prelude::*; -use crate::stream::{Extend, IntoStream}; +use crate::stream::{self, IntoStream}; -impl Extend for BinaryHeap { - fn stream_extend<'a, S: IntoStream + 'a>( +impl stream::Extend for BinaryHeap { + fn extend<'a, S: IntoStream + 'a>( &'a mut self, stream: S, ) -> Pin + 'a>> { diff --git a/src/collections/binary_heap/from_stream.rs b/src/collections/binary_heap/from_stream.rs index c8e44e93..6851948e 100644 --- a/src/collections/binary_heap/from_stream.rs +++ b/src/collections/binary_heap/from_stream.rs @@ -1,23 +1,19 @@ use std::collections::BinaryHeap; use std::pin::Pin; -use crate::stream::{Extend, FromStream, IntoStream}; +use crate::prelude::*; +use crate::stream::{self, FromStream, IntoStream}; impl FromStream for BinaryHeap { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = BinaryHeap::new(); - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } diff --git a/src/collections/btree_map/extend.rs b/src/collections/btree_map/extend.rs index ae02c424..19d306ff 100644 --- a/src/collections/btree_map/extend.rs +++ b/src/collections/btree_map/extend.rs @@ -2,10 +2,10 @@ use std::collections::BTreeMap; use std::pin::Pin; use crate::prelude::*; -use crate::stream::{Extend, IntoStream}; +use crate::stream::{self, IntoStream}; -impl Extend<(K, V)> for BTreeMap { - fn stream_extend<'a, S: IntoStream + 'a>( +impl stream::Extend<(K, V)> for BTreeMap { + fn extend<'a, S: IntoStream + 'a>( &'a mut self, stream: S, ) -> Pin + 'a>> { diff --git a/src/collections/btree_map/from_stream.rs b/src/collections/btree_map/from_stream.rs index bd80c069..85312236 100644 --- a/src/collections/btree_map/from_stream.rs +++ b/src/collections/btree_map/from_stream.rs @@ -1,23 +1,19 @@ use std::collections::BTreeMap; use std::pin::Pin; -use crate::stream::{Extend, FromStream, IntoStream}; +use crate::prelude::*; +use crate::stream::{self, FromStream, IntoStream}; impl FromStream<(K, V)> for BTreeMap { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = BTreeMap::new(); - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } diff --git a/src/collections/btree_set/extend.rs b/src/collections/btree_set/extend.rs index ccf03378..422640b1 100644 --- a/src/collections/btree_set/extend.rs +++ b/src/collections/btree_set/extend.rs @@ -2,10 +2,10 @@ use std::collections::BTreeSet; use std::pin::Pin; use crate::prelude::*; -use crate::stream::{Extend, IntoStream}; +use crate::stream::{self, IntoStream}; -impl Extend for BTreeSet { - fn stream_extend<'a, S: IntoStream + 'a>( +impl stream::Extend for BTreeSet { + fn extend<'a, S: IntoStream + 'a>( &'a mut self, stream: S, ) -> Pin + 'a>> { diff --git a/src/collections/btree_set/from_stream.rs b/src/collections/btree_set/from_stream.rs index bd2a744b..318af9e6 100644 --- a/src/collections/btree_set/from_stream.rs +++ b/src/collections/btree_set/from_stream.rs @@ -1,23 +1,19 @@ use std::collections::BTreeSet; use std::pin::Pin; -use crate::stream::{Extend, FromStream, IntoStream}; +use crate::prelude::*; +use crate::stream::{self, FromStream, IntoStream}; impl FromStream for BTreeSet { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = BTreeSet::new(); - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } diff --git a/src/collections/hash_map/extend.rs b/src/collections/hash_map/extend.rs index c34bb9ed..0f4ce0c6 100644 --- a/src/collections/hash_map/extend.rs +++ b/src/collections/hash_map/extend.rs @@ -3,14 +3,14 @@ use std::hash::{BuildHasher, Hash}; use std::pin::Pin; use crate::prelude::*; -use crate::stream::{Extend, IntoStream}; +use crate::stream::{self, IntoStream}; -impl Extend<(K, V)> for HashMap +impl stream::Extend<(K, V)> for HashMap where K: Eq + Hash, H: BuildHasher + Default, { - fn stream_extend<'a, S: IntoStream + 'a>( + fn extend<'a, S: IntoStream + 'a>( &'a mut self, stream: S, ) -> Pin + 'a>> { diff --git a/src/collections/hash_map/from_stream.rs b/src/collections/hash_map/from_stream.rs index 2b7bbc9b..d74a7ccf 100644 --- a/src/collections/hash_map/from_stream.rs +++ b/src/collections/hash_map/from_stream.rs @@ -2,7 +2,8 @@ use std::collections::HashMap; use std::hash::{BuildHasher, Hash}; use std::pin::Pin; -use crate::stream::{Extend, FromStream, IntoStream}; +use crate::prelude::*; +use crate::stream::{self, FromStream, IntoStream}; impl FromStream<(K, V)> for HashMap where @@ -10,19 +11,14 @@ where H: BuildHasher + Default, { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = HashMap::with_hasher(Default::default()); - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } diff --git a/src/collections/hash_set/extend.rs b/src/collections/hash_set/extend.rs index 123e844e..ba872b43 100644 --- a/src/collections/hash_set/extend.rs +++ b/src/collections/hash_set/extend.rs @@ -3,14 +3,14 @@ use std::hash::{BuildHasher, Hash}; use std::pin::Pin; use crate::prelude::*; -use crate::stream::{Extend, IntoStream}; +use crate::stream::{self, IntoStream}; -impl Extend for HashSet +impl stream::Extend for HashSet where T: Eq + Hash, H: BuildHasher + Default, { - fn stream_extend<'a, S: IntoStream + 'a>( + fn extend<'a, S: IntoStream + 'a>( &'a mut self, stream: S, ) -> Pin + 'a>> { diff --git a/src/collections/hash_set/from_stream.rs b/src/collections/hash_set/from_stream.rs index 42447fef..dc5e61e3 100644 --- a/src/collections/hash_set/from_stream.rs +++ b/src/collections/hash_set/from_stream.rs @@ -2,7 +2,8 @@ use std::collections::HashSet; use std::hash::{BuildHasher, Hash}; use std::pin::Pin; -use crate::stream::{Extend, FromStream, IntoStream}; +use crate::prelude::*; +use crate::stream::{self, FromStream, IntoStream}; impl FromStream for HashSet where @@ -10,19 +11,14 @@ where H: BuildHasher + Default, { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = HashSet::with_hasher(Default::default()); - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } diff --git a/src/collections/linked_list/extend.rs b/src/collections/linked_list/extend.rs index 63a1a97c..b0dff009 100644 --- a/src/collections/linked_list/extend.rs +++ b/src/collections/linked_list/extend.rs @@ -2,10 +2,10 @@ use std::collections::LinkedList; use std::pin::Pin; use crate::prelude::*; -use crate::stream::{Extend, IntoStream}; +use crate::stream::{self, IntoStream}; -impl Extend for LinkedList { - fn stream_extend<'a, S: IntoStream + 'a>( +impl stream::Extend for LinkedList { + fn extend<'a, S: IntoStream + 'a>( &'a mut self, stream: S, ) -> Pin + 'a>> { diff --git a/src/collections/linked_list/from_stream.rs b/src/collections/linked_list/from_stream.rs index 3ffe149b..d93bbb7b 100644 --- a/src/collections/linked_list/from_stream.rs +++ b/src/collections/linked_list/from_stream.rs @@ -1,23 +1,19 @@ use std::collections::LinkedList; use std::pin::Pin; -use crate::stream::{Extend, FromStream, IntoStream}; +use crate::prelude::*; +use crate::stream::{self, FromStream, IntoStream}; impl FromStream for LinkedList { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = LinkedList::new(); - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } diff --git a/src/collections/vec_deque/extend.rs b/src/collections/vec_deque/extend.rs index 17e396ab..dd2ddce3 100644 --- a/src/collections/vec_deque/extend.rs +++ b/src/collections/vec_deque/extend.rs @@ -2,10 +2,10 @@ use std::collections::VecDeque; use std::pin::Pin; use crate::prelude::*; -use crate::stream::{Extend, IntoStream}; +use crate::stream::{self, IntoStream}; -impl Extend for VecDeque { - fn stream_extend<'a, S: IntoStream + 'a>( +impl stream::Extend for VecDeque { + fn extend<'a, S: IntoStream + 'a>( &'a mut self, stream: S, ) -> Pin + 'a>> { diff --git a/src/collections/vec_deque/from_stream.rs b/src/collections/vec_deque/from_stream.rs index 8903de0e..241bd74e 100644 --- a/src/collections/vec_deque/from_stream.rs +++ b/src/collections/vec_deque/from_stream.rs @@ -1,23 +1,19 @@ use std::collections::VecDeque; use std::pin::Pin; -use crate::stream::{Extend, FromStream, IntoStream}; +use crate::prelude::*; +use crate::stream::{self, FromStream, IntoStream}; impl FromStream for VecDeque { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = VecDeque::new(); - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } diff --git a/src/fs/canonicalize.rs b/src/fs/canonicalize.rs index c484aeeb..38a5a6b9 100644 --- a/src/fs/canonicalize.rs +++ b/src/fs/canonicalize.rs @@ -1,7 +1,7 @@ -use std::path::{Path, PathBuf}; - use crate::io; -use crate::task::blocking; +use crate::path::{Path, PathBuf}; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Returns the canonical form of a path. /// @@ -33,5 +33,10 @@ use crate::task::blocking; /// ``` pub async fn canonicalize>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::canonicalize(path) }).await + spawn_blocking(move || { + std::fs::canonicalize(&path) + .map(Into::into) + .context(|| format!("could not canonicalize `{}`", path.display())) + }) + .await } diff --git a/src/fs/copy.rs b/src/fs/copy.rs index fc3fd3e6..8fb447bb 100644 --- a/src/fs/copy.rs +++ b/src/fs/copy.rs @@ -1,7 +1,7 @@ -use std::path::Path; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Copies the contents and permissions of a file to a new location. /// @@ -42,5 +42,9 @@ use crate::task::blocking; pub async fn copy, Q: AsRef>(from: P, to: Q) -> io::Result { let from = from.as_ref().to_owned(); let to = to.as_ref().to_owned(); - blocking::spawn(async move { std::fs::copy(&from, &to) }).await + spawn_blocking(move || { + std::fs::copy(&from, &to) + .context(|| format!("could not copy `{}` to `{}`", from.display(), to.display())) + }) + .await } diff --git a/src/fs/create_dir.rs b/src/fs/create_dir.rs index aa2d5724..37923c05 100644 --- a/src/fs/create_dir.rs +++ b/src/fs/create_dir.rs @@ -1,7 +1,7 @@ -use std::path::Path; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Creates a new directory. /// @@ -35,5 +35,9 @@ use crate::task::blocking; /// ``` pub async fn create_dir>(path: P) -> io::Result<()> { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::create_dir(path) }).await + spawn_blocking(move || { + std::fs::create_dir(&path) + .context(|| format!("could not create directory `{}`", path.display())) + }) + .await } diff --git a/src/fs/create_dir_all.rs b/src/fs/create_dir_all.rs index 33176f75..753dfd49 100644 --- a/src/fs/create_dir_all.rs +++ b/src/fs/create_dir_all.rs @@ -1,7 +1,7 @@ -use std::path::Path; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Creates a new directory and all of its parents if they are missing. /// @@ -30,5 +30,9 @@ use crate::task::blocking; /// ``` pub async fn create_dir_all>(path: P) -> io::Result<()> { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::create_dir_all(path) }).await + spawn_blocking(move || { + std::fs::create_dir_all(&path) + .context(|| format!("could not create directory path `{}`", path.display())) + }) + .await } diff --git a/src/fs/dir_builder.rs b/src/fs/dir_builder.rs index 3064f7a3..9ee6b55a 100644 --- a/src/fs/dir_builder.rs +++ b/src/fs/dir_builder.rs @@ -1,10 +1,8 @@ -use std::path::Path; +use std::future::Future; -use cfg_if::cfg_if; - -use crate::future::Future; use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; /// A builder for creating directories with configurable options. /// @@ -109,26 +107,17 @@ impl DirBuilder { } let path = path.as_ref().to_owned(); - async move { blocking::spawn(async move { builder.create(path) }).await } + async move { spawn_blocking(move || builder.create(path)).await } } } -cfg_if! { - if #[cfg(feature = "docs")] { - use crate::os::unix::fs::DirBuilderExt; - } else if #[cfg(unix)] { - use std::os::unix::fs::DirBuilderExt; - } -} +cfg_unix! { + use crate::os::unix::fs::DirBuilderExt; -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(unix, feature = "docs"))] { - impl DirBuilderExt for DirBuilder { - fn mode(&mut self, mode: u32) -> &mut Self { - self.mode = Some(mode); - self - } + impl DirBuilderExt for DirBuilder { + fn mode(&mut self, mode: u32) -> &mut Self { + self.mode = Some(mode); + self } } } diff --git a/src/fs/dir_entry.rs b/src/fs/dir_entry.rs index 66b3cb7c..527fab42 100644 --- a/src/fs/dir_entry.rs +++ b/src/fs/dir_entry.rs @@ -1,13 +1,11 @@ use std::ffi::OsString; use std::fmt; -use std::path::PathBuf; use std::sync::Arc; -use cfg_if::cfg_if; - use crate::fs::{FileType, Metadata}; use crate::io; -use crate::task::blocking; +use crate::path::PathBuf; +use crate::task::spawn_blocking; /// An entry in a directory. /// @@ -50,7 +48,7 @@ impl DirEntry { /// # Ok(()) }) } /// ``` pub fn path(&self) -> PathBuf { - self.0.path() + self.0.path().into() } /// Reads the metadata for this entry. @@ -89,7 +87,7 @@ impl DirEntry { /// ``` pub async fn metadata(&self) -> io::Result { let inner = self.0.clone(); - blocking::spawn(async move { inner.metadata() }).await + spawn_blocking(move || inner.metadata()).await } /// Reads the file type for this entry. @@ -127,7 +125,7 @@ impl DirEntry { /// ``` pub async fn file_type(&self) -> io::Result { let inner = self.0.clone(); - blocking::spawn(async move { inner.file_type() }).await + spawn_blocking(move || inner.file_type()).await } /// Returns the bare name of this entry without the leading path. @@ -160,21 +158,12 @@ impl fmt::Debug for DirEntry { } } -cfg_if! { - if #[cfg(feature = "docs")] { - use crate::os::unix::fs::DirEntryExt; - } else if #[cfg(unix)] { - use std::os::unix::fs::DirEntryExt; - } -} +cfg_unix! { + use crate::os::unix::fs::DirEntryExt; -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(unix, feature = "docs"))] { - impl DirEntryExt for DirEntry { - fn ino(&self) -> u64 { - self.0.ino() - } + impl DirEntryExt for DirEntry { + fn ino(&self) -> u64 { + self.0.ino() } } } diff --git a/src/fs/file.rs b/src/fs/file.rs index bdf93474..7fe99ee4 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -3,18 +3,17 @@ use std::cmp; use std::fmt; use std::io::{Read as _, Seek as _, Write as _}; use std::ops::{Deref, DerefMut}; -use std::path::Path; use std::pin::Pin; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; -use cfg_if::cfg_if; - use crate::fs::{Metadata, Permissions}; use crate::future; use crate::io::{self, Read, Seek, SeekFrom, Write}; +use crate::path::Path; use crate::prelude::*; -use crate::task::{self, blocking, Context, Poll, Waker}; +use crate::task::{self, spawn_blocking, Context, Poll, Waker}; +use crate::utils::Context as _; /// An open file on the filesystem. /// @@ -68,6 +67,23 @@ pub struct File { } impl File { + /// Creates an async file handle. + pub(crate) fn new(file: std::fs::File, is_flushed: bool) -> File { + let file = Arc::new(file); + + File { + file: file.clone(), + lock: Lock::new(State { + file, + mode: Mode::Idle, + cache: Vec::new(), + is_flushed, + last_read_err: None, + last_write_err: None, + }), + } + } + /// Opens a file in read-only mode. /// /// See the [`OpenOptions::open`] function for more options. @@ -97,8 +113,11 @@ impl File { /// ``` pub async fn open>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - let file = blocking::spawn(async move { std::fs::File::open(&path) }).await?; - Ok(file.into()) + let file = spawn_blocking(move || { + std::fs::File::open(&path).context(|| format!("could not open `{}`", path.display())) + }) + .await?; + Ok(File::new(file, true)) } /// Opens a file in write-only mode. @@ -132,8 +151,12 @@ impl File { /// ``` pub async fn create>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - let file = blocking::spawn(async move { std::fs::File::create(&path) }).await?; - Ok(file.into()) + let file = spawn_blocking(move || { + std::fs::File::create(&path) + .context(|| format!("could not create `{}`", path.display())) + }) + .await?; + Ok(File::new(file, true)) } /// Synchronizes OS-internal buffered contents and metadata to disk. @@ -165,7 +188,7 @@ impl File { }) .await?; - blocking::spawn(async move { state.file.sync_all() }).await + spawn_blocking(move || state.file.sync_all()).await } /// Synchronizes OS-internal buffered contents to disk. @@ -201,7 +224,7 @@ impl File { }) .await?; - blocking::spawn(async move { state.file.sync_data() }).await + spawn_blocking(move || state.file.sync_data()).await } /// Truncates or extends the file. @@ -234,7 +257,7 @@ impl File { }) .await?; - blocking::spawn(async move { state.file.set_len(size) }).await + spawn_blocking(move || state.file.set_len(size)).await } /// Reads the file's metadata. @@ -253,7 +276,7 @@ impl File { /// ``` pub async fn metadata(&self) -> io::Result { let file = self.file.clone(); - blocking::spawn(async move { file.metadata() }).await + spawn_blocking(move || file.metadata()).await } /// Changes the permissions on the file. @@ -282,7 +305,7 @@ impl File { /// ``` pub async fn set_permissions(&self, perm: Permissions) -> io::Result<()> { let file = self.file.clone(); - blocking::spawn(async move { file.set_permissions(perm) }).await + spawn_blocking(move || file.set_permissions(perm)).await } } @@ -385,83 +408,58 @@ impl Seek for &File { impl From for File { fn from(file: std::fs::File) -> File { - let file = Arc::new(file); - - File { - file: file.clone(), - lock: Lock::new(State { - file, - mode: Mode::Idle, - cache: Vec::new(), - is_flushed: false, - last_read_err: None, - last_write_err: None, - }), - } + File::new(file, false) } } -cfg_if! { - if #[cfg(feature = "docs")] { - use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; - use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; - } else if #[cfg(unix)] { - use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; - } else if #[cfg(windows)] { - use std::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; - } -} +cfg_unix! { + use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(unix, feature = "docs"))] { - impl AsRawFd for File { - fn as_raw_fd(&self) -> RawFd { - self.file.as_raw_fd() - } + impl AsRawFd for File { + fn as_raw_fd(&self) -> RawFd { + self.file.as_raw_fd() } + } - impl FromRawFd for File { - unsafe fn from_raw_fd(fd: RawFd) -> File { - std::fs::File::from_raw_fd(fd).into() - } + impl FromRawFd for File { + unsafe fn from_raw_fd(fd: RawFd) -> File { + std::fs::File::from_raw_fd(fd).into() } + } - impl IntoRawFd for File { - fn into_raw_fd(self) -> RawFd { - let file = self.file.clone(); - drop(self); - Arc::try_unwrap(file) - .expect("cannot acquire ownership of the file handle after drop") - .into_raw_fd() - } + impl IntoRawFd for File { + fn into_raw_fd(self) -> RawFd { + let file = self.file.clone(); + drop(self); + Arc::try_unwrap(file) + .expect("cannot acquire ownership of the file handle after drop") + .into_raw_fd() } } } -#[cfg_attr(feature = "docs", doc(cfg(windows)))] -cfg_if! { - if #[cfg(any(windows, feature = "docs"))] { - impl AsRawHandle for File { - fn as_raw_handle(&self) -> RawHandle { - self.file.as_raw_handle() - } +cfg_windows! { + use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; + + impl AsRawHandle for File { + fn as_raw_handle(&self) -> RawHandle { + self.file.as_raw_handle() } + } - impl FromRawHandle for File { - unsafe fn from_raw_handle(handle: RawHandle) -> File { - std::fs::File::from_raw_handle(handle).into() - } + impl FromRawHandle for File { + unsafe fn from_raw_handle(handle: RawHandle) -> File { + std::fs::File::from_raw_handle(handle).into() } + } - impl IntoRawHandle for File { - fn into_raw_handle(self) -> RawHandle { - let file = self.file.clone(); - drop(self); - Arc::try_unwrap(file) - .expect("cannot acquire ownership of the file handle after drop") - .into_raw_handle() - } + impl IntoRawHandle for File { + fn into_raw_handle(self) -> RawHandle { + let file = self.file.clone(); + drop(self); + Arc::try_unwrap(file) + .expect("cannot acquire ownership of the file handle after drop") + .into_raw_handle() } } } @@ -702,7 +700,7 @@ impl LockGuard { self.register(cx); // Start a read operation asynchronously. - blocking::spawn(async move { + spawn_blocking(move || { // Read some data from the file into the cache. let res = { let State { file, cache, .. } = &mut *self; @@ -743,7 +741,10 @@ impl LockGuard { if n > 0 { // Seek `n` bytes backwards. This call should not block because it only changes // the internal offset into the file and doesn't touch the actual file on disk. - (&*self.file).seek(SeekFrom::Current(-(n as i64)))?; + // + // We ignore errors here because special files like `/dev/random` are not + // seekable. + let _ = (&*self.file).seek(SeekFrom::Current(-(n as i64))); } // Switch to idle mode. @@ -811,7 +812,7 @@ impl LockGuard { self.register(cx); // Start a write operation asynchronously. - blocking::spawn(async move { + spawn_blocking(move || { match (&*self.file).write_all(&self.cache) { Ok(_) => { // Switch to idle mode. @@ -844,7 +845,7 @@ impl LockGuard { self.register(cx); // Start a flush operation asynchronously. - blocking::spawn(async move { + spawn_blocking(move || { match (&*self.file).flush() { Ok(()) => { // Mark the file as flushed. diff --git a/src/fs/file_type.rs b/src/fs/file_type.rs index a1627984..d7ce2570 100644 --- a/src/fs/file_type.rs +++ b/src/fs/file_type.rs @@ -1,86 +1,84 @@ -use cfg_if::cfg_if; +cfg_not_docs! { + pub use std::fs::FileType; +} + +cfg_docs! { + /// The type of a file or directory. + /// + /// A file type is returned by [`Metadata::file_type`]. + /// + /// Note that file types are mutually exclusive, i.e. at most one of methods [`is_dir`], + /// [`is_file`], and [`is_symlink`] can return `true`. + /// + /// This type is a re-export of [`std::fs::FileType`]. + /// + /// [`Metadata::file_type`]: struct.Metadata.html#method.file_type + /// [`is_dir`]: #method.is_dir + /// [`is_file`]: #method.is_file + /// [`is_symlink`]: #method.is_symlink + /// [`std::fs::FileType`]: https://doc.rust-lang.org/std/fs/struct.FileType.html + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] + pub struct FileType { + _private: (), + } -cfg_if! { - if #[cfg(feature = "docs")] { - /// The type of a file or directory. + impl FileType { + /// Returns `true` if this file type represents a regular directory. /// - /// A file type is returned by [`Metadata::file_type`]. + /// If this file type represents a symbolic link, this method returns `false`. /// - /// Note that file types are mutually exclusive, i.e. at most one of methods [`is_dir`], - /// [`is_file`], and [`is_symlink`] can return `true`. + /// # Examples /// - /// This type is a re-export of [`std::fs::FileType`]. + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; /// - /// [`Metadata::file_type`]: struct.Metadata.html#method.file_type - /// [`is_dir`]: #method.is_dir - /// [`is_file`]: #method.is_file - /// [`is_symlink`]: #method.is_symlink - /// [`std::fs::FileType`]: https://doc.rust-lang.org/std/fs/struct.FileType.html - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] - pub struct FileType { - _private: (), + /// let file_type = fs::metadata(".").await?.file_type(); + /// println!("{:?}", file_type.is_dir()); + /// # + /// # Ok(()) }) } + /// ``` + pub fn is_dir(&self) -> bool { + unreachable!("this impl only appears in the rendered docs") } - impl FileType { - /// Returns `true` if this file type represents a regular directory. - /// - /// If this file type represents a symbolic link, this method returns `false`. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let file_type = fs::metadata(".").await?.file_type(); - /// println!("{:?}", file_type.is_dir()); - /// # - /// # Ok(()) }) } - /// ``` - pub fn is_dir(&self) -> bool { - unimplemented!() - } - - /// Returns `true` if this file type represents a regular file. - /// - /// If this file type represents a symbolic link, this method returns `false`. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let file_type = fs::metadata("a.txt").await?.file_type(); - /// println!("{:?}", file_type.is_file()); - /// # - /// # Ok(()) }) } - /// ``` - pub fn is_file(&self) -> bool { - unimplemented!() - } + /// Returns `true` if this file type represents a regular file. + /// + /// If this file type represents a symbolic link, this method returns `false`. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; + /// + /// let file_type = fs::metadata("a.txt").await?.file_type(); + /// println!("{:?}", file_type.is_file()); + /// # + /// # Ok(()) }) } + /// ``` + pub fn is_file(&self) -> bool { + unreachable!("this impl only appears in the rendered docs") + } - /// Returns `true` if this file type represents a symbolic link. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let file_type = fs::metadata("a.txt").await?.file_type(); - /// println!("{:?}", file_type.is_symlink()); - /// # - /// # Ok(()) }) } - /// ``` - pub fn is_symlink(&self) -> bool { - unimplemented!() - } + /// Returns `true` if this file type represents a symbolic link. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; + /// + /// let file_type = fs::metadata("a.txt").await?.file_type(); + /// println!("{:?}", file_type.is_symlink()); + /// # + /// # Ok(()) }) } + /// ``` + pub fn is_symlink(&self) -> bool { + unreachable!("this impl only appears in the rendered docs") } - } else { - pub use std::fs::FileType; } } diff --git a/src/fs/hard_link.rs b/src/fs/hard_link.rs index 5f950cde..a6a40698 100644 --- a/src/fs/hard_link.rs +++ b/src/fs/hard_link.rs @@ -1,7 +1,7 @@ -use std::path::Path; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Creates a hard link on the filesystem. /// @@ -33,5 +33,14 @@ use crate::task::blocking; pub async fn hard_link, Q: AsRef>(from: P, to: Q) -> io::Result<()> { let from = from.as_ref().to_owned(); let to = to.as_ref().to_owned(); - blocking::spawn(async move { std::fs::hard_link(&from, &to) }).await + spawn_blocking(move || { + std::fs::hard_link(&from, &to).context(|| { + format!( + "could not create a hard link from `{}` to `{}`", + from.display(), + to.display() + ) + }) + }) + .await } diff --git a/src/fs/metadata.rs b/src/fs/metadata.rs index 2c9e41ec..2948016e 100644 --- a/src/fs/metadata.rs +++ b/src/fs/metadata.rs @@ -1,9 +1,6 @@ -use std::path::Path; - -use cfg_if::cfg_if; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; /// Reads metadata for a path. /// @@ -37,196 +34,196 @@ use crate::task::blocking; /// ``` pub async fn metadata>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::metadata(path) }).await + spawn_blocking(move || std::fs::metadata(path)).await +} + +cfg_not_docs! { + pub use std::fs::Metadata; } -cfg_if! { - if #[cfg(feature = "docs")] { - use std::time::SystemTime; +cfg_docs! { + use std::time::SystemTime; + + use crate::fs::{FileType, Permissions}; - use crate::fs::{FileType, Permissions}; + /// Metadata for a file or directory. + /// + /// Metadata is returned by [`metadata`] and [`symlink_metadata`]. + /// + /// This type is a re-export of [`std::fs::Metadata`]. + /// + /// [`metadata`]: fn.metadata.html + /// [`symlink_metadata`]: fn.symlink_metadata.html + /// [`is_dir`]: #method.is_dir + /// [`is_file`]: #method.is_file + /// [`std::fs::Metadata`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html + #[derive(Clone, Debug)] + pub struct Metadata { + _private: (), + } - /// Metadata for a file or directory. + impl Metadata { + /// Returns the file type from this metadata. /// - /// Metadata is returned by [`metadata`] and [`symlink_metadata`]. + /// # Examples /// - /// This type is a re-export of [`std::fs::Metadata`]. + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; /// - /// [`metadata`]: fn.metadata.html - /// [`symlink_metadata`]: fn.symlink_metadata.html - /// [`is_dir`]: #method.is_dir - /// [`is_file`]: #method.is_file - /// [`std::fs::Metadata`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html - #[derive(Clone, Debug)] - pub struct Metadata { - _private: (), + /// let metadata = fs::metadata("a.txt").await?; + /// println!("{:?}", metadata.file_type()); + /// # + /// # Ok(()) }) } + /// ``` + pub fn file_type(&self) -> FileType { + unreachable!("this impl only appears in the rendered docs") } - impl Metadata { - /// Returns the file type from this metadata. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let metadata = fs::metadata("a.txt").await?; - /// println!("{:?}", metadata.file_type()); - /// # - /// # Ok(()) }) } - /// ``` - pub fn file_type(&self) -> FileType { - unimplemented!() - } - - /// Returns `true` if this metadata is for a regular directory. - /// - /// If this metadata is for a symbolic link, this method returns `false`. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let metadata = fs::metadata(".").await?; - /// println!("{:?}", metadata.is_dir()); - /// # - /// # Ok(()) }) } - /// ``` - pub fn is_dir(&self) -> bool { - unimplemented!() - } + /// Returns `true` if this metadata is for a regular directory. + /// + /// If this metadata is for a symbolic link, this method returns `false`. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; + /// + /// let metadata = fs::metadata(".").await?; + /// println!("{:?}", metadata.is_dir()); + /// # + /// # Ok(()) }) } + /// ``` + pub fn is_dir(&self) -> bool { + unreachable!("this impl only appears in the rendered docs") + } - /// Returns `true` if this metadata is for a regular file. - /// - /// If this metadata is for a symbolic link, this method returns `false`. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let metadata = fs::metadata("a.txt").await?; - /// println!("{:?}", metadata.is_file()); - /// # - /// # Ok(()) }) } - /// ``` - pub fn is_file(&self) -> bool { - unimplemented!() - } + /// Returns `true` if this metadata is for a regular file. + /// + /// If this metadata is for a symbolic link, this method returns `false`. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; + /// + /// let metadata = fs::metadata("a.txt").await?; + /// println!("{:?}", metadata.is_file()); + /// # + /// # Ok(()) }) } + /// ``` + pub fn is_file(&self) -> bool { + unreachable!("this impl only appears in the rendered docs") + } - /// Returns the file size in bytes. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let metadata = fs::metadata("a.txt").await?; - /// println!("{}", metadata.len()); - /// # - /// # Ok(()) }) } - /// ``` - pub fn len(&self) -> u64 { - unimplemented!() - } + /// Returns the file size in bytes. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; + /// + /// let metadata = fs::metadata("a.txt").await?; + /// println!("{}", metadata.len()); + /// # + /// # Ok(()) }) } + /// ``` + pub fn len(&self) -> u64 { + unreachable!("this impl only appears in the rendered docs") + } - /// Returns the permissions from this metadata. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let metadata = fs::metadata("a.txt").await?; - /// println!("{:?}", metadata.permissions()); - /// # - /// # Ok(()) }) } - /// ``` - pub fn permissions(&self) -> Permissions { - unimplemented!() - } + /// Returns the permissions from this metadata. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; + /// + /// let metadata = fs::metadata("a.txt").await?; + /// println!("{:?}", metadata.permissions()); + /// # + /// # Ok(()) }) } + /// ``` + pub fn permissions(&self) -> Permissions { + unreachable!("this impl only appears in the rendered docs") + } - /// Returns the last modification time. - /// - /// # Errors - /// - /// This data may not be available on all platforms, in which case an error will be - /// returned. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let metadata = fs::metadata("a.txt").await?; - /// println!("{:?}", metadata.modified()); - /// # - /// # Ok(()) }) } - /// ``` - pub fn modified(&self) -> io::Result { - unimplemented!() - } + /// Returns the last modification time. + /// + /// # Errors + /// + /// This data may not be available on all platforms, in which case an error will be + /// returned. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; + /// + /// let metadata = fs::metadata("a.txt").await?; + /// println!("{:?}", metadata.modified()); + /// # + /// # Ok(()) }) } + /// ``` + pub fn modified(&self) -> io::Result { + unreachable!("this impl only appears in the rendered docs") + } - /// Returns the last access time. - /// - /// # Errors - /// - /// This data may not be available on all platforms, in which case an error will be - /// returned. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let metadata = fs::metadata("a.txt").await?; - /// println!("{:?}", metadata.accessed()); - /// # - /// # Ok(()) }) } - /// ``` - pub fn accessed(&self) -> io::Result { - unimplemented!() - } + /// Returns the last access time. + /// + /// # Errors + /// + /// This data may not be available on all platforms, in which case an error will be + /// returned. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; + /// + /// let metadata = fs::metadata("a.txt").await?; + /// println!("{:?}", metadata.accessed()); + /// # + /// # Ok(()) }) } + /// ``` + pub fn accessed(&self) -> io::Result { + unreachable!("this impl only appears in the rendered docs") + } - /// Returns the creation time. - /// - /// # Errors - /// - /// This data may not be available on all platforms, in which case an error will be - /// returned. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let metadata = fs::metadata("a.txt").await?; - /// println!("{:?}", metadata.created()); - /// # - /// # Ok(()) }) } - /// ``` - pub fn created(&self) -> io::Result { - unimplemented!() - } + /// Returns the creation time. + /// + /// # Errors + /// + /// This data may not be available on all platforms, in which case an error will be + /// returned. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; + /// + /// let metadata = fs::metadata("a.txt").await?; + /// println!("{:?}", metadata.created()); + /// # + /// # Ok(()) }) } + /// ``` + pub fn created(&self) -> io::Result { + unreachable!("this impl only appears in the rendered docs") } - } else { - pub use std::fs::Metadata; } } diff --git a/src/fs/mod.rs b/src/fs/mod.rs index 4598ec84..5cf086de 100644 --- a/src/fs/mod.rs +++ b/src/fs/mod.rs @@ -3,11 +3,13 @@ //! This module is an async version of [`std::fs`]. //! //! [`os::unix::fs`]: ../os/unix/fs/index.html +//! [`os::windows::fs`]: ../os/windows/fs/index.html //! [`std::fs`]: https://doc.rust-lang.org/std/fs/index.html //! //! # Platform-specific extensions //! //! * Unix: use the [`os::unix::fs`] module. +//! * Windows: use the [`os::windows::fs`] module. //! //! # Examples //! diff --git a/src/fs/open_options.rs b/src/fs/open_options.rs index 252873cd..91ad8cab 100644 --- a/src/fs/open_options.rs +++ b/src/fs/open_options.rs @@ -1,11 +1,9 @@ -use std::path::Path; - -use cfg_if::cfg_if; +use std::future::Future; use crate::fs::File; -use crate::future::Future; use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; /// A builder for opening files with configurable options. /// @@ -286,7 +284,10 @@ impl OpenOptions { pub fn open>(&self, path: P) -> impl Future> { let path = path.as_ref().to_owned(); let options = self.0.clone(); - async move { blocking::spawn(async move { options.open(path).map(|f| f.into()) }).await } + async move { + let file = spawn_blocking(move || options.open(path)).await?; + Ok(File::new(file, true)) + } } } @@ -296,27 +297,18 @@ impl Default for OpenOptions { } } -cfg_if! { - if #[cfg(feature = "docs")] { - use crate::os::unix::fs::OpenOptionsExt; - } else if #[cfg(unix)] { - use std::os::unix::fs::OpenOptionsExt; - } -} +cfg_unix! { + use crate::os::unix::fs::OpenOptionsExt; -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(unix, feature = "docs"))] { - impl OpenOptionsExt for OpenOptions { - fn mode(&mut self, mode: u32) -> &mut Self { - self.0.mode(mode); - self - } + impl OpenOptionsExt for OpenOptions { + fn mode(&mut self, mode: u32) -> &mut Self { + self.0.mode(mode); + self + } - fn custom_flags(&mut self, flags: i32) -> &mut Self { - self.0.custom_flags(flags); - self - } + fn custom_flags(&mut self, flags: i32) -> &mut Self { + self.0.custom_flags(flags); + self } } } diff --git a/src/fs/permissions.rs b/src/fs/permissions.rs index 628dd392..50aa45cd 100644 --- a/src/fs/permissions.rs +++ b/src/fs/permissions.rs @@ -1,58 +1,56 @@ -use cfg_if::cfg_if; +cfg_not_docs! { + pub use std::fs::Permissions; +} + +cfg_docs! { + /// A set of permissions on a file or directory. + /// + /// This type is a re-export of [`std::fs::Permissions`]. + /// + /// [`std::fs::Permissions`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct Permissions { + _private: (), + } -cfg_if! { - if #[cfg(feature = "docs")] { - /// A set of permissions on a file or directory. + impl Permissions { + /// Returns the read-only flag. /// - /// This type is a re-export of [`std::fs::Permissions`]. + /// # Examples /// - /// [`std::fs::Permissions`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html - #[derive(Clone, PartialEq, Eq, Debug)] - pub struct Permissions { - _private: (), + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; + /// + /// let perm = fs::metadata("a.txt").await?.permissions(); + /// println!("{:?}", perm.readonly()); + /// # + /// # Ok(()) }) } + /// ``` + pub fn readonly(&self) -> bool { + unreachable!("this impl only appears in the rendered docs") } - impl Permissions { - /// Returns the read-only flag. - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let perm = fs::metadata("a.txt").await?.permissions(); - /// println!("{:?}", perm.readonly()); - /// # - /// # Ok(()) }) } - /// ``` - pub fn readonly(&self) -> bool { - unimplemented!() - } - - /// Configures the read-only flag. - /// - /// [`fs::set_permissions`]: fn.set_permissions.html - /// - /// # Examples - /// - /// ```no_run - /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// # - /// use async_std::fs; - /// - /// let mut perm = fs::metadata("a.txt").await?.permissions(); - /// perm.set_readonly(true); - /// fs::set_permissions("a.txt", perm).await?; - /// # - /// # Ok(()) }) } - /// ``` - pub fn set_readonly(&mut self, readonly: bool) { - unimplemented!() - } + /// Configures the read-only flag. + /// + /// [`fs::set_permissions`]: fn.set_permissions.html + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; + /// + /// let mut perm = fs::metadata("a.txt").await?.permissions(); + /// perm.set_readonly(true); + /// fs::set_permissions("a.txt", perm).await?; + /// # + /// # Ok(()) }) } + /// ``` + pub fn set_readonly(&mut self, readonly: bool) { + unreachable!("this impl only appears in the rendered docs") } - } else { - pub use std::fs::Permissions; } } diff --git a/src/fs/read.rs b/src/fs/read.rs index 6b3560db..3b568f7a 100644 --- a/src/fs/read.rs +++ b/src/fs/read.rs @@ -1,7 +1,7 @@ -use std::path::Path; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Reads the entire contents of a file as raw bytes. /// @@ -37,5 +37,8 @@ use crate::task::blocking; /// ``` pub async fn read>(path: P) -> io::Result> { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::read(path) }).await + spawn_blocking(move || { + std::fs::read(&path).context(|| format!("could not read file `{}`", path.display())) + }) + .await } diff --git a/src/fs/read_dir.rs b/src/fs/read_dir.rs index 9b4269df..d8261a94 100644 --- a/src/fs/read_dir.rs +++ b/src/fs/read_dir.rs @@ -1,11 +1,12 @@ -use std::path::Path; +use std::future::Future; use std::pin::Pin; use crate::fs::DirEntry; -use crate::future::Future; use crate::io; +use crate::path::Path; use crate::stream::Stream; -use crate::task::{blocking, Context, JoinHandle, Poll}; +use crate::task::{spawn_blocking, Context, JoinHandle, Poll}; +use crate::utils::Context as _; /// Returns a stream of entries in a directory. /// @@ -45,9 +46,12 @@ use crate::task::{blocking, Context, JoinHandle, Poll}; /// ``` pub async fn read_dir>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::read_dir(path) }) - .await - .map(ReadDir::new) + spawn_blocking(move || { + std::fs::read_dir(&path) + .context(|| format!("could not read directory `{}`", path.display())) + }) + .await + .map(ReadDir::new) } /// A stream of entries in a directory. @@ -91,7 +95,7 @@ impl Stream for ReadDir { let mut inner = opt.take().unwrap(); // Start the operation asynchronously. - self.0 = State::Busy(blocking::spawn(async move { + self.0 = State::Busy(spawn_blocking(move || { let next = inner.next(); (inner, next) })); diff --git a/src/fs/read_link.rs b/src/fs/read_link.rs index aede99bc..d8cabb72 100644 --- a/src/fs/read_link.rs +++ b/src/fs/read_link.rs @@ -1,7 +1,7 @@ -use std::path::{Path, PathBuf}; - use crate::io; -use crate::task::blocking; +use crate::path::{Path, PathBuf}; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Reads a symbolic link and returns the path it points to. /// @@ -29,5 +29,10 @@ use crate::task::blocking; /// ``` pub async fn read_link>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::read_link(path) }).await + spawn_blocking(move || { + std::fs::read_link(&path) + .map(Into::into) + .context(|| format!("could not read link `{}`", path.display())) + }) + .await } diff --git a/src/fs/read_to_string.rs b/src/fs/read_to_string.rs index 345f76ef..2378aaed 100644 --- a/src/fs/read_to_string.rs +++ b/src/fs/read_to_string.rs @@ -1,7 +1,7 @@ -use std::path::Path; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Reads the entire contents of a file as a string. /// @@ -38,5 +38,9 @@ use crate::task::blocking; /// ``` pub async fn read_to_string>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::read_to_string(path) }).await + spawn_blocking(move || { + std::fs::read_to_string(&path) + .context(|| format!("could not read file `{}`", path.display())) + }) + .await } diff --git a/src/fs/remove_dir.rs b/src/fs/remove_dir.rs index a176edc8..8fdba18c 100644 --- a/src/fs/remove_dir.rs +++ b/src/fs/remove_dir.rs @@ -1,7 +1,7 @@ -use std::path::Path; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Removes an empty directory. /// @@ -30,5 +30,9 @@ use crate::task::blocking; /// ``` pub async fn remove_dir>(path: P) -> io::Result<()> { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::remove_dir(path) }).await + spawn_blocking(move || { + std::fs::remove_dir(&path) + .context(|| format!("could not remove directory `{}`", path.display())) + }) + .await } diff --git a/src/fs/remove_dir_all.rs b/src/fs/remove_dir_all.rs index 9db0c31f..d4bad3a2 100644 --- a/src/fs/remove_dir_all.rs +++ b/src/fs/remove_dir_all.rs @@ -1,7 +1,7 @@ -use std::path::Path; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Removes a directory and all of its contents. /// @@ -30,5 +30,9 @@ use crate::task::blocking; /// ``` pub async fn remove_dir_all>(path: P) -> io::Result<()> { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::remove_dir_all(path) }).await + spawn_blocking(move || { + std::fs::remove_dir_all(&path) + .context(|| format!("could not remove directory `{}`", path.display())) + }) + .await } diff --git a/src/fs/remove_file.rs b/src/fs/remove_file.rs index cc0eeb25..b881f8be 100644 --- a/src/fs/remove_file.rs +++ b/src/fs/remove_file.rs @@ -1,7 +1,7 @@ -use std::path::Path; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Removes a file. /// @@ -30,5 +30,9 @@ use crate::task::blocking; /// ``` pub async fn remove_file>(path: P) -> io::Result<()> { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::remove_file(path) }).await + spawn_blocking(move || { + std::fs::remove_file(&path) + .context(|| format!("could not remove file `{}`", path.display())) + }) + .await } diff --git a/src/fs/rename.rs b/src/fs/rename.rs index 72cd227f..25fc55fa 100644 --- a/src/fs/rename.rs +++ b/src/fs/rename.rs @@ -1,7 +1,7 @@ -use std::path::Path; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Renames a file or directory to a new location. /// @@ -35,5 +35,14 @@ use crate::task::blocking; pub async fn rename, Q: AsRef>(from: P, to: Q) -> io::Result<()> { let from = from.as_ref().to_owned(); let to = to.as_ref().to_owned(); - blocking::spawn(async move { std::fs::rename(&from, &to) }).await + spawn_blocking(move || { + std::fs::rename(&from, &to).context(|| { + format!( + "could not rename `{}` to `{}`", + from.display(), + to.display() + ) + }) + }) + .await } diff --git a/src/fs/set_permissions.rs b/src/fs/set_permissions.rs index 6fa6306f..60a6d6f1 100644 --- a/src/fs/set_permissions.rs +++ b/src/fs/set_permissions.rs @@ -1,8 +1,7 @@ -use std::path::Path; - use crate::fs::Permissions; use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; /// Changes the permissions of a file or directory. /// @@ -33,5 +32,5 @@ use crate::task::blocking; /// ``` pub async fn set_permissions>(path: P, perm: Permissions) -> io::Result<()> { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::set_permissions(path, perm) }).await + spawn_blocking(move || std::fs::set_permissions(path, perm)).await } diff --git a/src/fs/symlink_metadata.rs b/src/fs/symlink_metadata.rs index 6f1b9d50..45be6d99 100644 --- a/src/fs/symlink_metadata.rs +++ b/src/fs/symlink_metadata.rs @@ -1,8 +1,7 @@ -use std::path::Path; - use crate::fs::Metadata; use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; /// Reads metadata for a path without following symbolic links. /// @@ -35,5 +34,5 @@ use crate::task::blocking; /// ``` pub async fn symlink_metadata>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - blocking::spawn(async move { std::fs::symlink_metadata(path) }).await + spawn_blocking(move || std::fs::symlink_metadata(path)).await } diff --git a/src/fs/write.rs b/src/fs/write.rs index b0d7abcc..7c14098b 100644 --- a/src/fs/write.rs +++ b/src/fs/write.rs @@ -1,7 +1,7 @@ -use std::path::Path; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; +use crate::utils::Context as _; /// Writes a slice of bytes as the new contents of a file. /// @@ -34,5 +34,9 @@ use crate::task::blocking; pub async fn write, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> { let path = path.as_ref().to_owned(); let contents = contents.as_ref().to_owned(); - blocking::spawn(async move { std::fs::write(path, contents) }).await + spawn_blocking(move || { + std::fs::write(&path, contents) + .context(|| format!("could not write to file `{}`", path.display())) + }) + .await } diff --git a/src/future/future/delay.rs b/src/future/future/delay.rs new file mode 100644 index 00000000..641084ff --- /dev/null +++ b/src/future/future/delay.rs @@ -0,0 +1,43 @@ +use std::future::Future; +use std::pin::Pin; +use std::time::Duration; + +use futures_timer::Delay; +use pin_project_lite::pin_project; + +use crate::task::{Context, Poll}; + +pin_project! { + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct DelayFuture { + #[pin] + future: F, + #[pin] + delay: Delay, + } +} + +impl DelayFuture { + pub fn new(future: F, dur: Duration) -> DelayFuture { + let delay = Delay::new(dur); + + DelayFuture { future, delay } + } +} + +impl Future for DelayFuture { + type Output = F::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + match this.delay.poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(_) => match this.future.poll(cx) { + Poll::Ready(v) => Poll::Ready(v), + Poll::Pending => Poll::Pending, + }, + } + } +} diff --git a/src/future/future/flatten.rs b/src/future/future/flatten.rs new file mode 100644 index 00000000..a07b140c --- /dev/null +++ b/src/future/future/flatten.rs @@ -0,0 +1,52 @@ +use std::future::Future; +use std::pin::Pin; + +use crate::future::IntoFuture; +use crate::task::{ready, Context, Poll}; + +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub struct FlattenFuture { + state: State, +} + +#[derive(Debug)] +enum State { + First(Fut1), + Second(Fut2), + Empty, +} + +impl FlattenFuture { + pub(crate) fn new(future: Fut1) -> FlattenFuture { + FlattenFuture { + state: State::First(future), + } + } +} + +impl Future for FlattenFuture::Future> +where + Fut1: Future, + Fut1::Output: IntoFuture, +{ + type Output = ::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Self { state } = unsafe { self.get_unchecked_mut() }; + loop { + match state { + State::First(fut1) => { + let fut2 = ready!(unsafe { Pin::new_unchecked(fut1) }.poll(cx)).into_future(); + *state = State::Second(fut2); + } + State::Second(fut2) => { + let v = ready!(unsafe { Pin::new_unchecked(fut2) }.poll(cx)); + *state = State::Empty; + return Poll::Ready(v); + } + State::Empty => panic!("polled a completed future"), + } + } + } +} diff --git a/src/future/future/join.rs b/src/future/future/join.rs new file mode 100644 index 00000000..5cfbd99a --- /dev/null +++ b/src/future/future/join.rs @@ -0,0 +1,60 @@ +use std::pin::Pin; + +use async_macros::MaybeDone; +use pin_project_lite::pin_project; + +use crate::task::{Context, Poll}; +use std::future::Future; + +pin_project! { + #[allow(missing_docs)] + #[allow(missing_debug_implementations)] + pub struct Join + where + L: Future, + R: Future + { + #[pin] left: MaybeDone, + #[pin] right: MaybeDone, + } +} + +impl Join +where + L: Future, + R: Future, +{ + pub(crate) fn new(left: L, right: R) -> Self { + Self { + left: MaybeDone::new(left), + right: MaybeDone::new(right), + } + } +} + +impl Future for Join +where + L: Future, + R: Future, +{ + type Output = (L::Output, R::Output); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + let mut left = this.left; + let mut right = this.right; + + let is_left_ready = Future::poll(Pin::new(&mut left), cx).is_ready(); + if is_left_ready && right.as_ref().output().is_some() { + return Poll::Ready((left.take().unwrap(), right.take().unwrap())); + } + + let is_right_ready = Future::poll(Pin::new(&mut right), cx).is_ready(); + if is_right_ready && left.as_ref().output().is_some() { + return Poll::Ready((left.take().unwrap(), right.take().unwrap())); + } + + Poll::Pending + } +} diff --git a/src/future/future/mod.rs b/src/future/future/mod.rs new file mode 100644 index 00000000..5fdaf4b1 --- /dev/null +++ b/src/future/future/mod.rs @@ -0,0 +1,395 @@ +cfg_unstable! { + mod delay; + mod flatten; + mod race; + mod try_race; + mod join; + mod try_join; + + use std::time::Duration; + + use delay::DelayFuture; + use flatten::FlattenFuture; + use crate::future::IntoFuture; + use race::Race; + use try_race::TryRace; + use join::Join; + use try_join::TryJoin; +} + +extension_trait! { + use std::pin::Pin; + use std::ops::{Deref, DerefMut}; + + use crate::task::{Context, Poll}; + + #[doc = r#" + A future represents an asynchronous computation. + + A future is a value that may not have finished computing yet. This kind of + "asynchronous value" makes it possible for a thread to continue doing useful + work while it waits for the value to become available. + + The [provided methods] do not really exist in the trait itself, but they become + available when [`FutureExt`] from the [prelude] is imported: + + ``` + # #[allow(unused_imports)] + use async_std::prelude::*; + ``` + + # The `poll` method + + The core method of future, `poll`, *attempts* to resolve the future into a + final value. This method does not block if the value is not ready. Instead, + the current task is scheduled to be woken up when it's possible to make + further progress by `poll`ing again. The `context` passed to the `poll` + method can provide a [`Waker`], which is a handle for waking up the current + task. + + When using a future, you generally won't call `poll` directly, but instead + `.await` the value. + + [`Waker`]: ../task/struct.Waker.html + [provided methods]: #provided-methods + [`FutureExt`]: ../prelude/trait.FutureExt.html + [prelude]: ../prelude/index.html + "#] + pub trait Future { + #[doc = r#" + The type of value produced on completion. + "#] + type Output; + + #[doc = r#" + Attempt to resolve the future to a final value, registering + the current task for wakeup if the value is not yet available. + + # Return value + + This function returns: + + - [`Poll::Pending`] if the future is not ready yet + - [`Poll::Ready(val)`] with the result `val` of this future if it + finished successfully. + + Once a future has finished, clients should not `poll` it again. + + When a future is not ready yet, `poll` returns `Poll::Pending` and + stores a clone of the [`Waker`] copied from the current [`Context`]. + This [`Waker`] is then woken once the future can make progress. + For example, a future waiting for a socket to become + readable would call `.clone()` on the [`Waker`] and store it. + When a signal arrives elsewhere indicating that the socket is readable, + [`Waker::wake`] is called and the socket future's task is awoken. + Once a task has been woken up, it should attempt to `poll` the future + again, which may or may not produce a final value. + + Note that on multiple calls to `poll`, only the [`Waker`] from the + [`Context`] passed to the most recent call should be scheduled to + receive a wakeup. + + # Runtime characteristics + + Futures alone are *inert*; they must be *actively* `poll`ed to make + progress, meaning that each time the current task is woken up, it should + actively re-`poll` pending futures that it still has an interest in. + + The `poll` function is not called repeatedly in a tight loop -- instead, + it should only be called when the future indicates that it is ready to + make progress (by calling `wake()`). If you're familiar with the + `poll(2)` or `select(2)` syscalls on Unix it's worth noting that futures + typically do *not* suffer the same problems of "all wakeups must poll + all events"; they are more like `epoll(4)`. + + An implementation of `poll` should strive to return quickly, and should + not block. Returning quickly prevents unnecessarily clogging up + threads or event loops. If it is known ahead of time that a call to + `poll` may end up taking awhile, the work should be offloaded to a + thread pool (or something similar) to ensure that `poll` can return + quickly. + + # Panics + + Once a future has completed (returned `Ready` from `poll`), calling its + `poll` method again may panic, block forever, or cause other kinds of + problems; the `Future` trait places no requirements on the effects of + such a call. However, as the `poll` method is not marked `unsafe`, + Rust's usual rules apply: calls must never cause undefined behavior + (memory corruption, incorrect use of `unsafe` functions, or the like), + regardless of the future's state. + + [`Poll::Pending`]: ../task/enum.Poll.html#variant.Pending + [`Poll::Ready(val)`]: ../task/enum.Poll.html#variant.Ready + [`Context`]: ../task/struct.Context.html + [`Waker`]: ../task/struct.Waker.html + [`Waker::wake`]: ../task/struct.Waker.html#method.wake + "#] + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll; + } + + #[doc = r#" + Extension methods for [`Future`]. + + [`Future`]: ../future/trait.Future.html + "#] + pub trait FutureExt: std::future::Future { + /// Returns a Future that delays execution for a specified time. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// use async_std::prelude::*; + /// use async_std::future; + /// use std::time::Duration; + /// + /// let a = future::ready(1).delay(Duration::from_millis(2000)); + /// dbg!(a.await); + /// # }) + /// ``` + #[cfg(all(feature = "default", feature = "unstable"))] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn delay(self, dur: Duration) -> impl Future [DelayFuture] + where + Self: Sized, + { + DelayFuture::new(self, dur) + } + + /// Flatten out the execution of this future when the result itself + /// can be converted into another future. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// use async_std::prelude::*; + /// + /// let nested_future = async { async { 1 } }; + /// let future = nested_future.flatten(); + /// assert_eq!(future.await, 1); + /// # }) + /// ``` + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn flatten( + self, + ) -> impl Future::Output> + [FlattenFuture::Future>] + where + Self: Sized, + ::Output: IntoFuture, + { + FlattenFuture::new(self) + } + + #[doc = r#" + Waits for one of two similarly-typed futures to complete. + + Awaits multiple futures simultaneously, returning the output of the + first future that completes. + + This function will return a new future which awaits for either one of both + futures to complete. If multiple futures are completed at the same time, + resolution will occur in the order that they have been passed. + + Note that this function consumes all futures passed, and once a future is + completed, all other futures are dropped. + + # Examples + + ``` + # async_std::task::block_on(async { + use async_std::prelude::*; + use async_std::future; + + let a = future::pending(); + let b = future::ready(1u8); + let c = future::ready(2u8); + + let f = a.race(b).race(c); + assert_eq!(f.await, 1u8); + # }); + ``` + "#] + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn race( + self, + other: F, + ) -> impl Future::Output> [Race] + where + Self: std::future::Future + Sized, + F: std::future::Future::Output>, + { + Race::new(self, other) + } + + #[doc = r#" + Waits for one of two similarly-typed fallible futures to complete. + + Awaits multiple futures simultaneously, returning all results once complete. + + `try_race` is similar to [`race`], but keeps going if a future + resolved to an error until all futures have been resolved. In which case + an error is returned. + + The ordering of which value is yielded when two futures resolve + simultaneously is intentionally left unspecified. + + [`race`]: #method.race + + # Examples + + ``` + # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::future; + use std::io::{Error, ErrorKind}; + + let a = future::pending::>(); + let b = future::ready(Err(Error::from(ErrorKind::Other))); + let c = future::ready(Ok(1u8)); + + let f = a.try_race(b).try_race(c); + assert_eq!(f.await?, 1u8); + # + # Ok(()) }) } + ``` + "#] + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn try_race( + self, + other: F + ) -> impl Future::Output> [TryRace] + where + Self: std::future::Future> + Sized, + F: std::future::Future::Output>, + { + TryRace::new(self, other) + } + + #[doc = r#" + Waits for two similarly-typed futures to complete. + + Awaits multiple futures simultaneously, returning the output of the + futures once both complete. + + This function returns a new future which polls both futures + concurrently. + + # Examples + + ``` + # async_std::task::block_on(async { + use async_std::prelude::*; + use async_std::future; + + let a = future::ready(1u8); + let b = future::ready(2u8); + + let f = a.join(b); + assert_eq!(f.await, (1u8, 2u8)); + # }); + ``` + "#] + #[cfg(any(feature = "unstable", feature = "docs"))] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn join( + self, + other: F + ) -> impl Future::Output, ::Output)> [Join] + where + Self: std::future::Future + Sized, + F: std::future::Future::Output>, + { + Join::new(self, other) + } + + #[doc = r#" + Waits for two similarly-typed fallible futures to complete. + + Awaits multiple futures simultaneously, returning all results once + complete. + + `try_join` is similar to [`join`], but returns an error immediately + if a future resolves to an error. + + [`join`]: #method.join + + # Examples + + ``` + # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::future; + + let a = future::ready(Err("Error")); + let b = future::ready(Ok(1u8)); + + let f = a.try_join(b); + assert_eq!(f.await, Err("Error")); + + let a = future::ready(Ok::(1u8)); + let b = future::ready(Ok::(2u8)); + + let f = a.try_join(b); + assert_eq!(f.await, Ok((1u8, 2u8))); + # + # Ok(()) }) } + ``` + "#] + #[cfg(any(feature = "unstable", feature = "docs"))] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn try_join( + self, + other: F + ) -> impl Future> [TryJoin] + where + Self: std::future::Future> + Sized, + F: std::future::Future::Output>, + { + TryJoin::new(self, other) + } + } + + impl Future for Box { + type Output = F::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + unreachable!("this impl only appears in the rendered docs") + } + } + + impl Future for &mut F { + type Output = F::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + unreachable!("this impl only appears in the rendered docs") + } + } + + impl

Future for Pin

+ where + P: DerefMut + Unpin, +

::Target: Future, + { + type Output = <

::Target as Future>::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + unreachable!("this impl only appears in the rendered docs") + } + } + + impl Future for std::panic::AssertUnwindSafe { + type Output = F::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + unreachable!("this impl only appears in the rendered docs") + } + } +} diff --git a/src/future/future/race.rs b/src/future/future/race.rs new file mode 100644 index 00000000..ed034f05 --- /dev/null +++ b/src/future/future/race.rs @@ -0,0 +1,57 @@ +use std::future::Future; +use std::pin::Pin; + +use async_macros::MaybeDone; +use pin_project_lite::pin_project; + +use crate::task::{Context, Poll}; + +pin_project! { + #[allow(missing_docs)] + #[allow(missing_debug_implementations)] + pub struct Race + where + L: Future, + R: Future + { + #[pin] left: MaybeDone, + #[pin] right: MaybeDone, + } +} + +impl Race +where + L: Future, + R: Future, +{ + pub(crate) fn new(left: L, right: R) -> Self { + Self { + left: MaybeDone::new(left), + right: MaybeDone::new(right), + } + } +} + +impl Future for Race +where + L: Future, + R: Future, +{ + type Output = L::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + let mut left = this.left; + if Future::poll(Pin::new(&mut left), cx).is_ready() { + return Poll::Ready(left.take().unwrap()); + } + + let mut right = this.right; + if Future::poll(Pin::new(&mut right), cx).is_ready() { + return Poll::Ready(right.take().unwrap()); + } + + Poll::Pending + } +} diff --git a/src/future/future/try_join.rs b/src/future/future/try_join.rs new file mode 100644 index 00000000..58ae6d62 --- /dev/null +++ b/src/future/future/try_join.rs @@ -0,0 +1,72 @@ +use std::pin::Pin; + +use async_macros::MaybeDone; +use pin_project_lite::pin_project; + +use crate::task::{Context, Poll}; +use std::future::Future; + +pin_project! { + #[allow(missing_docs)] + #[allow(missing_debug_implementations)] + pub struct TryJoin + where + L: Future, + R: Future + { + #[pin] left: MaybeDone, + #[pin] right: MaybeDone, + } +} + +impl TryJoin +where + L: Future, + R: Future, +{ + pub(crate) fn new(left: L, right: R) -> Self { + Self { + left: MaybeDone::new(left), + right: MaybeDone::new(right), + } + } +} + +impl Future for TryJoin +where + L: Future>, + R: Future, +{ + type Output = Result<(T, T), E>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + + let mut left = this.left; + let mut right = this.right; + + if Future::poll(Pin::new(&mut left), cx).is_ready() { + if left.as_ref().output().unwrap().is_err() { + return Poll::Ready(Err(left.take().unwrap().err().unwrap())); + } else if right.as_ref().output().is_some() { + return Poll::Ready(Ok(( + left.take().unwrap().ok().unwrap(), + right.take().unwrap().ok().unwrap(), + ))); + } + } + + if Future::poll(Pin::new(&mut right), cx).is_ready() { + if right.as_ref().output().unwrap().is_err() { + return Poll::Ready(Err(right.take().unwrap().err().unwrap())); + } else if left.as_ref().output().is_some() { + return Poll::Ready(Ok(( + left.take().unwrap().ok().unwrap(), + right.take().unwrap().ok().unwrap(), + ))); + } + } + + Poll::Pending + } +} diff --git a/src/future/future/try_race.rs b/src/future/future/try_race.rs new file mode 100644 index 00000000..d0ca4a90 --- /dev/null +++ b/src/future/future/try_race.rs @@ -0,0 +1,66 @@ +use std::pin::Pin; + +use async_macros::MaybeDone; +use pin_project_lite::pin_project; + +use crate::task::{Context, Poll}; +use std::future::Future; + +pin_project! { + #[allow(missing_docs)] + #[allow(missing_debug_implementations)] + pub struct TryRace + where + L: Future, + R: Future + { + #[pin] left: MaybeDone, + #[pin] right: MaybeDone, + } +} + +impl TryRace +where + L: Future, + R: Future, +{ + pub(crate) fn new(left: L, right: R) -> Self { + Self { + left: MaybeDone::new(left), + right: MaybeDone::new(right), + } + } +} + +impl Future for TryRace +where + L: Future>, + R: Future, +{ + type Output = L::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let mut left_errored = false; + + // Check if the left future is ready & successful. Continue if not. + let mut left = this.left; + if Future::poll(Pin::new(&mut left), cx).is_ready() { + if left.as_ref().output().unwrap().is_ok() { + return Poll::Ready(left.take().unwrap()); + } else { + left_errored = true; + } + } + + // Check if the right future is ready & successful. Return err if left + // future also resolved to err. Continue if not. + let mut right = this.right; + let is_ready = Future::poll(Pin::new(&mut right), cx).is_ready(); + if is_ready && (right.as_ref().output().unwrap().is_ok() || left_errored) { + return Poll::Ready(right.take().unwrap()); + } + + Poll::Pending + } +} diff --git a/src/future/into_future.rs b/src/future/into_future.rs new file mode 100644 index 00000000..8e5e5e04 --- /dev/null +++ b/src/future/into_future.rs @@ -0,0 +1,53 @@ +use std::future::Future; + +/// Convert a type into a `Future`. +/// +/// # Examples +/// +/// ``` +/// use async_std::future::{Future, IntoFuture}; +/// use async_std::io; +/// use async_std::pin::Pin; +/// +/// struct Client; +/// +/// impl Client { +/// pub async fn send(self) -> io::Result<()> { +/// // Send a request +/// Ok(()) +/// } +/// } +/// +/// impl IntoFuture for Client { +/// type Output = io::Result<()>; +/// +/// type Future = Pin>>; +/// +/// fn into_future(self) -> Self::Future { +/// Box::pin(async { +/// self.send().await +/// }) +/// } +/// } +/// ``` +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +pub trait IntoFuture { + /// The type of value produced on completion. + type Output; + + /// Which kind of future are we turning this into? + type Future: Future; + + /// Create a future from a value + fn into_future(self) -> Self::Future; +} + +impl IntoFuture for T { + type Output = T::Output; + type Future = T; + + fn into_future(self) -> Self::Future { + self + } +} diff --git a/src/future/mod.rs b/src/future/mod.rs index 1f67e1aa..8b51a6a5 100644 --- a/src/future/mod.rs +++ b/src/future/mod.rs @@ -4,63 +4,64 @@ //! //! Often it's desireable to await multiple futures as if it was a single //! future. The `join` family of operations converts multiple futures into a -//! single future that returns all of their outputs. The `select` family of +//! single future that returns all of their outputs. The `race` family of //! operations converts multiple future into a single future that returns the //! first output. //! -//! For operating on futures the following macros can be used: +//! For operating on futures the following functions can be used: //! -//! | Name | Return signature | When does it return? | -//! | --- | --- | --- | -//! | `future::join` | `(T1, T2)` | Wait for all to complete -//! | `future::select` | `T` | Return on first value +//! | Name | Return signature | When does it return? | +//! | --- | --- | --- | +//! | [`Future::join`] | `(T1, T2)` | Wait for all to complete +//! | [`Future::race`] | `T` | Return on first value //! //! ## Fallible Futures Concurrency //! //! For operating on futures that return `Result` additional `try_` variants of -//! the macros mentioned before can be used. These macros are aware of `Result`, +//! the functions mentioned before can be used. These functions are aware of `Result`, //! and will behave slightly differently from their base variants. //! //! In the case of `try_join`, if any of the futures returns `Err` all //! futures are dropped and an error is returned. This is referred to as //! "short-circuiting". //! -//! In the case of `try_select`, instead of returning the first future that +//! In the case of `try_race`, instead of returning the first future that //! completes it returns the first future that _successfully_ completes. This -//! means `try_select` will keep going until any one of the futures returns +//! means `try_race` will keep going until any one of the futures returns //! `Ok`, or _all_ futures have returned `Err`. //! -//! However sometimes it can be useful to use the base variants of the macros +//! However sometimes it can be useful to use the base variants of the functions //! even on futures that return `Result`. Here is an overview of operations that //! work on `Result`, and their respective semantics: //! -//! | Name | Return signature | When does it return? | -//! | --- | --- | --- | -//! | `future::join` | `(Result, Result)` | Wait for all to complete -//! | `future::try_join` | `Result<(T1, T2), E>` | Return on first `Err`, wait for all to complete -//! | `future::select` | `Result` | Return on first value -//! | `future::try_select` | `Result` | Return on first `Ok`, reject on last Err - -#[doc(inline)] -pub use std::future::Future; - -#[doc(inline)] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -pub use async_macros::{join, select, try_join, try_select}; - -use cfg_if::cfg_if; +//! | Name | Return signature | When does it return? | +//! | --- | --- | --- | +//! | [`Future::join`] | `(Result, Result)` | Wait for all to complete +//! | [`Future::try_join`] | `Result<(T1, T2), E>` | Return on first `Err`, wait for all to complete +//! | [`Future::race`] | `Result` | Return on first value +//! | [`Future::try_race`] | `Result` | Return on first `Ok`, reject on last Err +//! +//! [`Future::join`]: trait.Future.html#method.join +//! [`Future::try_join`]: trait.Future.html#method.try_join +//! [`Future::race`]: trait.Future.html#method.race +//! [`Future::try_race`]: trait.Future.html#method.try_race +pub use future::Future; pub use pending::pending; pub use poll_fn::poll_fn; pub use ready::ready; +pub(crate) mod future; mod pending; mod poll_fn; mod ready; -cfg_if! { - if #[cfg(any(feature = "unstable", feature = "docs"))] { - mod timeout; - pub use timeout::{timeout, TimeoutError}; - } +cfg_default! { + pub use timeout::{timeout, TimeoutError}; + mod timeout; +} + +cfg_unstable! { + pub use into_future::IntoFuture; + mod into_future; } diff --git a/src/future/pending.rs b/src/future/pending.rs index aaee7065..968972b5 100644 --- a/src/future/pending.rs +++ b/src/future/pending.rs @@ -1,7 +1,7 @@ +use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; -use crate::future::Future; use crate::task::{Context, Poll}; /// Never resolves to a value. @@ -9,7 +9,7 @@ use crate::task::{Context, Poll}; /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # /// use std::time::Duration; /// @@ -22,7 +22,7 @@ use crate::task::{Context, Poll}; /// let res: io::Result<()> = io::timeout(dur, fut).await; /// assert!(res.is_err()); /// # -/// # }) } +/// # }) /// ``` pub async fn pending() -> T { let fut = Pending { diff --git a/src/future/poll_fn.rs b/src/future/poll_fn.rs index 116e71c6..19452640 100644 --- a/src/future/poll_fn.rs +++ b/src/future/poll_fn.rs @@ -1,6 +1,6 @@ use std::pin::Pin; +use std::future::Future; -use crate::future::Future; use crate::task::{Context, Poll}; /// Creates a new future wrapping around a function returning [`Poll`]. @@ -10,7 +10,7 @@ use crate::task::{Context, Poll}; /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # /// use async_std::future; /// use async_std::task::{Context, Poll}; @@ -21,7 +21,7 @@ use crate::task::{Context, Poll}; /// /// assert_eq!(future::poll_fn(poll_greeting).await, "hello world"); /// # -/// # }) } +/// # }) /// ``` pub async fn poll_fn(f: F) -> T where diff --git a/src/future/ready.rs b/src/future/ready.rs index 04f37b87..65cba563 100644 --- a/src/future/ready.rs +++ b/src/future/ready.rs @@ -7,13 +7,13 @@ /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # /// use async_std::future; /// /// assert_eq!(future::ready(10).await, 10); /// # -/// # }) } +/// # }) /// ``` pub async fn ready(val: T) -> T { val diff --git a/src/future/timeout.rs b/src/future/timeout.rs index aa88f646..ff87ae4f 100644 --- a/src/future/timeout.rs +++ b/src/future/timeout.rs @@ -2,10 +2,11 @@ use std::error::Error; use std::fmt; use std::pin::Pin; use std::time::Duration; +use std::future::Future; use futures_timer::Delay; +use pin_project_lite::pin_project; -use crate::future::Future; use crate::task::{Context, Poll}; /// Awaits a future or times out after a duration of time. @@ -28,8 +29,6 @@ use crate::task::{Context, Poll}; /// # /// # Ok(()) }) } /// ``` -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -#[cfg(any(feature = "unstable", feature = "docs"))] pub async fn timeout(dur: Duration, f: F) -> Result where F: Future, @@ -41,26 +40,24 @@ where f.await } -/// A future that times out after a duration of time. -#[doc(hidden)] -#[allow(missing_debug_implementations)] -struct TimeoutFuture { - future: F, - delay: Delay, -} - -impl TimeoutFuture { - pin_utils::unsafe_pinned!(future: F); - pin_utils::unsafe_pinned!(delay: Delay); +pin_project! { + /// A future that times out after a duration of time. + struct TimeoutFuture { + #[pin] + future: F, + #[pin] + delay: Delay, + } } impl Future for TimeoutFuture { type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.as_mut().future().poll(cx) { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + match this.future.poll(cx) { Poll::Ready(v) => Poll::Ready(Ok(v)), - Poll::Pending => match self.delay().poll(cx) { + Poll::Pending => match this.delay.poll(cx) { Poll::Ready(_) => Poll::Ready(Err(TimeoutError { _private: () })), Poll::Pending => Poll::Pending, }, @@ -69,8 +66,6 @@ impl Future for TimeoutFuture { } /// An error returned when a future times out. -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -#[cfg(any(feature = "unstable", feature = "docs"))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct TimeoutError { _private: (), diff --git a/src/io/buf_read/lines.rs b/src/io/buf_read/lines.rs index 6cb4a076..c60529cd 100644 --- a/src/io/buf_read/lines.rs +++ b/src/io/buf_read/lines.rs @@ -2,50 +2,55 @@ use std::mem; use std::pin::Pin; use std::str; +use pin_project_lite::pin_project; + use super::read_until_internal; use crate::io::{self, BufRead}; use crate::stream::Stream; use crate::task::{Context, Poll}; -/// A stream of lines in a byte stream. -/// -/// This stream is created by the [`lines`] method on types that implement [`BufRead`]. -/// -/// This type is an async version of [`std::io::Lines`]. -/// -/// [`lines`]: trait.BufRead.html#method.lines -/// [`BufRead`]: trait.BufRead.html -/// [`std::io::Lines`]: https://doc.rust-lang.org/std/io/struct.Lines.html -#[derive(Debug)] -pub struct Lines { - pub(crate) reader: R, - pub(crate) buf: String, - pub(crate) bytes: Vec, - pub(crate) read: usize, +pin_project! { + /// A stream of lines in a byte stream. + /// + /// This stream is created by the [`lines`] method on types that implement [`BufRead`]. + /// + /// This type is an async version of [`std::io::Lines`]. + /// + /// [`lines`]: trait.BufRead.html#method.lines + /// [`BufRead`]: trait.BufRead.html + /// [`std::io::Lines`]: https://doc.rust-lang.org/std/io/struct.Lines.html + #[derive(Debug)] + pub struct Lines { + #[pin] + pub(crate) reader: R, + pub(crate) buf: String, + pub(crate) bytes: Vec, + pub(crate) read: usize, + } } impl Stream for Lines { type Item = io::Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let Self { - reader, - buf, - bytes, - read, - } = unsafe { self.get_unchecked_mut() }; - let reader = unsafe { Pin::new_unchecked(reader) }; - let n = futures_core::ready!(read_line_internal(reader, cx, buf, bytes, read))?; - if n == 0 && buf.is_empty() { + let this = self.project(); + let n = futures_core::ready!(read_line_internal( + this.reader, + cx, + this.buf, + this.bytes, + this.read + ))?; + if n == 0 && this.buf.is_empty() { return Poll::Ready(None); } - if buf.ends_with('\n') { - buf.pop(); - if buf.ends_with('\r') { - buf.pop(); + if this.buf.ends_with('\n') { + this.buf.pop(); + if this.buf.ends_with('\r') { + this.buf.pop(); } } - Poll::Ready(Some(Ok(mem::replace(buf, String::new())))) + Poll::Ready(Some(Ok(mem::replace(this.buf, String::new())))) } } diff --git a/src/io/buf_read/mod.rs b/src/io/buf_read/mod.rs index d34b2ae2..d919a782 100644 --- a/src/io/buf_read/mod.rs +++ b/src/io/buf_read/mod.rs @@ -1,27 +1,23 @@ mod lines; mod read_line; mod read_until; +mod split; pub use lines::Lines; +pub use split::Split; + use read_line::ReadLineFuture; use read_until::ReadUntilFuture; use std::mem; use std::pin::Pin; -use cfg_if::cfg_if; - use crate::io; use crate::task::{Context, Poll}; -use crate::utils::extension_trait; - -cfg_if! { - if #[cfg(feature = "docs")] { - use std::ops::{Deref, DerefMut}; - } -} extension_trait! { + use std::ops::{Deref, DerefMut}; + #[doc = r#" Allows reading from a buffered byte stream. @@ -29,7 +25,7 @@ extension_trait! { [`std::io::BufRead`]. The [provided methods] do not really exist in the trait itself, but they become - available when the prelude is imported: + available when [`BufReadExt`] from the [prelude] is imported: ``` # #[allow(unused_imports)] @@ -38,10 +34,12 @@ extension_trait! { [`std::io::BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html [`futures::io::AsyncBufRead`]: - https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncBufRead.html + https://docs.rs/futures/0.3/futures/io/trait.AsyncBufRead.html [provided methods]: #provided-methods + [`BufReadExt`]: ../io/prelude/trait.BufReadExt.html + [prelude]: ../prelude/index.html "#] - pub trait BufRead [BufReadExt: futures_io::AsyncBufRead] { + pub trait BufRead { #[doc = r#" Returns the contents of the internal buffer, filling it with more data from the inner reader if it is empty. @@ -64,7 +62,14 @@ extension_trait! { should no longer be returned in calls to `read`. "#] fn consume(self: Pin<&mut Self>, amt: usize); + } + #[doc = r#" + Extension methods for [`BufRead`]. + + [`BufRead`]: ../trait.BufRead.html + "#] + pub trait BufReadExt: futures_io::AsyncBufRead { #[doc = r#" Reads all bytes into `buf` until the delimiter `byte` or EOF is reached. @@ -226,6 +231,57 @@ extension_trait! { read: 0, } } + + #[doc = r#" + Returns a stream over the contents of this reader split on the byte `byte`. + + The stream returned from this function will return instances of + [`io::Result`]`<`[`Vec`]`>`. Each vector returned will *not* have + the delimiter byte at the end. + + This function will yield errors whenever [`read_until`] would have + also yielded an error. + + [`io::Result`]: type.Result.html + [`Vec`]: ../vec/struct.Vec.html + [`read_until`]: #method.read_until + + # Examples + + [`std::io::Cursor`][`Cursor`] is a type that implements `BufRead`. In + this example, we use [`Cursor`] to iterate over all hyphen delimited + segments in a byte slice + + [`Cursor`]: struct.Cursor.html + + ``` + # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::io; + + let cursor = io::Cursor::new(b"lorem-ipsum-dolor"); + + let mut split_iter = cursor.split(b'-').map(|l| l.unwrap()); + assert_eq!(split_iter.next().await, Some(b"lorem".to_vec())); + assert_eq!(split_iter.next().await, Some(b"ipsum".to_vec())); + assert_eq!(split_iter.next().await, Some(b"dolor".to_vec())); + assert_eq!(split_iter.next().await, None); + # + # Ok(()) }) } + ``` + "#] + fn split(self, byte: u8) -> Split + where + Self: Sized, + { + Split { + reader: self, + buf: Vec::new(), + delim: byte, + read: 0, + } + } } impl BufRead for Box { diff --git a/src/io/buf_read/read_line.rs b/src/io/buf_read/read_line.rs index 29866be0..b66079bc 100644 --- a/src/io/buf_read/read_line.rs +++ b/src/io/buf_read/read_line.rs @@ -1,9 +1,9 @@ use std::mem; use std::pin::Pin; use std::str; +use std::future::Future; use super::read_until_internal; -use crate::future::Future; use crate::io::{self, BufRead}; use crate::task::{Context, Poll}; @@ -37,8 +37,12 @@ impl Future for ReadLineFuture<'_, T> { )) })) } else { - debug_assert!(buf.is_empty()); - debug_assert_eq!(*read, 0); + #[allow(clippy::debug_assert_with_mut_call)] + { + debug_assert!(buf.is_empty()); + debug_assert_eq!(*read, 0); + } + // Safety: `bytes` is a valid UTF-8 because `str::from_utf8` returned `Ok`. mem::swap(unsafe { buf.as_mut_vec() }, bytes); Poll::Ready(ret) diff --git a/src/io/buf_read/read_until.rs b/src/io/buf_read/read_until.rs index 72385abb..bda1eee9 100644 --- a/src/io/buf_read/read_until.rs +++ b/src/io/buf_read/read_until.rs @@ -1,7 +1,7 @@ use std::pin::Pin; +use std::future::Future; use super::read_until_internal; -use crate::future::Future; use crate::io::{self, BufRead}; use crate::task::{Context, Poll}; diff --git a/src/io/buf_read/split.rs b/src/io/buf_read/split.rs new file mode 100644 index 00000000..229a99b3 --- /dev/null +++ b/src/io/buf_read/split.rs @@ -0,0 +1,51 @@ +use std::mem; +use std::pin::Pin; + +use pin_project_lite::pin_project; + +use super::read_until_internal; +use crate::io::{self, BufRead}; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + /// A stream over the contents of an instance of [`BufRead`] split on a particular byte. + /// + /// This stream is created by the [`split`] method on types that implement [`BufRead`]. + /// + /// This type is an async version of [`std::io::Split`]. + /// + /// [`split`]: trait.BufRead.html#method.lines + /// [`BufRead`]: trait.BufRead.html + /// [`std::io::Split`]: https://doc.rust-lang.org/std/io/struct.Split.html + #[derive(Debug)] + pub struct Split { + #[pin] + pub(crate) reader: R, + pub(crate) buf: Vec, + pub(crate) read: usize, + pub(crate) delim: u8, + } +} + +impl Stream for Split { + type Item = io::Result>; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + let n = futures_core::ready!(read_until_internal( + this.reader, + cx, + *this.delim, + this.buf, + this.read + ))?; + if n == 0 && this.buf.is_empty() { + return Poll::Ready(None); + } + if this.buf[this.buf.len() - 1] == *this.delim { + this.buf.pop(); + } + Poll::Ready(Some(Ok(mem::replace(this.buf, vec![])))) + } +} diff --git a/src/io/buf_reader.rs b/src/io/buf_reader.rs index 13cb4cc5..e6d8e669 100644 --- a/src/io/buf_reader.rs +++ b/src/io/buf_reader.rs @@ -2,51 +2,54 @@ use std::io::{IoSliceMut, Read as _}; use std::pin::Pin; use std::{cmp, fmt}; -use crate::io::{self, BufRead, Read, Seek, SeekFrom}; -use crate::task::{Context, Poll}; +use pin_project_lite::pin_project; -const DEFAULT_CAPACITY: usize = 8 * 1024; +use crate::io::{self, BufRead, Read, Seek, SeekFrom, DEFAULT_BUF_SIZE}; +use crate::task::{Context, Poll}; -/// Adds buffering to any reader. -/// -/// It can be excessively inefficient to work directly with a [`Read`] instance. A `BufReader` -/// performs large, infrequent reads on the underlying [`Read`] and maintains an in-memory buffer -/// of the incoming byte stream. -/// -/// `BufReader` can improve the speed of programs that make *small* and *repeated* read calls to -/// the same file or network socket. It does not help when reading very large amounts at once, or -/// reading just one or a few times. It also provides no advantage when reading from a source that -/// is already in memory, like a `Vec`. -/// -/// When the `BufReader` is dropped, the contents of its buffer will be discarded. Creating -/// multiple instances of a `BufReader` on the same stream can cause data loss. -/// -/// This type is an async version of [`std::io::BufReader`]. -/// -/// [`Read`]: trait.Read.html -/// [`std::io::BufReader`]: https://doc.rust-lang.org/std/io/struct.BufReader.html -/// -/// # Examples -/// -/// ```no_run -/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { -/// # -/// use async_std::fs::File; -/// use async_std::io::BufReader; -/// use async_std::prelude::*; -/// -/// let mut file = BufReader::new(File::open("a.txt").await?); -/// -/// let mut line = String::new(); -/// file.read_line(&mut line).await?; -/// # -/// # Ok(()) }) } -/// ``` -pub struct BufReader { - inner: R, - buf: Box<[u8]>, - pos: usize, - cap: usize, +pin_project! { + /// Adds buffering to any reader. + /// + /// It can be excessively inefficient to work directly with a [`Read`] instance. A `BufReader` + /// performs large, infrequent reads on the underlying [`Read`] and maintains an in-memory buffer + /// of the incoming byte stream. + /// + /// `BufReader` can improve the speed of programs that make *small* and *repeated* read calls to + /// the same file or network socket. It does not help when reading very large amounts at once, or + /// reading just one or a few times. It also provides no advantage when reading from a source that + /// is already in memory, like a `Vec`. + /// + /// When the `BufReader` is dropped, the contents of its buffer will be discarded. Creating + /// multiple instances of a `BufReader` on the same stream can cause data loss. + /// + /// This type is an async version of [`std::io::BufReader`]. + /// + /// [`Read`]: trait.Read.html + /// [`std::io::BufReader`]: https://doc.rust-lang.org/std/io/struct.BufReader.html + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs::File; + /// use async_std::io::BufReader; + /// use async_std::prelude::*; + /// + /// let mut file = BufReader::new(File::open("a.txt").await?); + /// + /// let mut line = String::new(); + /// file.read_line(&mut line).await?; + /// # + /// # Ok(()) }) } + /// ``` + pub struct BufReader { + #[pin] + inner: R, + buf: Box<[u8]>, + pos: usize, + cap: usize, + } } impl BufReader { @@ -67,7 +70,7 @@ impl BufReader { /// # Ok(()) }) } /// ``` pub fn new(inner: R) -> BufReader { - BufReader::with_capacity(DEFAULT_CAPACITY, inner) + BufReader::with_capacity(DEFAULT_BUF_SIZE, inner) } /// Creates a new buffered reader with the specified capacity. @@ -95,10 +98,6 @@ impl BufReader { } impl BufReader { - pin_utils::unsafe_pinned!(inner: R); - pin_utils::unsafe_unpinned!(pos: usize); - pin_utils::unsafe_unpinned!(cap: usize); - /// Gets a reference to the underlying reader. /// /// It is inadvisable to directly read from the underlying reader. @@ -141,6 +140,13 @@ impl BufReader { &mut self.inner } + /// Gets a pinned mutable reference to the underlying reader. + /// + /// It is inadvisable to directly read from the underlying reader. + fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut R> { + self.project().inner + } + /// Returns a reference to the internal buffer. /// /// This function will not attempt to fill the buffer if it is empty. @@ -185,9 +191,10 @@ impl BufReader { /// Invalidates all data in the internal buffer. #[inline] - fn discard_buffer(mut self: Pin<&mut Self>) { - *self.as_mut().pos() = 0; - *self.cap() = 0; + fn discard_buffer(self: Pin<&mut Self>) { + let this = self.project(); + *this.pos = 0; + *this.cap = 0; } } @@ -201,7 +208,7 @@ impl Read for BufReader { // (larger than our internal buffer), bypass our internal buffer // entirely. if self.pos == self.cap && buf.len() >= self.buf.len() { - let res = futures_core::ready!(self.as_mut().inner().poll_read(cx, buf)); + let res = futures_core::ready!(self.as_mut().get_pin_mut().poll_read(cx, buf)); self.discard_buffer(); return Poll::Ready(res); } @@ -218,7 +225,8 @@ impl Read for BufReader { ) -> Poll> { let total_len = bufs.iter().map(|b| b.len()).sum::(); if self.pos == self.cap && total_len >= self.buf.len() { - let res = futures_core::ready!(self.as_mut().inner().poll_read_vectored(cx, bufs)); + let res = + futures_core::ready!(self.as_mut().get_pin_mut().poll_read_vectored(cx, bufs)); self.discard_buffer(); return Poll::Ready(res); } @@ -234,28 +242,23 @@ impl BufRead for BufReader { self: Pin<&'a mut Self>, cx: &mut Context<'_>, ) -> Poll> { - let Self { - inner, - buf, - cap, - pos, - } = unsafe { self.get_unchecked_mut() }; - let mut inner = unsafe { Pin::new_unchecked(inner) }; + let mut this = self.project(); // If we've reached the end of our internal buffer then we need to fetch // some more data from the underlying reader. // Branch using `>=` instead of the more correct `==` // to tell the compiler that the pos..cap slice is always valid. - if *pos >= *cap { - debug_assert!(*pos == *cap); - *cap = futures_core::ready!(inner.as_mut().poll_read(cx, buf))?; - *pos = 0; + if *this.pos >= *this.cap { + debug_assert!(*this.pos == *this.cap); + *this.cap = futures_core::ready!(this.inner.as_mut().poll_read(cx, this.buf))?; + *this.pos = 0; } - Poll::Ready(Ok(&buf[*pos..*cap])) + Poll::Ready(Ok(&this.buf[*this.pos..*this.cap])) } - fn consume(mut self: Pin<&mut Self>, amt: usize) { - *self.as_mut().pos() = cmp::min(self.pos + amt, self.cap); + fn consume(self: Pin<&mut Self>, amt: usize) { + let this = self.project(); + *this.pos = cmp::min(*this.pos + amt, *this.cap); } } @@ -305,24 +308,26 @@ impl Seek for BufReader { if let Some(offset) = n.checked_sub(remainder) { result = futures_core::ready!( self.as_mut() - .inner() + .get_pin_mut() .poll_seek(cx, SeekFrom::Current(offset)) )?; } else { // seek backwards by our remainder, and then by the offset futures_core::ready!( self.as_mut() - .inner() + .get_pin_mut() .poll_seek(cx, SeekFrom::Current(-remainder)) )?; self.as_mut().discard_buffer(); result = futures_core::ready!( - self.as_mut().inner().poll_seek(cx, SeekFrom::Current(n)) + self.as_mut() + .get_pin_mut() + .poll_seek(cx, SeekFrom::Current(n)) )?; } } else { // Seeking with Start/End doesn't care about our buffer length. - result = futures_core::ready!(self.as_mut().inner().poll_seek(cx, pos))?; + result = futures_core::ready!(self.as_mut().get_pin_mut().poll_seek(cx, pos))?; } self.discard_buffer(); Poll::Ready(Ok(result)) diff --git a/src/io/buf_writer.rs b/src/io/buf_writer.rs index 440e2705..c527d027 100644 --- a/src/io/buf_writer.rs +++ b/src/io/buf_writer.rs @@ -1,92 +1,121 @@ use std::fmt; use std::pin::Pin; -use futures_core::ready; +use pin_project_lite::pin_project; -use crate::io::{self, Seek, SeekFrom, Write}; -use crate::task::{Context, Poll}; +use crate::io::write::WriteExt; +use crate::io::{self, Seek, SeekFrom, Write, DEFAULT_BUF_SIZE}; +use crate::task::{Context, Poll, ready}; -const DEFAULT_CAPACITY: usize = 8 * 1024; +pin_project! { + /// Wraps a writer and buffers its output. + /// + /// It can be excessively inefficient to work directly with something that + /// implements [`Write`]. For example, every call to + /// [`write`][`TcpStream::write`] on [`TcpStream`] results in a system call. A + /// `BufWriter` keeps an in-memory buffer of data and writes it to an underlying + /// writer in large, infrequent batches. + /// + /// `BufWriter` can improve the speed of programs that make *small* and + /// *repeated* write calls to the same file or network socket. It does not + /// help when writing very large amounts at once, or writing just one or a few + /// times. It also provides no advantage when writing to a destination that is + /// in memory, like a `Vec`. + /// + /// Unlike the `BufWriter` type in `std`, this type does not write out the + /// contents of its buffer when it is dropped. Therefore, it is absolutely + /// critical that users explicitly flush the buffer before dropping a + /// `BufWriter`. + /// + /// This type is an async version of [`std::io::BufWriter`]. + /// + /// [`std::io::BufWriter`]: https://doc.rust-lang.org/std/io/struct.BufWriter.html + /// + /// # Examples + /// + /// Let's write the numbers one through ten to a [`TcpStream`]: + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// use async_std::net::TcpStream; + /// use async_std::prelude::*; + /// + /// let mut stream = TcpStream::connect("127.0.0.1:34254").await?; + /// + /// for i in 0..10 { + /// let arr = [i+1]; + /// stream.write(&arr).await?; + /// } + /// # + /// # Ok(()) }) } + /// ``` + /// + /// Because we're not buffering, we write each one in turn, incurring the + /// overhead of a system call per byte written. We can fix this with a + /// `BufWriter`: + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// use async_std::io::BufWriter; + /// use async_std::net::TcpStream; + /// use async_std::prelude::*; + /// + /// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").await?); + /// + /// for i in 0..10 { + /// let arr = [i+1]; + /// stream.write(&arr).await?; + /// }; + /// + /// stream.flush().await?; + /// # + /// # Ok(()) }) } + /// ``` + /// + /// By wrapping the stream with a `BufWriter`, these ten writes are all grouped + /// together by the buffer, and will all be written out in one system call when + /// the `stream` is dropped. + /// + /// [`Write`]: trait.Write.html + /// [`TcpStream::write`]: ../net/struct.TcpStream.html#method.write + /// [`TcpStream`]: ../net/struct.TcpStream.html + /// [`flush`]: trait.Write.html#tymethod.flush + pub struct BufWriter { + #[pin] + inner: W, + buf: Vec, + written: usize, + } +} -/// Wraps a writer and buffers its output. -/// -/// It can be excessively inefficient to work directly with something that -/// implements [`Write`]. For example, every call to -/// [`write`][`TcpStream::write`] on [`TcpStream`] results in a system call. A -/// `BufWriter` keeps an in-memory buffer of data and writes it to an underlying -/// writer in large, infrequent batches. -/// -/// `BufWriter` can improve the speed of programs that make *small* and -/// *repeated* write calls to the same file or network socket. It does not -/// help when writing very large amounts at once, or writing just one or a few -/// times. It also provides no advantage when writing to a destination that is -/// in memory, like a `Vec`. -/// -/// When the `BufWriter` is dropped, the contents of its buffer will be written -/// out. However, any errors that happen in the process of flushing the buffer -/// when the writer is dropped will be ignored. Code that wishes to handle such -/// errors must manually call [`flush`] before the writer is dropped. -/// -/// This type is an async version of [`std::io::BufReader`]. -/// -/// [`std::io::BufReader`]: https://doc.rust-lang.org/std/io/struct.BufReader.html +/// An error returned by `into_inner` which combines an error that +/// happened while writing out the buffer, and the buffered writer object +/// which may be used to recover from the condition. /// /// # Examples /// -/// Let's write the numbers one through ten to a [`TcpStream`]: -/// -/// ```no_run -/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { -/// use async_std::net::TcpStream; -/// use async_std::prelude::*; -/// -/// let mut stream = TcpStream::connect("127.0.0.1:34254").await?; -/// -/// for i in 0..10 { -/// let arr = [i+1]; -/// stream.write(&arr).await?; -/// } -/// # -/// # Ok(()) }) } -/// ``` -/// -/// Because we're not buffering, we write each one in turn, incurring the -/// overhead of a system call per byte written. We can fix this with a -/// `BufWriter`: -/// /// ```no_run /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { /// use async_std::io::BufWriter; /// use async_std::net::TcpStream; -/// use async_std::prelude::*; /// -/// let mut stream = BufWriter::new(TcpStream::connect("127.0.0.1:34254").await?); -/// for i in 0..10 { -/// let arr = [i+1]; -/// stream.write(&arr).await?; +/// let buf_writer = BufWriter::new(TcpStream::connect("127.0.0.1:34251").await?); +/// +/// // unwrap the TcpStream and flush the buffer +/// let stream = match buf_writer.into_inner().await { +/// Ok(s) => s, +/// Err(e) => { +/// // Here, e is an IntoInnerError +/// panic!("An error occurred"); +/// } /// }; /// # /// # Ok(()) }) } -/// ``` -/// -/// By wrapping the stream with a `BufWriter`, these ten writes are all grouped -/// together by the buffer, and will all be written out in one system call when -/// the `stream` is dropped. -/// -/// [`Write`]: trait.Write.html -/// [`TcpStream::write`]: ../net/struct.TcpStream.html#method.write -/// [`TcpStream`]: ../net/struct.TcpStream.html -/// [`flush`]: trait.Write.html#tymethod.flush -pub struct BufWriter { - inner: W, - buf: Vec, - written: usize, -} +///``` +#[derive(Debug)] +pub struct IntoInnerError(W, crate::io::Error); impl BufWriter { - pin_utils::unsafe_pinned!(inner: W); - pin_utils::unsafe_unpinned!(buf: Vec); - /// Creates a new `BufWriter` with a default buffer capacity. The default is currently 8 KB, /// but may change in the future. /// @@ -103,7 +132,7 @@ impl BufWriter { /// # Ok(()) }) } /// ``` pub fn new(inner: W) -> BufWriter { - BufWriter::with_capacity(DEFAULT_CAPACITY, inner) + BufWriter::with_capacity(DEFAULT_BUF_SIZE, inner) } /// Creates a new `BufWriter` with the specified buffer capacity. @@ -174,14 +203,41 @@ impl BufWriter { &mut self.inner } + /// Gets a pinned mutable reference to the underlying writer. + /// + /// It is inadvisable to directly write to the underlying writer. + fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut W> { + self.project().inner + } + /// Consumes BufWriter, returning the underlying writer /// /// This method will not write leftover data, it will be lost. /// For method that will attempt to write before returning the writer see [`poll_into_inner`] /// /// [`poll_into_inner`]: #method.poll_into_inner - pub fn into_inner(self) -> W { - self.inner + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// use async_std::io::BufWriter; + /// use async_std::net::TcpStream; + /// + /// let buf_writer = BufWriter::new(TcpStream::connect("127.0.0.1:34251").await?); + /// + /// // unwrap the TcpStream and flush the buffer + /// let stream = buf_writer.into_inner().await.unwrap(); + /// # + /// # Ok(()) }) } + /// ``` + pub async fn into_inner(mut self) -> Result>> + where + Self: Unpin, + { + match self.flush().await { + Err(e) => Err(IntoInnerError(self, e)), + Ok(()) => Ok(self.inner), + } } /// Returns a reference to the internally buffered data. @@ -210,16 +266,15 @@ impl BufWriter { /// /// [`LineWriter`]: struct.LineWriter.html fn poll_flush_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let Self { - inner, - buf, - written, - } = unsafe { Pin::get_unchecked_mut(self) }; - let mut inner = unsafe { Pin::new_unchecked(inner) }; - let len = buf.len(); + let mut this = self.project(); + let len = this.buf.len(); let mut ret = Ok(()); - while *written < len { - match inner.as_mut().poll_write(cx, &buf[*written..]) { + while *this.written < len { + match this + .inner + .as_mut() + .poll_write(cx, &this.buf[*this.written..]) + { Poll::Ready(Ok(0)) => { ret = Err(io::Error::new( io::ErrorKind::WriteZero, @@ -227,7 +282,7 @@ impl BufWriter { )); break; } - Poll::Ready(Ok(n)) => *written += n, + Poll::Ready(Ok(n)) => *this.written += n, Poll::Ready(Err(ref e)) if e.kind() == io::ErrorKind::Interrupted => {} Poll::Ready(Err(e)) => { ret = Err(e); @@ -236,10 +291,10 @@ impl BufWriter { Poll::Pending => return Poll::Pending, } } - if *written > 0 { - buf.drain(..*written); + if *this.written > 0 { + this.buf.drain(..*this.written); } - *written = 0; + *this.written = 0; Poll::Ready(ret) } } @@ -254,26 +309,26 @@ impl Write for BufWriter { ready!(self.as_mut().poll_flush_buf(cx))?; } if buf.len() >= self.buf.capacity() { - self.inner().poll_write(cx, buf) + self.get_pin_mut().poll_write(cx, buf) } else { - Pin::new(&mut *self.buf()).poll_write(cx, buf) + Pin::new(&mut *self.project().buf).poll_write(cx, buf) } } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.as_mut().poll_flush_buf(cx))?; - self.inner().poll_flush(cx) + self.get_pin_mut().poll_flush(cx) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.as_mut().poll_flush_buf(cx))?; - self.inner().poll_close(cx) + self.get_pin_mut().poll_close(cx) } } impl fmt::Debug for BufWriter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("BufReader") + f.debug_struct("BufWriter") .field("writer", &self.inner) .field("buf", &self.buf) .finish() @@ -290,6 +345,6 @@ impl Seek for BufWriter { pos: SeekFrom, ) -> Poll> { ready!(self.as_mut().poll_flush_buf(cx))?; - self.inner().poll_seek(cx, pos) + self.get_pin_mut().poll_seek(cx, pos) } } diff --git a/src/io/copy.rs b/src/io/copy.rs index 3840d2af..f05ed0e1 100644 --- a/src/io/copy.rs +++ b/src/io/copy.rs @@ -1,8 +1,11 @@ +use std::future::Future; use std::pin::Pin; -use crate::future::Future; +use pin_project_lite::pin_project; + use crate::io::{self, BufRead, BufReader, Read, Write}; use crate::task::{Context, Poll}; +use crate::utils::Context as _; /// Copies the entire contents of a reader into a writer. /// @@ -41,52 +44,131 @@ use crate::task::{Context, Poll}; /// # /// # Ok(()) }) } /// ``` +#[cfg(any(feature = "docs", not(feature = "unstable")))] pub async fn copy(reader: &mut R, writer: &mut W) -> io::Result where R: Read + Unpin + ?Sized, W: Write + Unpin + ?Sized, { - pub struct CopyFuture<'a, R, W: ?Sized> { - reader: R, - writer: &'a mut W, - amt: u64, + pin_project! { + struct CopyFuture { + #[pin] + reader: R, + #[pin] + writer: W, + amt: u64, + } } - impl CopyFuture<'_, R, W> { - fn project(self: Pin<&mut Self>) -> (Pin<&mut R>, Pin<&mut W>, &mut u64) { - unsafe { - let this = self.get_unchecked_mut(); - ( - Pin::new_unchecked(&mut this.reader), - Pin::new(&mut *this.writer), - &mut this.amt, - ) + impl Future for CopyFuture + where + R: BufRead, + W: Write + Unpin, + { + type Output = io::Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + loop { + let buffer = futures_core::ready!(this.reader.as_mut().poll_fill_buf(cx))?; + if buffer.is_empty() { + futures_core::ready!(this.writer.as_mut().poll_flush(cx))?; + return Poll::Ready(Ok(*this.amt)); + } + + let i = futures_core::ready!(this.writer.as_mut().poll_write(cx, buffer))?; + if i == 0 { + return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); + } + *this.amt += i as u64; + this.reader.as_mut().consume(i); } } } - impl Future for CopyFuture<'_, R, W> + let future = CopyFuture { + reader: BufReader::new(reader), + writer, + amt: 0, + }; + future.await.context(|| String::from("io::copy failed")) +} + +/// Copies the entire contents of a reader into a writer. +/// +/// This function will continuously read data from `reader` and then +/// write it into `writer` in a streaming fashion until `reader` +/// returns EOF. +/// +/// On success, the total number of bytes that were copied from +/// `reader` to `writer` is returned. +/// +/// If you’re wanting to copy the contents of one file to another and you’re +/// working with filesystem paths, see the [`fs::copy`] function. +/// +/// This function is an async version of [`std::io::copy`]. +/// +/// [`std::io::copy`]: https://doc.rust-lang.org/std/io/fn.copy.html +/// [`fs::copy`]: ../fs/fn.copy.html +/// +/// # Errors +/// +/// This function will return an error immediately if any call to `read` or +/// `write` returns an error. All instances of `ErrorKind::Interrupted` are +/// handled by this function and the underlying operation is retried. +/// +/// # Examples +/// +/// ``` +/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +/// # +/// use async_std::io; +/// +/// let mut reader: &[u8] = b"hello"; +/// let mut writer = io::stdout(); +/// +/// io::copy(&mut reader, &mut writer).await?; +/// # +/// # Ok(()) }) } +/// ``` +#[cfg(all(feature = "unstable", not(feature = "docs")))] +pub async fn copy(reader: R, writer: W) -> io::Result +where + R: Read + Unpin, + W: Write + Unpin, +{ + pin_project! { + struct CopyFuture { + #[pin] + reader: R, + #[pin] + writer: W, + amt: u64, + } + } + + impl Future for CopyFuture where R: BufRead, - W: Write + Unpin + ?Sized, + W: Write + Unpin, { type Output = io::Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let (mut reader, mut writer, amt) = self.project(); + let mut this = self.project(); loop { - let buffer = futures_core::ready!(reader.as_mut().poll_fill_buf(cx))?; + let buffer = futures_core::ready!(this.reader.as_mut().poll_fill_buf(cx))?; if buffer.is_empty() { - futures_core::ready!(writer.as_mut().poll_flush(cx))?; - return Poll::Ready(Ok(*amt)); + futures_core::ready!(this.writer.as_mut().poll_flush(cx))?; + return Poll::Ready(Ok(*this.amt)); } - let i = futures_core::ready!(writer.as_mut().poll_write(cx, buffer))?; + let i = futures_core::ready!(this.writer.as_mut().poll_write(cx, buffer))?; if i == 0 { return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); } - *amt += i as u64; - reader.as_mut().consume(i); + *this.amt += i as u64; + this.reader.as_mut().consume(i); } } } @@ -96,5 +178,5 @@ where writer, amt: 0, }; - future.await + future.await.context(|| String::from("io::copy failed")) } diff --git a/src/io/empty.rs b/src/io/empty.rs index d8d768e0..90442675 100644 --- a/src/io/empty.rs +++ b/src/io/empty.rs @@ -28,9 +28,10 @@ pub fn empty() -> Empty { /// A reader that contains no data. /// -/// This reader is constructed by the [`sink`] function. +/// This reader is created by the [`empty`] function. See its +/// documentation for more. /// -/// [`sink`]: fn.sink.html +/// [`empty`]: fn.empty.html pub struct Empty { _private: (), } diff --git a/src/io/mod.rs b/src/io/mod.rs index 7a942854..ee1289d3 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -1,59 +1,328 @@ -//! Basic input and output. +//! Traits, helpers, and type definitions for core I/O functionality. +//! +//! The `async_std::io` module contains a number of common things you'll need +//! when doing input and output. The most core part of this module is +//! the [`Read`] and [`Write`] traits, which provide the +//! most general interface for reading and writing input and output. //! //! This module is an async version of [`std::io`]. //! //! [`std::io`]: https://doc.rust-lang.org/std/io/index.html //! -//! # Examples +//! # Read and Write +//! +//! Because they are traits, [`Read`] and [`Write`] are implemented by a number +//! of other types, and you can implement them for your types too. As such, +//! you'll see a few different types of I/O throughout the documentation in +//! this module: [`File`]s, [`TcpStream`]s, and sometimes even [`Vec`]s. For +//! example, [`Read`] adds a [`read`][`Read::read`] method, which we can use on +//! [`File`]s: +//! +//! ```no_run +//! use async_std::fs::File; +//! use async_std::prelude::*; +//! +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! let mut f = File::open("foo.txt").await?; +//! let mut buffer = [0; 10]; +//! +//! // read up to 10 bytes +//! let n = f.read(&mut buffer).await?; +//! +//! println!("The bytes: {:?}", &buffer[..n]); +//! # +//! # Ok(()) }) } +//! ``` +//! +//! [`Read`] and [`Write`] are so important, implementors of the two traits have a +//! nickname: readers and writers. So you'll sometimes see 'a reader' instead +//! of 'a type that implements the [`Read`] trait'. Much easier! +//! +//! ## Seek and BufRead +//! +//! Beyond that, there are two important traits that are provided: [`Seek`] +//! and [`BufRead`]. Both of these build on top of a reader to control +//! how the reading happens. [`Seek`] lets you control where the next byte is +//! coming from: +//! +//! ```no_run +//! use async_std::fs::File; +//! use async_std::io::SeekFrom; +//! use async_std::prelude::*; +//! +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! let mut f = File::open("foo.txt").await?; +//! let mut buffer = [0; 10]; +//! +//! // skip to the last 10 bytes of the file +//! f.seek(SeekFrom::End(-10)).await?; +//! +//! // read up to 10 bytes +//! let n = f.read(&mut buffer).await?; +//! +//! println!("The bytes: {:?}", &buffer[..n]); +//! # +//! # Ok(()) }) } +//! ``` +//! +//! [`BufRead`] uses an internal buffer to provide a number of other ways to read, but +//! to show it off, we'll need to talk about buffers in general. Keep reading! +//! +//! ## BufReader and BufWriter +//! +//! Byte-based interfaces are unwieldy and can be inefficient, as we'd need to be +//! making near-constant calls to the operating system. To help with this, +//! `std::io` comes with two structs, [`BufReader`] and [`BufWriter`], which wrap +//! readers and writers. The wrapper uses a buffer, reducing the number of +//! calls and providing nicer methods for accessing exactly what you want. +//! +//! For example, [`BufReader`] works with the [`BufRead`] trait to add extra +//! methods to any reader: +//! +//! ```no_run +//! use async_std::fs::File; +//! use async_std::io::BufReader; +//! use async_std::prelude::*; +//! +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! let f = File::open("foo.txt").await?; +//! let mut reader = BufReader::new(f); +//! let mut buffer = String::new(); +//! +//! // read a line into buffer +//! reader.read_line(&mut buffer).await?; +//! +//! println!("{}", buffer); +//! # +//! # Ok(()) }) } +//! ``` +//! +//! [`BufWriter`] doesn't add any new ways of writing; it just buffers every call +//! to [`write`][`Write::write`]: +//! +//! ```no_run +//! use async_std::fs::File; +//! use async_std::io::BufWriter; +//! use async_std::io::prelude::*; +//! +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! let f = File::create("foo.txt").await?; +//! { +//! let mut writer = BufWriter::new(f); +//! +//! // write a byte to the buffer +//! writer.write(&[42]).await?; +//! +//! } // the buffer is flushed once writer goes out of scope +//! # +//! # Ok(()) }) } +//! ``` +//! +//! ## Standard input and output +//! +//! A very common source of input is standard input: +//! +//! ```no_run +//! use async_std::io; +//! +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! let mut input = String::new(); +//! +//! io::stdin().read_line(&mut input).await?; +//! +//! println!("You typed: {}", input.trim()); +//! # +//! # Ok(()) }) } +//! ``` +//! +//! Note that you cannot use the [`?` operator] in functions that do not return +//! a [`Result`][`Result`]. Instead, you can call [`.unwrap()`] +//! or `match` on the return value to catch any possible errors: +//! +//! ```no_run +//! use async_std::io; +//! +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! let mut input = String::new(); +//! +//! io::stdin().read_line(&mut input).await.unwrap(); +//! # +//! # Ok(()) }) } +//! ``` +//! +//! And a very common source of output is standard output: +//! +//! ```no_run +//! use async_std::io; +//! use async_std::io::prelude::*; +//! +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! io::stdout().write(&[42]).await?; +//! # +//! # Ok(()) }) } +//! ``` +//! +//! Of course, using [`io::stdout`] directly is less common than something like +//! [`println!`]. +//! +//! ## Iterator types //! -//! Read a line from the standard input: +//! A large number of the structures provided by `std::io` are for various +//! ways of iterating over I/O. For example, [`Lines`] is used to split over +//! lines: //! //! ```no_run +//! use async_std::fs::File; +//! use async_std::io::BufReader; +//! use async_std::prelude::*; +//! //! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { //! # +//! let f = File::open("foo.txt").await?; +//! let reader = BufReader::new(f); +//! +//! let mut lines = reader.lines(); +//! while let Some(line) = lines.next().await { +//! println!("{}", line?); +//! } +//! # +//! # Ok(()) }) } +//! ``` +//! +//! ## Functions +//! +//! There are a number of [functions][functions-list] that offer access to various +//! features. For example, we can use three of these functions to copy everything +//! from standard input to standard output: +//! +//! ```no_run +//! use async_std::io; +//! +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! io::copy(&mut io::stdin(), &mut io::stdout()).await?; +//! # +//! # Ok(()) }) } +//! ``` +//! +//! [functions-list]: #functions-1 +//! +//! ## io::Result +//! +//! Last, but certainly not least, is [`io::Result`]. This type is used +//! as the return type of many `std::io` functions that can cause an error, and +//! can be returned from your own functions as well. Many of the examples in this +//! module use the [`?` operator]: +//! +//! ``` +//! #![allow(dead_code)] //! use async_std::io; //! -//! let stdin = io::stdin(); -//! let mut line = String::new(); -//! stdin.read_line(&mut line).await?; -//! # -//! # Ok(()) }) } -//! ``` - -#[doc(inline)] -pub use std::io::{Error, ErrorKind, IoSlice, IoSliceMut, Result, SeekFrom}; - -pub use buf_read::{BufRead, Lines}; -pub use buf_reader::BufReader; -pub use buf_writer::BufWriter; -pub use copy::copy; -pub use cursor::Cursor; -pub use empty::{empty, Empty}; -pub use read::Read; -pub use repeat::{repeat, Repeat}; -pub use seek::Seek; -pub use sink::{sink, Sink}; -pub use stderr::{stderr, Stderr}; -pub use stdin::{stdin, Stdin}; -pub use stdout::{stdout, Stdout}; -pub use timeout::timeout; -pub use write::Write; - -pub mod prelude; - -pub(crate) mod buf_read; -pub(crate) mod read; -pub(crate) mod seek; -pub(crate) mod write; - -mod buf_reader; -mod buf_writer; -mod copy; -mod cursor; -mod empty; -mod repeat; -mod sink; -mod stderr; -mod stdin; -mod stdout; -mod timeout; +//! async fn read_input() -> io::Result<()> { +//! let mut input = String::new(); +//! +//! io::stdin().read_line(&mut input).await?; +//! +//! println!("You typed: {}", input.trim()); +//! +//! Ok(()) +//! } +//! ``` +//! +//! The return type of `read_input`, [`io::Result<()>`][`io::Result`], is a very +//! common type for functions which don't have a 'real' return value, but do want to +//! return errors if they happen. In this case, the only purpose of this function is +//! to read the line and print it, so we use `()`. +//! +//! ## Platform-specific behavior +//! +//! Many I/O functions throughout the standard library are documented to indicate +//! what various library or syscalls they are delegated to. This is done to help +//! applications both understand what's happening under the hood as well as investigate +//! any possibly unclear semantics. Note, however, that this is informative, not a binding +//! contract. The implementation of many of these functions are subject to change over +//! time and may call fewer or more syscalls/library functions. +//! +//! [`Read`]: trait.Read.html +//! [`Write`]: trait.Write.html +//! [`Seek`]: trait.Seek.html +//! [`BufRead`]: trait.BufRead.html +//! [`File`]: ../fs/struct.File.html +//! [`TcpStream`]: ../net/struct.TcpStream.html +//! [`Vec`]: ../vec/struct.Vec.html +//! [`BufReader`]: struct.BufReader.html +//! [`BufWriter`]: struct.BufWriter.html +//! [`Write::write`]: trait.Write.html#tymethod.write +//! [`io::stdout`]: fn.stdout.html +//! [`println!`]: ../macro.println.html +//! [`Lines`]: struct.Lines.html +//! [`io::Result`]: type.Result.html +//! [`?` operator]: https://doc.rust-lang.org/stable/book/appendix-02-operators.html +//! [`Read::read`]: trait.Read.html#tymethod.read +//! [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html +//! [`.unwrap()`]: https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap + +const DEFAULT_BUF_SIZE: usize = 8 * 1024; + +cfg_std! { + #[doc(inline)] + pub use std::io::{Error, ErrorKind, IoSlice, IoSliceMut, Result, SeekFrom}; + + pub use buf_read::{BufRead, Lines}; + pub use buf_reader::BufReader; + pub use buf_writer::{BufWriter, IntoInnerError}; + pub use copy::copy; + pub use cursor::Cursor; + pub use empty::{empty, Empty}; + pub use read::Read; + pub use repeat::{repeat, Repeat}; + pub use seek::Seek; + pub use sink::{sink, Sink}; + pub use write::Write; + + pub mod prelude; + + pub(crate) mod buf_read; + pub(crate) mod read; + pub(crate) mod seek; + pub(crate) mod write; + pub(crate) mod utils; + + mod buf_reader; + mod buf_writer; + mod copy; + mod cursor; + mod empty; + mod repeat; + mod sink; +} + +cfg_default! { + // For use in the print macros. + #[doc(hidden)] + pub use stdio::{_eprint, _print}; + + pub use stderr::{stderr, Stderr}; + pub use stdin::{stdin, Stdin}; + pub use stdout::{stdout, Stdout}; + pub use timeout::timeout; + + mod timeout; + mod stderr; + mod stdin; + mod stdio; + mod stdout; +} + +cfg_unstable! { + pub use stderr::StderrLock; + pub use stdin::StdinLock; + pub use stdout::StdoutLock; +} diff --git a/src/io/prelude.rs b/src/io/prelude.rs index fb1b9456..c90b289a 100644 --- a/src/io/prelude.rs +++ b/src/io/prelude.rs @@ -1,4 +1,4 @@ -//! The async I/O Prelude +//! The async I/O prelude. //! //! The purpose of this module is to alleviate imports of many common I/O traits //! by adding a glob import to the top of I/O heavy modules: @@ -17,11 +17,11 @@ pub use crate::io::Seek; #[doc(no_inline)] pub use crate::io::Write; -#[doc(hidden)] -pub use crate::io::buf_read::BufReadExt as _; -#[doc(hidden)] -pub use crate::io::read::ReadExt as _; -#[doc(hidden)] -pub use crate::io::seek::SeekExt as _; -#[doc(hidden)] -pub use crate::io::write::WriteExt as _; +#[doc(inline)] +pub use crate::io::buf_read::BufReadExt; +#[doc(inline)] +pub use crate::io::read::ReadExt; +#[doc(inline)] +pub use crate::io::seek::SeekExt; +#[doc(inline)] +pub use crate::io::write::WriteExt; diff --git a/src/io/read/chain.rs b/src/io/read/chain.rs index 09517cca..335cac25 100644 --- a/src/io/read/chain.rs +++ b/src/io/read/chain.rs @@ -1,20 +1,25 @@ -use crate::io::IoSliceMut; use std::fmt; use std::pin::Pin; -use crate::io::{self, BufRead, Read}; +use pin_project_lite::pin_project; + +use crate::io::{self, BufRead, IoSliceMut, Read}; use crate::task::{Context, Poll}; -/// Adaptor to chain together two readers. -/// -/// This struct is generally created by calling [`chain`] on a reader. -/// Please see the documentation of [`chain`] for more details. -/// -/// [`chain`]: trait.Read.html#method.chain -pub struct Chain { - pub(crate) first: T, - pub(crate) second: U, - pub(crate) done_first: bool, +pin_project! { + /// Adaptor to chain together two readers. + /// + /// This struct is generally created by calling [`chain`] on a reader. + /// Please see the documentation of [`chain`] for more details. + /// + /// [`chain`]: trait.Read.html#method.chain + pub struct Chain { + #[pin] + pub(crate) first: T, + #[pin] + pub(crate) second: U, + pub(crate) done_first: bool, + } } impl Chain { @@ -98,76 +103,64 @@ impl fmt::Debug for Chain { } } -impl Read for Chain { +impl Read for Chain { fn poll_read( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - if !self.done_first { - let rd = Pin::new(&mut self.first); - - match futures_core::ready!(rd.poll_read(cx, buf)) { - Ok(0) if !buf.is_empty() => self.done_first = true, + let this = self.project(); + if !*this.done_first { + match futures_core::ready!(this.first.poll_read(cx, buf)) { + Ok(0) if !buf.is_empty() => *this.done_first = true, Ok(n) => return Poll::Ready(Ok(n)), Err(err) => return Poll::Ready(Err(err)), } } - let rd = Pin::new(&mut self.second); - rd.poll_read(cx, buf) + this.second.poll_read(cx, buf) } fn poll_read_vectored( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>], ) -> Poll> { - if !self.done_first { - let rd = Pin::new(&mut self.first); - - match futures_core::ready!(rd.poll_read_vectored(cx, bufs)) { - Ok(0) if !bufs.is_empty() => self.done_first = true, + let this = self.project(); + if !*this.done_first { + match futures_core::ready!(this.first.poll_read_vectored(cx, bufs)) { + Ok(0) if !bufs.is_empty() => *this.done_first = true, Ok(n) => return Poll::Ready(Ok(n)), Err(err) => return Poll::Ready(Err(err)), } } - let rd = Pin::new(&mut self.second); - rd.poll_read_vectored(cx, bufs) + this.second.poll_read_vectored(cx, bufs) } } -impl BufRead for Chain { +impl BufRead for Chain { fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let Self { - first, - second, - done_first, - } = unsafe { self.get_unchecked_mut() }; - - if !*done_first { - let first = unsafe { Pin::new_unchecked(first) }; - match futures_core::ready!(first.poll_fill_buf(cx)) { + let this = self.project(); + if !*this.done_first { + match futures_core::ready!(this.first.poll_fill_buf(cx)) { Ok(buf) if buf.is_empty() => { - *done_first = true; + *this.done_first = true; } Ok(buf) => return Poll::Ready(Ok(buf)), Err(err) => return Poll::Ready(Err(err)), } } - let second = unsafe { Pin::new_unchecked(second) }; - second.poll_fill_buf(cx) + this.second.poll_fill_buf(cx) } - fn consume(mut self: Pin<&mut Self>, amt: usize) { - if !self.done_first { - let rd = Pin::new(&mut self.first); - rd.consume(amt) + fn consume(self: Pin<&mut Self>, amt: usize) { + let this = self.project(); + if !*this.done_first { + this.first.consume(amt) } else { - let rd = Pin::new(&mut self.second); - rd.consume(amt) + this.second.consume(amt) } } } diff --git a/src/io/read/mod.rs b/src/io/read/mod.rs index 6fd95c12..0d7f4dcc 100644 --- a/src/io/read/mod.rs +++ b/src/io/read/mod.rs @@ -13,23 +13,17 @@ use read_to_end::{read_to_end_internal, ReadToEndFuture}; use read_to_string::ReadToStringFuture; use read_vectored::ReadVectoredFuture; -use cfg_if::cfg_if; use std::mem; use crate::io::IoSliceMut; -use crate::utils::extension_trait; -cfg_if! { - if #[cfg(feature = "docs")] { - use std::pin::Pin; - use std::ops::{Deref, DerefMut}; +extension_trait! { + use std::pin::Pin; + use std::ops::{Deref, DerefMut}; - use crate::io; - use crate::task::{Context, Poll}; - } -} + use crate::io; + use crate::task::{Context, Poll}; -extension_trait! { #[doc = r#" Allows reading from a byte stream. @@ -37,7 +31,7 @@ extension_trait! { [`std::io::Read`]. Methods other than [`poll_read`] and [`poll_read_vectored`] do not really exist in the - trait itself, but they become available when the prelude is imported: + trait itself, but they become available when [`ReadExt`] from the [prelude] is imported: ``` # #[allow(unused_imports)] @@ -46,11 +40,13 @@ extension_trait! { [`std::io::Read`]: https://doc.rust-lang.org/std/io/trait.Read.html [`futures::io::AsyncRead`]: - https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncRead.html + https://docs.rs/futures/0.3/futures/io/trait.AsyncRead.html [`poll_read`]: #tymethod.poll_read [`poll_read_vectored`]: #method.poll_read_vectored + [`ReadExt`]: ../io/prelude/trait.ReadExt.html + [prelude]: ../prelude/index.html "#] - pub trait Read [ReadExt: futures_io::AsyncRead] { + pub trait Read { #[doc = r#" Attempt to read from the `AsyncRead` into `buf`. "#] @@ -70,7 +66,14 @@ extension_trait! { ) -> Poll> { unreachable!("this impl only appears in the rendered docs") } + } + + #[doc = r#" + Extension methods for [`Read`]. + [`Read`]: ../trait.Read.html + "#] + pub trait ReadExt: futures_io::AsyncRead { #[doc = r#" Reads some bytes from the byte stream. @@ -271,7 +274,7 @@ extension_trait! { This function returns a new instance of `Read` which will read at most `limit` bytes, after which it will always return EOF ([`Ok(0)`]). Any read errors will not count towards the number of bytes read and future - calls to [`read()`] may succeed. + calls to [`read`] may succeed. # Examples @@ -279,7 +282,7 @@ extension_trait! { [`File`]: ../fs/struct.File.html [`Ok(0)`]: ../../std/result/enum.Result.html#variant.Ok - [`read()`]: tymethod.read + [`read`]: tymethod.read ```no_run # fn main() -> std::io::Result<()> { async_std::task::block_on(async { @@ -307,34 +310,34 @@ extension_trait! { #[doc = r#" Creates a "by reference" adaptor for this instance of `Read`. - + The returned adaptor also implements `Read` and will simply borrow this current reader. - + # Examples - + [`File`][file]s implement `Read`: - + [file]: ../fs/struct.File.html - + ```no_run # fn main() -> std::io::Result<()> { async_std::task::block_on(async { # use async_std::prelude::*; use async_std::fs::File; - + let mut f = File::open("foo.txt").await?; let mut buffer = Vec::new(); let mut other_buffer = Vec::new(); - + { let reference = f.by_ref(); - + // read at most 5 bytes reference.take(5).read_to_end(&mut buffer).await?; - + } // drop our &mut reference so we can use f again - + // original file still usable, read the rest f.read_to_end(&mut other_buffer).await?; # @@ -346,27 +349,27 @@ extension_trait! { #[doc = r#" Transforms this `Read` instance to a `Stream` over its bytes. - + The returned type implements `Stream` where the `Item` is `Result`. The yielded item is `Ok` if a byte was successfully read and `Err` otherwise. EOF is mapped to returning `None` from this iterator. - + # Examples - + [`File`][file]s implement `Read`: - + [file]: ../fs/struct.File.html - + ```no_run # fn main() -> std::io::Result<()> { async_std::task::block_on(async { # use async_std::prelude::*; use async_std::fs::File; - + let f = File::open("foo.txt").await?; let mut s = f.bytes(); - + while let Some(byte) = s.next().await { println!("{}", byte.unwrap()); } @@ -380,29 +383,29 @@ extension_trait! { #[doc = r#" Creates an adaptor which will chain this stream with another. - + The returned `Read` instance will first read all bytes from this object until EOF is encountered. Afterwards the output is equivalent to the output of `next`. - + # Examples - + [`File`][file]s implement `Read`: - + [file]: ../fs/struct.File.html - + ```no_run # fn main() -> std::io::Result<()> { async_std::task::block_on(async { # use async_std::prelude::*; use async_std::fs::File; - + let f1 = File::open("foo.txt").await?; let f2 = File::open("bar.txt").await?; - + let mut handle = f1.chain(f2); let mut buffer = String::new(); - + // read the value into a String. We could use any Read method here, // this is just one example. handle.read_to_string(&mut buffer).await?; diff --git a/src/io/read/read.rs b/src/io/read/read.rs index c46aff66..0ba04e57 100644 --- a/src/io/read/read.rs +++ b/src/io/read/read.rs @@ -1,6 +1,6 @@ use std::pin::Pin; +use std::future::Future; -use crate::future::Future; use crate::io::{self, Read}; use crate::task::{Context, Poll}; diff --git a/src/io/read/read_exact.rs b/src/io/read/read_exact.rs index c970f431..71cf004d 100644 --- a/src/io/read/read_exact.rs +++ b/src/io/read/read_exact.rs @@ -1,7 +1,7 @@ use std::mem; use std::pin::Pin; +use std::future::Future; -use crate::future::Future; use crate::io::{self, Read}; use crate::task::{Context, Poll}; diff --git a/src/io/read/read_to_end.rs b/src/io/read/read_to_end.rs index d76ee8c4..c7c47b8f 100644 --- a/src/io/read/read_to_end.rs +++ b/src/io/read/read_to_end.rs @@ -1,6 +1,6 @@ use std::pin::Pin; +use std::future::Future; -use crate::future::Future; use crate::io::{self, Read}; use crate::task::{Context, Poll}; diff --git a/src/io/read/read_to_string.rs b/src/io/read/read_to_string.rs index 60773e07..5b74389e 100644 --- a/src/io/read/read_to_string.rs +++ b/src/io/read/read_to_string.rs @@ -1,9 +1,9 @@ use std::mem; use std::pin::Pin; use std::str; +use std::future::Future; use super::read_to_end_internal; -use crate::future::Future; use crate::io::{self, Read}; use crate::task::{Context, Poll}; @@ -37,7 +37,11 @@ impl Future for ReadToStringFuture<'_, T> { )) })) } else { - debug_assert!(buf.is_empty()); + #[allow(clippy::debug_assert_with_mut_call)] + { + debug_assert!(buf.is_empty()); + } + // Safety: `bytes` is a valid UTF-8 because `str::from_utf8` returned `Ok`. mem::swap(unsafe { buf.as_mut_vec() }, bytes); Poll::Ready(ret) diff --git a/src/io/read/read_vectored.rs b/src/io/read/read_vectored.rs index 8e52ba2d..b4c61b8f 100644 --- a/src/io/read/read_vectored.rs +++ b/src/io/read/read_vectored.rs @@ -1,6 +1,6 @@ use std::pin::Pin; +use std::future::Future; -use crate::future::Future; use crate::io::{self, IoSliceMut, Read}; use crate::task::{Context, Poll}; diff --git a/src/io/read/take.rs b/src/io/read/take.rs index def4e240..09b02c2f 100644 --- a/src/io/read/take.rs +++ b/src/io/read/take.rs @@ -1,19 +1,24 @@ use std::cmp; use std::pin::Pin; +use pin_project_lite::pin_project; + use crate::io::{self, BufRead, Read}; use crate::task::{Context, Poll}; -/// Reader adaptor which limits the bytes read from an underlying reader. -/// -/// This struct is generally created by calling [`take`] on a reader. -/// Please see the documentation of [`take`] for more details. -/// -/// [`take`]: trait.Read.html#method.take -#[derive(Debug)] -pub struct Take { - pub(crate) inner: T, - pub(crate) limit: u64, +pin_project! { + /// Reader adaptor which limits the bytes read from an underlying reader. + /// + /// This struct is generally created by calling [`take`] on a reader. + /// Please see the documentation of [`take`] for more details. + /// + /// [`take`]: trait.Read.html#method.take + #[derive(Debug)] + pub struct Take { + #[pin] + pub(crate) inner: T, + pub(crate) limit: u64, + } } impl Take { @@ -152,15 +157,15 @@ impl Take { } } -impl Read for Take { +impl Read for Take { /// Attempt to read from the `AsyncRead` into `buf`. fn poll_read( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - let Self { inner, limit } = &mut *self; - take_read_internal(Pin::new(inner), cx, buf, limit) + let this = self.project(); + take_read_internal(this.inner, cx, buf, this.limit) } } @@ -186,31 +191,30 @@ pub fn take_read_internal( } } -impl BufRead for Take { +impl BufRead for Take { fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let Self { inner, limit } = unsafe { self.get_unchecked_mut() }; - let inner = unsafe { Pin::new_unchecked(inner) }; + let this = self.project(); - if *limit == 0 { + if *this.limit == 0 { return Poll::Ready(Ok(&[])); } - match futures_core::ready!(inner.poll_fill_buf(cx)) { + match futures_core::ready!(this.inner.poll_fill_buf(cx)) { Ok(buf) => { - let cap = cmp::min(buf.len() as u64, *limit) as usize; + let cap = cmp::min(buf.len() as u64, *this.limit) as usize; Poll::Ready(Ok(&buf[..cap])) } Err(e) => Poll::Ready(Err(e)), } } - fn consume(mut self: Pin<&mut Self>, amt: usize) { + fn consume(self: Pin<&mut Self>, amt: usize) { + let this = self.project(); // Don't let callers reset the limit by passing an overlarge value - let amt = cmp::min(amt as u64, self.limit) as usize; - self.limit -= amt as u64; + let amt = cmp::min(amt as u64, *this.limit) as usize; + *this.limit -= amt as u64; - let rd = Pin::new(&mut self.inner); - rd.consume(amt); + this.inner.consume(amt); } } diff --git a/src/io/repeat.rs b/src/io/repeat.rs index a82e21be..56368179 100644 --- a/src/io/repeat.rs +++ b/src/io/repeat.rs @@ -29,7 +29,8 @@ pub fn repeat(byte: u8) -> Repeat { /// A reader which yields one byte over and over and over and over and over and... /// -/// This reader is constructed by the [`repeat`] function. +/// This reader is created by the [`repeat`] function. See its +/// documentation for more. /// /// [`repeat`]: fn.repeat.html pub struct Repeat { diff --git a/src/io/seek.rs b/src/io/seek/mod.rs similarity index 73% rename from src/io/seek.rs rename to src/io/seek/mod.rs index 274eee73..f565ca46 100644 --- a/src/io/seek.rs +++ b/src/io/seek/mod.rs @@ -1,19 +1,16 @@ -use std::pin::Pin; +mod seek; -use cfg_if::cfg_if; +use seek::SeekFuture; -use crate::future::Future; -use crate::io::{self, SeekFrom}; -use crate::task::{Context, Poll}; -use crate::utils::extension_trait; - -cfg_if! { - if #[cfg(feature = "docs")] { - use std::ops::{Deref, DerefMut}; - } -} +use crate::io::SeekFrom; extension_trait! { + use std::ops::{Deref, DerefMut}; + use std::pin::Pin; + + use crate::io; + use crate::task::{Context, Poll}; + #[doc = r#" Allows seeking through a byte stream. @@ -21,7 +18,7 @@ extension_trait! { [`std::io::Seek`]. The [provided methods] do not really exist in the trait itself, but they become - available when the prelude is imported: + available when [`SeekExt`] the [prelude] is imported: ``` # #[allow(unused_imports)] @@ -30,10 +27,12 @@ extension_trait! { [`std::io::Seek`]: https://doc.rust-lang.org/std/io/trait.Seek.html [`futures::io::AsyncSeek`]: - https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncSeek.html + https://docs.rs/futures/0.3/futures/io/trait.AsyncSeek.html [provided methods]: #provided-methods + [`SeekExt`]: ../io/prelude/trait.SeekExt.html + [prelude]: ../prelude/index.html "#] - pub trait Seek [SeekExt: futures_io::AsyncSeek] { + pub trait Seek { #[doc = r#" Attempt to seek to an offset, in bytes, in a stream. "#] @@ -42,7 +41,14 @@ extension_trait! { cx: &mut Context<'_>, pos: SeekFrom, ) -> Poll>; + } + + #[doc = r#" + Extension methods for [`Seek`]. + [`Seek`]: ../trait.Seek.html + "#] + pub trait SeekExt: futures_io::AsyncSeek { #[doc = r#" Seeks to a new position in a byte stream. @@ -70,7 +76,7 @@ extension_trait! { fn seek( &mut self, pos: SeekFrom, - ) -> impl Future> [SeekFuture<'_, Self>] + ) -> impl Future> + '_ [SeekFuture<'_, Self>] where Self: Unpin, { @@ -112,19 +118,3 @@ extension_trait! { } } } - -#[doc(hidden)] -#[allow(missing_debug_implementations)] -pub struct SeekFuture<'a, T: Unpin + ?Sized> { - seeker: &'a mut T, - pos: SeekFrom, -} - -impl Future for SeekFuture<'_, T> { - type Output = io::Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let pos = self.pos; - Pin::new(&mut *self.seeker).poll_seek(cx, pos) - } -} diff --git a/src/io/seek/seek.rs b/src/io/seek/seek.rs new file mode 100644 index 00000000..74aa93e5 --- /dev/null +++ b/src/io/seek/seek.rs @@ -0,0 +1,21 @@ +use std::pin::Pin; +use std::future::Future; + +use crate::io::{self, Seek, SeekFrom}; +use crate::task::{Context, Poll}; + +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub struct SeekFuture<'a, T: Unpin + ?Sized> { + pub(crate) seeker: &'a mut T, + pub(crate) pos: SeekFrom, +} + +impl Future for SeekFuture<'_, T> { + type Output = io::Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let pos = self.pos; + Pin::new(&mut *self.seeker).poll_seek(cx, pos) + } +} diff --git a/src/io/sink.rs b/src/io/sink.rs index faa763c6..86aeb0ae 100644 --- a/src/io/sink.rs +++ b/src/io/sink.rs @@ -25,7 +25,8 @@ pub fn sink() -> Sink { /// A writer that consumes and drops all data. /// -/// This writer is constructed by the [`sink`] function. +/// This writer is constructed by the [`sink`] function. See its documentation +/// for more. /// /// [`sink`]: fn.sink.html pub struct Sink { diff --git a/src/io/stderr.rs b/src/io/stderr.rs index 5706aa2e..5ff8a029 100644 --- a/src/io/stderr.rs +++ b/src/io/stderr.rs @@ -1,11 +1,14 @@ use std::pin::Pin; use std::sync::Mutex; +use std::future::Future; -use cfg_if::cfg_if; - -use crate::future::Future; use crate::io::{self, Write}; -use crate::task::{blocking, Context, JoinHandle, Poll}; +use crate::task::{spawn_blocking, Context, JoinHandle, Poll}; + +cfg_unstable! { + use once_cell::sync::Lazy; + use std::io::Write as _; +} /// Constructs a new handle to the standard error of the current process. /// @@ -13,6 +16,12 @@ use crate::task::{blocking, Context, JoinHandle, Poll}; /// /// [`std::io::stderr`]: https://doc.rust-lang.org/std/io/fn.stderr.html /// +/// ### Note: Windows Portability Consideration +/// +/// When operating in a console, the Windows implementation of this stream does not support +/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return +/// an error. +/// /// # Examples /// /// ```no_run @@ -36,15 +45,35 @@ pub fn stderr() -> Stderr { /// A handle to the standard error of the current process. /// -/// Created by the [`stderr`] function. +/// This writer is created by the [`stderr`] function. See its documentation for +/// more. /// -/// This type is an async version of [`std::io::Stderr`]. +/// ### Note: Windows Portability Consideration +/// +/// When operating in a console, the Windows implementation of this stream does not support +/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return +/// an error. /// /// [`stderr`]: fn.stderr.html -/// [`std::io::Stderr`]: https://doc.rust-lang.org/std/io/struct.Stderr.html #[derive(Debug)] pub struct Stderr(Mutex); +/// A locked reference to the Stderr handle. +/// +/// This handle implements the [`Write`] traits, and is constructed via the [`Stderr::lock`] +/// method. +/// +/// [`Write`]: trait.Read.html +/// [`Stderr::lock`]: struct.Stderr.html#method.lock +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[derive(Debug)] +pub struct StderrLock<'a>(std::io::StderrLock<'a>); + +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +unsafe impl Send for StderrLock<'_> {} + /// The state of the asynchronous stderr. /// /// The stderr can be either idle or busy performing an asynchronous operation. @@ -79,6 +108,35 @@ enum Operation { Flush(io::Result<()>), } +impl Stderr { + /// Locks this handle to the standard error stream, returning a writable guard. + /// + /// The lock is released when the returned lock goes out of scope. The returned guard also implements the Write trait for writing data. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::io; + /// use async_std::prelude::*; + /// + /// let stderr = io::stderr(); + /// let mut handle = stderr.lock().await; + /// + /// handle.write_all(b"hello world").await?; + /// # + /// # Ok(()) }) } + /// ``` + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + #[cfg(any(feature = "unstable", feature = "docs"))] + pub async fn lock(&self) -> StderrLock<'static> { + static STDERR: Lazy = Lazy::new(std::io::stderr); + + spawn_blocking(move || StderrLock(STDERR.lock())).await + } +} + impl Write for Stderr { fn poll_write( mut self: Pin<&mut Self>, @@ -116,7 +174,7 @@ impl Write for Stderr { inner.buf[..buf.len()].copy_from_slice(buf); // Start the operation asynchronously. - *state = State::Busy(blocking::spawn(async move { + *state = State::Busy(spawn_blocking(move || { let res = std::io::Write::write(&mut inner.stderr, &inner.buf); inner.last_op = Some(Operation::Write(res)); State::Idle(Some(inner)) @@ -144,7 +202,7 @@ impl Write for Stderr { let mut inner = opt.take().unwrap(); // Start the operation asynchronously. - *state = State::Busy(blocking::spawn(async move { + *state = State::Busy(spawn_blocking(move || { let res = std::io::Write::flush(&mut inner.stderr); inner.last_op = Some(Operation::Flush(res)); State::Idle(Some(inner)) @@ -162,35 +220,42 @@ impl Write for Stderr { } } -cfg_if! { - if #[cfg(feature = "docs")] { - use crate::os::unix::io::{AsRawFd, RawFd}; - use crate::os::windows::io::{AsRawHandle, RawHandle}; - } else if #[cfg(unix)] { - use std::os::unix::io::{AsRawFd, RawFd}; - } else if #[cfg(windows)] { - use std::os::windows::io::{AsRawHandle, RawHandle}; +cfg_unix! { + use crate::os::unix::io::{AsRawFd, RawFd}; + + impl AsRawFd for Stderr { + fn as_raw_fd(&self) -> RawFd { + std::io::stderr().as_raw_fd() + } } } -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(unix, feature = "docs"))] { - impl AsRawFd for Stderr { - fn as_raw_fd(&self) -> RawFd { - std::io::stderr().as_raw_fd() - } +cfg_windows! { + use crate::os::windows::io::{AsRawHandle, RawHandle}; + + impl AsRawHandle for Stderr { + fn as_raw_handle(&self) -> RawHandle { + std::io::stderr().as_raw_handle() } } } -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(windows, feature = "docs"))] { - impl AsRawHandle for Stderr { - fn as_raw_handle(&self) -> RawHandle { - std::io::stderr().as_raw_handle() - } - } +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +impl io::Write for StderrLock<'_> { + fn poll_write( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Poll::Ready(self.0.write(buf)) + } + + fn poll_flush(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(self.0.flush()) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_flush(cx) } } diff --git a/src/io/stdin.rs b/src/io/stdin.rs index 95a77b80..369ccae4 100644 --- a/src/io/stdin.rs +++ b/src/io/stdin.rs @@ -1,11 +1,16 @@ +use std::future::Future; use std::pin::Pin; use std::sync::Mutex; -use cfg_if::cfg_if; - -use crate::future::{self, Future}; +use crate::future; use crate::io::{self, Read}; -use crate::task::{blocking, Context, JoinHandle, Poll}; +use crate::task::{spawn_blocking, Context, JoinHandle, Poll}; +use crate::utils::Context as _; + +cfg_unstable! { + use once_cell::sync::Lazy; + use std::io::Read as _; +} /// Constructs a new handle to the standard input of the current process. /// @@ -13,6 +18,12 @@ use crate::task::{blocking, Context, JoinHandle, Poll}; /// /// [`std::io::stdin`]: https://doc.rust-lang.org/std/io/fn.stdin.html /// +/// ### Note: Windows Portability Consideration +/// +/// When operating in a console, the Windows implementation of this stream does not support +/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return +/// an error. +/// /// # Examples /// /// ```no_run @@ -37,15 +48,34 @@ pub fn stdin() -> Stdin { /// A handle to the standard input of the current process. /// -/// Created by the [`stdin`] function. +/// This reader is created by the [`stdin`] function. See its documentation for +/// more. /// -/// This type is an async version of [`std::io::Stdin`]. +/// ### Note: Windows Portability Consideration +/// +/// When operating in a console, the Windows implementation of this stream does not support +/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return +/// an error. /// /// [`stdin`]: fn.stdin.html -/// [`std::io::Stdin`]: https://doc.rust-lang.org/std/io/struct.Stdin.html #[derive(Debug)] pub struct Stdin(Mutex); +/// A locked reference to the Stdin handle. +/// +/// This handle implements the [`Read`] traits, and is constructed via the [`Stdin::lock`] method. +/// +/// [`Read`]: trait.Read.html +/// [`Stdin::lock`]: struct.Stdin.html#method.lock +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[cfg(feature = "unstable")] +#[derive(Debug)] +pub struct StdinLock<'a>(std::io::StdinLock<'a>); + +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +unsafe impl Send for StdinLock<'_> {} + /// The state of the asynchronous stdin. /// /// The stdin can be either idle or busy performing an asynchronous operation. @@ -119,7 +149,7 @@ impl Stdin { let mut inner = opt.take().unwrap(); // Start the operation asynchronously. - *state = State::Busy(blocking::spawn(async move { + *state = State::Busy(spawn_blocking(move || { inner.line.clear(); let res = inner.stdin.read_line(&mut inner.line); inner.last_op = Some(Operation::ReadLine(res)); @@ -133,6 +163,36 @@ impl Stdin { } }) .await + .context(|| String::from("could not read line on stdin")) + } + + /// Locks this handle to the standard input stream, returning a readable guard. + /// + /// The lock is released when the returned lock goes out of scope. The returned guard also implements the Read trait for accessing the underlying data. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::io; + /// use async_std::prelude::*; + /// + /// let mut buffer = String::new(); + /// + /// let stdin = io::stdin(); + /// let mut handle = stdin.lock().await; + /// + /// handle.read_to_string(&mut buffer).await?; + /// # + /// # Ok(()) }) } + /// ``` + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + #[cfg(any(feature = "unstable", feature = "docs"))] + pub async fn lock(&self) -> StdinLock<'static> { + static STDIN: Lazy = Lazy::new(std::io::stdin); + + spawn_blocking(move || StdinLock(STDIN.lock())).await } } @@ -172,7 +232,7 @@ impl Read for Stdin { } // Start the operation asynchronously. - *state = State::Busy(blocking::spawn(async move { + *state = State::Busy(spawn_blocking(move || { let res = std::io::Read::read(&mut inner.stdin, &mut inner.buf); inner.last_op = Some(Operation::Read(res)); State::Idle(Some(inner)) @@ -186,35 +246,34 @@ impl Read for Stdin { } } -cfg_if! { - if #[cfg(feature = "docs")] { - use crate::os::unix::io::{AsRawFd, RawFd}; - use crate::os::windows::io::{AsRawHandle, RawHandle}; - } else if #[cfg(unix)] { - use std::os::unix::io::{AsRawFd, RawFd}; - } else if #[cfg(windows)] { - use std::os::windows::io::{AsRawHandle, RawHandle}; +cfg_unix! { + use crate::os::unix::io::{AsRawFd, RawFd}; + + impl AsRawFd for Stdin { + fn as_raw_fd(&self) -> RawFd { + std::io::stdin().as_raw_fd() + } } } -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(unix, feature = "docs"))] { - impl AsRawFd for Stdin { - fn as_raw_fd(&self) -> RawFd { - std::io::stdin().as_raw_fd() - } +cfg_windows! { + use crate::os::windows::io::{AsRawHandle, RawHandle}; + + impl AsRawHandle for Stdin { + fn as_raw_handle(&self) -> RawHandle { + std::io::stdin().as_raw_handle() } } } -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(windows, feature = "docs"))] { - impl AsRawHandle for Stdin { - fn as_raw_handle(&self) -> RawHandle { - std::io::stdin().as_raw_handle() - } - } +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +impl Read for StdinLock<'_> { + fn poll_read( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + Poll::Ready(self.0.read(buf)) } } diff --git a/src/io/stdio.rs b/src/io/stdio.rs new file mode 100644 index 00000000..0e11e1a9 --- /dev/null +++ b/src/io/stdio.rs @@ -0,0 +1,21 @@ +//! Internal types for stdio. +//! +//! This module is a port of `libstd/io/stdio.rs`,and contains internal types for `print`/`eprint`. + +use crate::io::{stderr, stdout}; +use crate::prelude::*; +use std::fmt; + +#[doc(hidden)] +pub async fn _print(args: fmt::Arguments<'_>) { + if let Err(e) = stdout().write_fmt(args).await { + panic!("failed printing to stdout: {}", e); + } +} + +#[doc(hidden)] +pub async fn _eprint(args: fmt::Arguments<'_>) { + if let Err(e) = stderr().write_fmt(args).await { + panic!("failed printing to stderr: {}", e); + } +} diff --git a/src/io/stdout.rs b/src/io/stdout.rs index 7849f1ce..1711c090 100644 --- a/src/io/stdout.rs +++ b/src/io/stdout.rs @@ -1,11 +1,14 @@ use std::pin::Pin; use std::sync::Mutex; +use std::future::Future; -use cfg_if::cfg_if; - -use crate::future::Future; use crate::io::{self, Write}; -use crate::task::{blocking, Context, JoinHandle, Poll}; +use crate::task::{spawn_blocking, Context, JoinHandle, Poll}; + +cfg_unstable! { + use once_cell::sync::Lazy; + use std::io::Write as _; +} /// Constructs a new handle to the standard output of the current process. /// @@ -13,6 +16,12 @@ use crate::task::{blocking, Context, JoinHandle, Poll}; /// /// [`std::io::stdout`]: https://doc.rust-lang.org/std/io/fn.stdout.html /// +/// ### Note: Windows Portability Consideration +/// +/// When operating in a console, the Windows implementation of this stream does not support +/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return +/// an error. +/// /// # Examples /// /// ```no_run @@ -36,15 +45,35 @@ pub fn stdout() -> Stdout { /// A handle to the standard output of the current process. /// -/// Created by the [`stdout`] function. +/// This writer is created by the [`stdout`] function. See its documentation +/// for more. /// -/// This type is an async version of [`std::io::Stdout`]. +/// ### Note: Windows Portability Consideration +/// +/// When operating in a console, the Windows implementation of this stream does not support +/// non-UTF-8 byte sequences. Attempting to write bytes that are not valid UTF-8 will return +/// an error. /// /// [`stdout`]: fn.stdout.html -/// [`std::io::Stdout`]: https://doc.rust-lang.org/std/io/struct.Stdout.html #[derive(Debug)] pub struct Stdout(Mutex); +/// A locked reference to the Stderr handle. +/// +/// This handle implements the [`Write`] traits, and is constructed via the [`Stdout::lock`] +/// method. +/// +/// [`Write`]: trait.Read.html +/// [`Stdout::lock`]: struct.Stdout.html#method.lock +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[derive(Debug)] +pub struct StdoutLock<'a>(std::io::StdoutLock<'a>); + +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +unsafe impl Send for StdoutLock<'_> {} + /// The state of the asynchronous stdout. /// /// The stdout can be either idle or busy performing an asynchronous operation. @@ -79,6 +108,35 @@ enum Operation { Flush(io::Result<()>), } +impl Stdout { + /// Locks this handle to the standard error stream, returning a writable guard. + /// + /// The lock is released when the returned lock goes out of scope. The returned guard also implements the Write trait for writing data. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::io; + /// use async_std::prelude::*; + /// + /// let stdout = io::stdout(); + /// let mut handle = stdout.lock().await; + /// + /// handle.write_all(b"hello world").await?; + /// # + /// # Ok(()) }) } + /// ``` + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + #[cfg(any(feature = "unstable", feature = "docs"))] + pub async fn lock(&self) -> StdoutLock<'static> { + static STDOUT: Lazy = Lazy::new(std::io::stdout); + + spawn_blocking(move || StdoutLock(STDOUT.lock())).await + } +} + impl Write for Stdout { fn poll_write( mut self: Pin<&mut Self>, @@ -116,7 +174,7 @@ impl Write for Stdout { inner.buf[..buf.len()].copy_from_slice(buf); // Start the operation asynchronously. - *state = State::Busy(blocking::spawn(async move { + *state = State::Busy(spawn_blocking(move || { let res = std::io::Write::write(&mut inner.stdout, &inner.buf); inner.last_op = Some(Operation::Write(res)); State::Idle(Some(inner)) @@ -144,7 +202,7 @@ impl Write for Stdout { let mut inner = opt.take().unwrap(); // Start the operation asynchronously. - *state = State::Busy(blocking::spawn(async move { + *state = State::Busy(spawn_blocking(move || { let res = std::io::Write::flush(&mut inner.stdout); inner.last_op = Some(Operation::Flush(res)); State::Idle(Some(inner)) @@ -162,35 +220,42 @@ impl Write for Stdout { } } -cfg_if! { - if #[cfg(feature = "docs")] { - use crate::os::unix::io::{AsRawFd, RawFd}; - use crate::os::windows::io::{AsRawHandle, RawHandle}; - } else if #[cfg(unix)] { - use std::os::unix::io::{AsRawFd, RawFd}; - } else if #[cfg(windows)] { - use std::os::windows::io::{AsRawHandle, RawHandle}; +cfg_unix! { + use crate::os::unix::io::{AsRawFd, RawFd}; + + impl AsRawFd for Stdout { + fn as_raw_fd(&self) -> RawFd { + std::io::stdout().as_raw_fd() + } } } -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(unix, feature = "docs"))] { - impl AsRawFd for Stdout { - fn as_raw_fd(&self) -> RawFd { - std::io::stdout().as_raw_fd() - } +cfg_windows! { + use crate::os::windows::io::{AsRawHandle, RawHandle}; + + impl AsRawHandle for Stdout { + fn as_raw_handle(&self) -> RawHandle { + std::io::stdout().as_raw_handle() } } } -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(windows, feature = "docs"))] { - impl AsRawHandle for Stdout { - fn as_raw_handle(&self) -> RawHandle { - std::io::stdout().as_raw_handle() - } - } +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +impl Write for StdoutLock<'_> { + fn poll_write( + mut self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Poll::Ready(self.0.write(buf)) + } + + fn poll_flush(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(self.0.flush()) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_flush(cx) } } diff --git a/src/io/timeout.rs b/src/io/timeout.rs index 2d1b29e7..6e22dbf2 100644 --- a/src/io/timeout.rs +++ b/src/io/timeout.rs @@ -1,8 +1,11 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time::Duration; +use std::future::Future; -use futures_timer::TryFutureExt; +use futures_timer::Delay; +use pin_project_lite::pin_project; -use crate::future::Future; use crate::io; /// Awaits an I/O future or times out after a duration of time. @@ -33,5 +36,45 @@ pub async fn timeout(dur: Duration, f: F) -> io::Result where F: Future>, { - f.timeout(dur).await + Timeout { + timeout: Delay::new(dur), + future: f, + } + .await +} + +pin_project! { + /// Future returned by the `FutureExt::timeout` method. + #[derive(Debug)] + pub struct Timeout + where + F: Future>, + { + #[pin] + future: F, + #[pin] + timeout: Delay, + } +} + +impl Future for Timeout +where + F: Future>, +{ + type Output = io::Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + match this.future.poll(cx) { + Poll::Pending => {} + other => return other, + } + + if this.timeout.poll(cx).is_ready() { + let err = Err(io::Error::new(io::ErrorKind::TimedOut, "future timed out")); + Poll::Ready(err) + } else { + Poll::Pending + } + } } diff --git a/src/io/utils.rs b/src/io/utils.rs new file mode 100644 index 00000000..1b730645 --- /dev/null +++ b/src/io/utils.rs @@ -0,0 +1,46 @@ +use crate::utils::Context; + +/// Wrap `std::io::Error` with additional message +/// +/// Keeps the original error kind and stores the original I/O error as `source`. +impl Context for Result { + fn context(self, message: impl Fn() -> String) -> Self { + self.map_err(|e| VerboseError::wrap(e, message())) + } +} + +use std::{error::Error as StdError, fmt, io}; + +#[derive(Debug)] +pub(crate) struct VerboseError { + source: io::Error, + message: String, +} + +impl VerboseError { + pub(crate) fn wrap(source: io::Error, message: impl Into) -> io::Error { + io::Error::new( + source.kind(), + VerboseError { + source, + message: message.into(), + }, + ) + } +} + +impl fmt::Display for VerboseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message) + } +} + +impl StdError for VerboseError { + fn description(&self) -> &str { + self.source.description() + } + + fn source(&self) -> Option<&(dyn StdError + 'static)> { + Some(&self.source) + } +} diff --git a/src/io/write/flush.rs b/src/io/write/flush.rs index 08f2b5b4..590c12e8 100644 --- a/src/io/write/flush.rs +++ b/src/io/write/flush.rs @@ -1,6 +1,6 @@ use std::pin::Pin; +use std::future::Future; -use crate::future::Future; use crate::io::{self, Write}; use crate::task::{Context, Poll}; diff --git a/src/io/write/mod.rs b/src/io/write/mod.rs index 5e1ecc8b..0ed91dda 100644 --- a/src/io/write/mod.rs +++ b/src/io/write/mod.rs @@ -1,29 +1,23 @@ mod flush; mod write; mod write_all; +mod write_fmt; mod write_vectored; use flush::FlushFuture; use write::WriteFuture; use write_all::WriteAllFuture; +use write_fmt::WriteFmtFuture; use write_vectored::WriteVectoredFuture; -use cfg_if::cfg_if; +use crate::io::{self, IoSlice}; -use crate::io::IoSlice; -use crate::utils::extension_trait; +extension_trait! { + use std::pin::Pin; + use std::ops::{Deref, DerefMut}; -cfg_if! { - if #[cfg(feature = "docs")] { - use std::pin::Pin; - use std::ops::{Deref, DerefMut}; + use crate::task::{Context, Poll}; - use crate::io; - use crate::task::{Context, Poll}; - } -} - -extension_trait! { #[doc = r#" Allows writing to a byte stream. @@ -32,7 +26,7 @@ extension_trait! { Methods other than [`poll_write`], [`poll_write_vectored`], [`poll_flush`], and [`poll_close`] do not really exist in the trait itself, but they become available when - the prelude is imported: + [`WriteExt`] from the [prelude] is imported: ``` # #[allow(unused_imports)] @@ -41,13 +35,15 @@ extension_trait! { [`std::io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html [`futures::io::AsyncWrite`]: - https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncWrite.html + https://docs.rs/futures/0.3/futures/io/trait.AsyncWrite.html [`poll_write`]: #tymethod.poll_write [`poll_write_vectored`]: #method.poll_write_vectored [`poll_flush`]: #tymethod.poll_flush [`poll_close`]: #tymethod.poll_close + [`WriteExt`]: ../io/prelude/trait.WriteExt.html + [prelude]: ../prelude/index.html "#] - pub trait Write [WriteExt: futures_io::AsyncWrite] { + pub trait Write { #[doc = r#" Attempt to write bytes from `buf` into the object. "#] @@ -78,7 +74,14 @@ extension_trait! { Attempt to close the object. "#] fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; + } + + #[doc = r#" + Extension methods for [`Write`]. + [`Write`]: ../trait.Write.html + "#] + pub trait WriteExt: futures_io::AsyncWrite { #[doc = r#" Writes some bytes into the byte stream. @@ -197,6 +200,50 @@ extension_trait! { { WriteAllFuture { writer: self, buf } } + + #[doc = r#" + Writes a formatted string into this writer, returning any error encountered. + + This method will continuously call [`write`] until there is no more data to be + written or an error is returned. This future will not resolve until the entire + buffer has been successfully written or such an error occurs. + + [`write`]: #tymethod.write + + # Examples + + ```no_run + # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + # + use async_std::io::prelude::*; + use async_std::fs::File; + + let mut buffer = File::create("foo.txt").await?; + + // this call + write!(buffer, "{:.*}", 2, 1.234567).await?; + // turns into this: + buffer.write_fmt(format_args!("{:.*}", 2, 1.234567)).await?; + # + # Ok(()) }) } + ``` + "#] + fn write_fmt<'a>( + &'a mut self, + fmt: std::fmt::Arguments<'_>, + ) -> impl Future> + 'a [WriteFmtFuture<'a, Self>] + where + Self: Unpin, + { + // In order to not have to implement an async version of `fmt` including private types + // and all, we convert `Arguments` to a `Result>` and pass that to the Future. + // Doing an owned conversion saves us from juggling references. + let mut string = String::new(); + let res = std::fmt::write(&mut string, fmt) + .map(|_| string.into_bytes()) + .map_err(|_| io::Error::new(io::ErrorKind::Other, "formatter error")); + WriteFmtFuture { writer: self, res: Some(res), buffer: None, amt: 0 } + } } impl Write for Box { diff --git a/src/io/write/write.rs b/src/io/write/write.rs index da6e5c50..8f13091d 100644 --- a/src/io/write/write.rs +++ b/src/io/write/write.rs @@ -1,6 +1,6 @@ use std::pin::Pin; +use std::future::Future; -use crate::future::Future; use crate::io::{self, Write}; use crate::task::{Context, Poll}; diff --git a/src/io/write/write_all.rs b/src/io/write/write_all.rs index 5353f7ab..f04c55d6 100644 --- a/src/io/write/write_all.rs +++ b/src/io/write/write_all.rs @@ -1,7 +1,7 @@ use std::mem; use std::pin::Pin; +use std::future::Future; -use crate::future::Future; use crate::io::{self, Write}; use crate::task::{Context, Poll}; diff --git a/src/io/write/write_fmt.rs b/src/io/write/write_fmt.rs new file mode 100644 index 00000000..ec7847f2 --- /dev/null +++ b/src/io/write/write_fmt.rs @@ -0,0 +1,50 @@ +use std::pin::Pin; +use std::future::Future; + +use crate::io::{self, Write}; +use crate::task::{Context, Poll}; + +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub struct WriteFmtFuture<'a, T: Unpin + ?Sized> { + pub(crate) writer: &'a mut T, + pub(crate) res: Option>>, + pub(crate) buffer: Option>, + pub(crate) amt: u64, +} + +impl Future for WriteFmtFuture<'_, T> { + type Output = io::Result<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Process the interal Result the first time we run. + if self.buffer.is_none() { + match self.res.take().unwrap() { + Err(err) => return Poll::Ready(Err(err)), + Ok(buffer) => self.buffer = Some(buffer), + }; + } + + // Get the types from the future. + let Self { + writer, + amt, + buffer, + .. + } = &mut *self; + let buffer = buffer.as_mut().unwrap(); + + // Copy the data from the buffer into the writer until it's done. + loop { + if *amt == buffer.len() as u64 { + futures_core::ready!(Pin::new(&mut **writer).poll_flush(cx))?; + return Poll::Ready(Ok(())); + } + let i = futures_core::ready!(Pin::new(&mut **writer).poll_write(cx, buffer))?; + if i == 0 { + return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); + } + *amt += i as u64; + } + } +} diff --git a/src/io/write/write_vectored.rs b/src/io/write/write_vectored.rs index 5f8492b7..cdb49d42 100644 --- a/src/io/write/write_vectored.rs +++ b/src/io/write/write_vectored.rs @@ -1,6 +1,6 @@ use std::pin::Pin; +use std::future::Future; -use crate::future::Future; use crate::io::{self, IoSlice, Write}; use crate::task::{Context, Poll}; diff --git a/src/lib.rs b/src/lib.rs index fa4e946f..d0c87ff5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,30 +1,185 @@ -//! Async version of the Rust standard library. +//! # Async version of the Rust standard library //! -//! Modules in this crate are organized in the same way as in the standard library, except blocking +//! `async-std` is a foundation of portable Rust software, a set of minimal and battle-tested +//! shared abstractions for the [broader Rust ecosystem][crates.io]. It offers std types, like +//! [`Future`] and [`Stream`], library-defined [operations on language primitives](#primitives), +//! [standard macros](#macros), [I/O] and [multithreading], among [many other things][other]. +//! +//! `async-std` is available from [crates.io]. Once included, `async-std` can be accessed +//! in [`use`] statements through the path `async_std`, as in [`use async_std::future`]. +//! +//! [I/O]: io/index.html +//! [multithreading]: task/index.html +//! [other]: #what-is-in-the-standard-library-documentation +//! [`use`]: https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html +//! [`use async_std::future`]: future/index.html +//! [crates.io]: https://crates.io +//! [`Future`]: future/trait.Future.html +//! [`Stream`]: stream/trait.Stream.html +//! +//! # How to read this documentation +//! +//! If you already know the name of what you are looking for, the fastest way to +//! find it is to use the search +//! bar at the top of the page. +//! +//! Otherwise, you may want to jump to one of these useful sections: +//! +//! * [`async_std::*` modules](#modules) +//! * [Async macros](#macros) +//! * [The Async Prelude](prelude/index.html) +//! * [Cargo.toml feature flags](#features) +//! * [Examples](#examples) +//! +//! If this is your first time, the documentation for `async-std` is +//! written to be casually perused. Clicking on interesting things should +//! generally lead you to interesting places. Still, there are important bits +//! you don't want to miss, so read on for a tour of the `async-std` and +//! its documentation! +//! +//! Once you are familiar with the contents of `async-std` you may +//! begin to find the verbosity of the prose distracting. At this stage in your +//! development you may want to press the `[-]` button near the top of the +//! page to collapse it into a more skimmable view. +//! +//! While you are looking at that `[-]` button also notice the `[src]` +//! button. Rust's API documentation comes with the source code and you are +//! encouraged to read it. The `async-std` source is generally high +//! quality and a peek behind the curtains is often enlightening. +//! +//! Modules in this crate are organized in the same way as in `async-std`, except blocking //! functions have been replaced with async functions and threads have been replaced with //! lightweight tasks. //! -//! More information, reading materials, and other resources: +//! You can find more information, reading materials, and other resources here: +//! +//! * [The async-std website](https://async.rs/) +//! * [The async-std book](https://book.async.rs) +//! * [GitHub repository](https://github.com/async-rs/async-std) +//! * [List of code examples](https://github.com/async-rs/async-std/tree/master/examples) +//! * [Discord chat](https://discord.gg/JvZeVNe) +//! +//! # What is in the `async-std` documentation? +//! +//! First, `async-std` is divided into a number of focused +//! modules, [all listed further down this page](#modules). These modules are +//! the bedrock upon which async Rust is forged, and they have mighty names +//! like [`async_std::os`] and [`async_std::task`]. Modules' documentation +//! typically includes an overview of the module along with examples, and are +//! a smart place to start familiarizing yourself with the library. +//! +//! Second, `async-std` defines [The Async Prelude], a small collection +//! of items - mostly traits - that should be imported into every module of +//! every async crate. The traits in the prelude are pervasive, making the +//! prelude documentation a good entry point to learning about the library. +//! +//! [The Async Prelude]: prelude/index.html +//! [`async_std::os`]: os/index.html +//! [`async_std::task`]: task/index.html +//! +//! And finally, `async-std` exports a number of async macros, and +//! [lists them on this page](#macros). //! -//! * [🌐 The async-std website](https://async.rs/) -//! * [📖 The async-std book](https://book.async.rs) -//! * [🐙 GitHub repository](https://github.com/async-rs/async-std) -//! * [📒 List of code examples](https://github.com/async-rs/async-std/tree/master/examples) -//! * [💬 Discord chat](https://discord.gg/JvZeVNe) +//! # Contributing changes to the documentation +//! +//! Check out `async-std`'s contribution guidelines [here](https://async.rs/contribute). +//! The source for this documentation can be found on [GitHub](https://github.com/async-rs). +//! To contribute changes, make sure you read the guidelines first, then submit +//! pull requests for your suggested changes. +//! +//! Contributions are appreciated! If you see a part of the docs that can be +//! improved, submit a PR, or chat with us first on +//! [Discord](https://discord.gg/JvZeVNe). +//! +//! # A tour of `async-std` +//! +//! The rest of this crate documentation is dedicated to pointing out notable +//! features of `async-std`. +//! +//! ## Platform abstractions and I/O +//! +//! Besides basic data types, `async-std` is largely concerned with +//! abstracting over differences in common platforms, most notably Windows and +//! Unix derivatives. +//! +//! Common types of I/O, including [files], [TCP], [UDP], are defined in the +//! [`io`], [`fs`], and [`net`] modules. +//! +//! The [`task`] module contains `async-std`'s task abstractions. [`sync`] +//! contains further primitive shared memory types, including [`channel`], +//! which contains the channel types for message passing. +//! +//! [files]: fs/struct.File.html +//! [TCP]: net/struct.TcpStream.html +//! [UDP]: net/struct.UdpSocket.html +//! [`io`]: fs/struct.File.html +//! [`sync`]: sync/index.html +//! [`channel`]: sync/fn.channel.html +//! +//! ## Timeouts, intervals, and delays +//! +//! `async-std` provides several methods to manipulate time: +//! +//! * [`task::sleep`] to wait for a duration to pass without blocking. +//! * [`stream::interval`] for emitting an event at a set interval. +//! * [`future::timeout`] to time-out futures if they don't resolve within a +//! set interval. +//! +//! [`task::sleep`]: task/fn.sleep.html +//! [`stream::interval`]: stream/fn.interval.html +//! [`future::timeout`]: future/fn.timeout.html //! //! # Examples //! -//! Spawn a task and block the current thread on its result: +//! All examples require the [`"attributes"` feature](#features) to be enabled. +//! This feature is not enabled by default because it significantly impacts +//! compile times. See [`task::block_on`] for an alternative way to start +//! executing tasks. +//! +//! Call an async function from the main function: +//! +//! ``` +//! async fn say_hello() { +//! println!("Hello, world!"); +//! } +//! +//! #[async_std::main] +//! async fn main() { +//! say_hello().await; +//! } +//! ``` +//! +//! Await two futures concurrently, and return a tuple of their output: //! //! ``` -//! use async_std::task; +//! #[async_std::main] +//! async fn main() { +//! let a = async { 1u8 }; +//! let b = async { 2u8 }; +//! assert_eq!(a.join(b).await, (1u8, 2u8)) +//! } +//! ``` //! -//! fn main() { -//! task::block_on(async { -//! println!("Hello, world!"); -//! }) +//! Create a UDP server that echoes back each received message to the sender: +//! +//! ```no_run +//! use async_std::net::UdpSocket; +//! +//! #[async_std::main] +//! async fn main() -> std::io::Result<()> { +//! let mut socket = UdpSocket::bind("127.0.0.1:8080")?; +//! println!("Listening on {}", socket.local_addr()?); +//! +//! let mut buf = vec![0u8; 1024]; +//! +//! loop { +//! let (recv, peer) = socket.recv_from(&mut buf).await?; +//! let sent = socket.send_to(&buf[..recv], &peer).await?; +//! println!("Sent {} out of {} bytes to {}", sent, recv, peer); +//! } //! } //! ``` +//! [`task::block_on`]: task/fn.block_on.html //! //! # Features //! @@ -37,43 +192,79 @@ //! //! ```toml //! [dependencies.async-std] -//! version = "0.99" +//! version = "1.0.0" //! features = ["unstable"] //! ``` +//! +//! Items marked with +//! attributes +//! are available only when the `attributes` Cargo feature is enabled: +//! +//! ```toml +//! [dependencies.async-std] +//! version = "1.0.0" +//! features = ["attributes"] +//! ``` +//! +//! Additionally it's possible to only use the core traits and combinators by +//! only enabling the `std` Cargo feature: +//! +//! ```toml +//! [dependencies.async-std] +//! version = "1.0.0" +//! default-features = false +//! features = ["std"] +//! ``` #![cfg_attr(feature = "docs", feature(doc_cfg))] #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] +#![allow(clippy::mutex_atomic, clippy::module_inception)] #![doc(test(attr(deny(rust_2018_idioms, warnings))))] #![doc(test(attr(allow(unused_extern_crates, unused_variables))))] #![doc(html_logo_url = "https://async.rs/images/logo--hero.svg")] -#![recursion_limit = "1024"] +#![recursion_limit = "2048"] -use cfg_if::cfg_if; +#[macro_use] +mod utils; -pub mod fs; -pub mod future; -pub mod io; -pub mod net; -pub mod os; -pub mod prelude; -pub mod stream; -pub mod sync; -pub mod task; +#[cfg(feature = "attributes")] +#[cfg_attr(feature = "docs", doc(cfg(attributes)))] +#[doc(inline)] +pub use async_attributes::{main, test}; -cfg_if! { - if #[cfg(any(feature = "unstable", feature = "docs"))] { - #[cfg_attr(feature = "docs", doc(cfg(unstable)))] - pub mod path; - #[cfg_attr(feature = "docs", doc(cfg(unstable)))] - pub mod pin; +#[cfg(feature = "std")] +mod macros; + +cfg_std! { + pub mod future; + pub mod io; + pub mod os; + pub mod prelude; + pub mod stream; + pub mod sync; + pub mod task; +} - mod unit; - mod vec; - mod result; - mod option; - mod string; - mod collections; - } +cfg_default! { + pub mod fs; + pub mod path; + pub mod net; } -pub(crate) mod utils; +cfg_unstable! { + pub mod pin; + pub mod process; + + mod unit; + mod vec; + mod result; + mod option; + mod string; + mod collections; + + #[doc(inline)] + pub use std::{write, writeln}; +} diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 00000000..638a2348 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,219 @@ +/// Prints to the standard output. +/// +/// Equivalent to the [`println!`] macro except that a newline is not printed at +/// the end of the message. +/// +/// Note that stdout is frequently line-buffered by default so it may be +/// necessary to use [`io::stdout().flush()`][flush] to ensure the output is emitted +/// immediately. +/// +/// Use `print!` only for the primary output of your program. Use +/// [`eprint!`] instead to print error and progress messages. +/// +/// [`println!`]: macro.println.html +/// [flush]: io/trait.Write.html#tymethod.flush +/// [`eprint!`]: macro.eprint.html +/// +/// # Panics +/// +/// Panics if writing to `io::stdout()` fails. +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::io; +/// use async_std::prelude::*; +/// use async_std::print; +/// +/// print!("this ").await; +/// print!("will ").await; +/// print!("be ").await; +/// print!("on ").await; +/// print!("the ").await; +/// print!("same ").await; +/// print!("line ").await; +/// +/// io::stdout().flush().await.unwrap(); +/// +/// print!("this string has a newline, why not choose println! instead?\n").await; +/// +/// io::stdout().flush().await.unwrap(); +/// # +/// # }) +/// ``` +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*))) +} + +/// Prints to the standard output, with a newline. +/// +/// On all platforms, the newline is the LINE FEED character (`\n`/`U+000A`) alone +/// (no additional CARRIAGE RETURN (`\r`/`U+000D`)). +/// +/// Use the [`format!`] syntax to write data to the standard output. +/// See [`std::fmt`] for more information. +/// +/// Use `println!` only for the primary output of your program. Use +/// [`eprintln!`] instead to print error and progress messages. +/// +/// [`format!`]: macro.format.html +/// [`std::fmt`]: https://doc.rust-lang.org/std/fmt/index.html +/// [`eprintln!`]: macro.eprintln.html +/// # Panics +/// +/// Panics if writing to `io::stdout` fails. +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::println; +/// +/// println!().await; // prints just a newline +/// println!("hello there!").await; +/// println!("format {} arguments", "some").await; +/// # +/// # }) +/// ``` +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => (async { + $crate::io::_print(format_args!($($arg)*)).await; + $crate::io::_print(format_args!("\n")).await; + }) +} + +/// Prints to the standard error. +/// +/// Equivalent to the [`print!`] macro, except that output goes to +/// [`io::stderr`] instead of `io::stdout`. See [`print!`] for +/// example usage. +/// +/// Use `eprint!` only for error and progress messages. Use `print!` +/// instead for the primary output of your program. +/// +/// [`io::stderr`]: io/struct.Stderr.html +/// [`print!`]: macro.print.html +/// +/// # Panics +/// +/// Panics if writing to `io::stderr` fails. +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::eprint; +/// +/// eprint!("Error: Could not complete task").await; +/// # +/// # }) +/// ``` +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[macro_export] +macro_rules! eprint { + ($($arg:tt)*) => ($crate::io::_eprint(format_args!($($arg)*))) +} + +/// Prints to the standard error, with a newline. +/// +/// Equivalent to the [`println!`] macro, except that output goes to +/// [`io::stderr`] instead of `io::stdout`. See [`println!`] for +/// example usage. +/// +/// Use `eprintln!` only for error and progress messages. Use `println!` +/// instead for the primary output of your program. +/// +/// [`io::stderr`]: io/struct.Stderr.html +/// [`println!`]: macro.println.html +/// +/// # Panics +/// +/// Panics if writing to `io::stderr` fails. +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::eprintln; +/// +/// eprintln!("Error: Could not complete task").await; +/// # +/// # }) +/// ``` +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[macro_export] +macro_rules! eprintln { + () => (async { $crate::eprint!("\n").await; }); + ($($arg:tt)*) => ( + async { + $crate::io::_eprint(format_args!($($arg)*)).await; + $crate::io::_eprint(format_args!("\n")).await; + } + ); +} + +/// Declares task-local values. +/// +/// The macro wraps any number of static declarations and makes them task-local. Attributes and +/// visibility modifiers are allowed. +/// +/// Each declared value is of the accessor type [`LocalKey`]. +/// +/// [`LocalKey`]: task/struct.LocalKey.html +/// +/// # Examples +/// +/// ``` +/// # +/// use std::cell::Cell; +/// +/// use async_std::prelude::*; +/// use async_std::task; +/// +/// task_local! { +/// static VAL: Cell = Cell::new(5); +/// } +/// +/// task::block_on(async { +/// let v = VAL.with(|c| c.get()); +/// assert_eq!(v, 5); +/// }); +/// ``` +#[cfg(feature = "default")] +#[macro_export] +macro_rules! task_local { + () => (); + + ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr) => ( + $(#[$attr])* $vis static $name: $crate::task::LocalKey<$t> = { + #[inline] + fn __init() -> $t { + $init + } + + $crate::task::LocalKey { + __init, + __key: ::std::sync::atomic::AtomicU32::new(0), + } + }; + ); + + ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => ( + $crate::task_local!($(#[$attr])* $vis static $name: $t = $init); + $crate::task_local!($($rest)*); + ); +} diff --git a/src/net/addr.rs b/src/net/addr.rs index 64b22cfc..ea839500 100644 --- a/src/net/addr.rs +++ b/src/net/addr.rs @@ -1,26 +1,25 @@ +use std::future::Future; use std::mem; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; use std::pin::Pin; -use cfg_if::cfg_if; - -use crate::future::Future; use crate::io; -use crate::task::{blocking, Context, JoinHandle, Poll}; +use crate::task::{spawn_blocking, Context, JoinHandle, Poll}; +use crate::utils::Context as ErrorContext; + +cfg_not_docs! { + macro_rules! ret { + (impl Future, $fut:ty) => ($fut); + } +} -cfg_if! { - if #[cfg(feature = "docs")] { - #[doc(hidden)] - pub struct ImplFuture(std::marker::PhantomData); +cfg_docs! { + #[doc(hidden)] + pub struct ImplFuture(std::marker::PhantomData); - macro_rules! ret { - (impl Future, $fut:ty) => (ImplFuture<$out>); - } - } else { - macro_rules! ret { - (impl Future, $fut:ty) => ($fut); - } + macro_rules! ret { + (impl Future, $fut:ty) => (ImplFuture<$out>); } } @@ -69,6 +68,18 @@ pub enum ToSocketAddrsFuture { Done, } +/// Wrap `std::io::Error` with additional message +/// +/// Keeps the original error kind and stores the original I/O error as `source`. +impl ErrorContext for ToSocketAddrsFuture { + fn context(self, message: impl Fn() -> String) -> Self { + match self { + ToSocketAddrsFuture::Ready(res) => ToSocketAddrsFuture::Ready(res.context(message)), + x => x, + } + } +} + impl> Future for ToSocketAddrsFuture { type Output = io::Result; @@ -112,7 +123,9 @@ impl ToSocketAddrs for SocketAddrV4 { impl Future, ToSocketAddrsFuture ) { - SocketAddr::V4(*self).to_socket_addrs() + SocketAddr::V4(*self) + .to_socket_addrs() + .context(|| format!("could not resolve address `{}`", self)) } } @@ -125,7 +138,9 @@ impl ToSocketAddrs for SocketAddrV6 { impl Future, ToSocketAddrsFuture ) { - SocketAddr::V6(*self).to_socket_addrs() + SocketAddr::V6(*self) + .to_socket_addrs() + .context(|| format!("could not resolve address `{}`", self)) } } @@ -196,8 +211,10 @@ impl ToSocketAddrs for (&str, u16) { } let host = host.to_string(); - let task = blocking::spawn(async move { - std::net::ToSocketAddrs::to_socket_addrs(&(host.as_str(), port)) + let task = spawn_blocking(move || { + let addr = (host.as_str(), port); + std::net::ToSocketAddrs::to_socket_addrs(&addr) + .context(|| format!("could not resolve address `{:?}`", addr)) }); ToSocketAddrsFuture::Resolving(task) } @@ -212,13 +229,15 @@ impl ToSocketAddrs for str { impl Future, ToSocketAddrsFuture ) { - if let Some(addr) = self.parse().ok() { + if let Ok(addr) = self.parse() { return ToSocketAddrsFuture::Ready(Ok(vec![addr].into_iter())); } let addr = self.to_string(); - let task = - blocking::spawn(async move { std::net::ToSocketAddrs::to_socket_addrs(addr.as_str()) }); + let task = spawn_blocking(move || { + std::net::ToSocketAddrs::to_socket_addrs(addr.as_str()) + .context(|| format!("could not resolve address `{:?}`", addr)) + }); ToSocketAddrsFuture::Resolving(task) } } diff --git a/src/net/driver/mod.rs b/src/net/driver/mod.rs index 806acdbe..7f33e859 100644 --- a/src/net/driver/mod.rs +++ b/src/net/driver/mod.rs @@ -1,8 +1,8 @@ use std::fmt; use std::sync::{Arc, Mutex}; -use lazy_static::lazy_static; use mio::{self, Evented}; +use once_cell::sync::Lazy; use slab::Slab; use crate::io; @@ -100,25 +100,23 @@ impl Reactor { // } } -lazy_static! { - /// The state of the global networking driver. - static ref REACTOR: Reactor = { - // Spawn a thread that waits on the poller for new events and wakes up tasks blocked on I/O - // handles. - std::thread::Builder::new() - .name("async-net-driver".to_string()) - .spawn(move || { - // If the driver thread panics, there's not much we can do. It is not a - // recoverable error and there is no place to propagate it into so we just abort. - abort_on_panic(|| { - main_loop().expect("async networking thread has panicked"); - }) +/// The state of the global networking driver. +static REACTOR: Lazy = Lazy::new(|| { + // Spawn a thread that waits on the poller for new events and wakes up tasks blocked on I/O + // handles. + std::thread::Builder::new() + .name("async-std/net".to_string()) + .spawn(move || { + // If the driver thread panics, there's not much we can do. It is not a + // recoverable error and there is no place to propagate it into so we just abort. + abort_on_panic(|| { + main_loop().expect("async networking thread has panicked"); }) - .expect("cannot start a thread driving blocking tasks"); + }) + .expect("cannot start a thread driving blocking tasks"); - Reactor::new().expect("cannot initialize reactor") - }; -} + Reactor::new().expect("cannot initialize reactor") +}); /// Waits on the poller for new events and wakes up tasks blocked on I/O handles. fn main_loop() -> io::Result<()> { diff --git a/src/net/mod.rs b/src/net/mod.rs index b3ae287f..29e43090 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -1,14 +1,42 @@ //! Networking primitives for TCP/UDP communication. //! -//! For OS-specific networking primitives like Unix domain sockets, refer to the [`async_std::os`] -//! module. +//! This module provides networking functionality for the Transmission Control and User +//! Datagram Protocols, as well as types for IP and socket addresses. //! //! This module is an async version of [`std::net`]. //! +//! # Organization +//! +//! * [`TcpListener`] and [`TcpStream`] provide functionality for communication over TCP +//! * [`UdpSocket`] provides functionality for communication over UDP +//! * [`IpAddr`] represents IP addresses of either IPv4 or IPv6; [`Ipv4Addr`] and +//! [`Ipv6Addr`] are respectively IPv4 and IPv6 addresses +//! * [`SocketAddr`] represents socket addresses of either IPv4 or IPv6; [`SocketAddrV4`] +//! and [`SocketAddrV6`] are respectively IPv4 and IPv6 socket addresses +//! * [`ToSocketAddrs`] is a trait that used for generic address resolution when interacting +//! with networking objects like [`TcpListener`], [`TcpStream`] or [`UdpSocket`] +//! * Other types are return or parameter types for various methods in this module +//! +//! [`IpAddr`]: enum.IpAddr.html +//! [`Ipv4Addr`]: struct.Ipv4Addr.html +//! [`Ipv6Addr`]: struct.Ipv6Addr.html +//! [`SocketAddr`]: enum.SocketAddr.html +//! [`SocketAddrV4`]: struct.SocketAddrV4.html +//! [`SocketAddrV6`]: struct.SocketAddrV6.html +//! [`TcpListener`]: struct.TcpListener.html +//! [`TcpStream`]: struct.TcpStream.html +//! [`ToSocketAddrs`]: trait.ToSocketAddrs.html +//! [`UdpSocket`]: struct.UdpSocket.html +//! +//! # Platform-specific extensions +//! +//! APIs such as Unix domain sockets are available on certain platforms only. You can find +//! platform-specific extensions in the [`async_std::os`] module. +//! //! [`async_std::os`]: ../os/index.html //! [`std::net`]: https://doc.rust-lang.org/std/net/index.html //! -//! ## Examples +//! # Examples //! //! A simple UDP echo server: //! diff --git a/src/net/tcp/listener.rs b/src/net/tcp/listener.rs index 26e19d79..fe06a96d 100644 --- a/src/net/tcp/listener.rs +++ b/src/net/tcp/listener.rs @@ -1,13 +1,11 @@ +use std::future::Future; use std::net::SocketAddr; use std::pin::Pin; -use cfg_if::cfg_if; - -use super::TcpStream; -use crate::future::{self, Future}; +use crate::future; use crate::io; use crate::net::driver::Watcher; -use crate::net::ToSocketAddrs; +use crate::net::{TcpStream, ToSocketAddrs}; use crate::stream::Stream; use crate::task::{Context, Poll}; @@ -77,8 +75,11 @@ impl TcpListener { /// [`local_addr`]: #method.local_addr pub async fn bind(addrs: A) -> io::Result { let mut last_err = None; + let addrs = addrs + .to_socket_addrs() + .await?; - for addr in addrs.to_socket_addrs().await? { + for addr in addrs { match mio::net::TcpListener::bind(&addr) { Ok(mio_listener) => { return Ok(TcpListener { @@ -213,59 +214,46 @@ impl From for TcpListener { } } -cfg_if! { - if #[cfg(feature = "docs")] { - use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; - // use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; - } else if #[cfg(unix)] { - use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; - } else if #[cfg(windows)] { - // use std::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; - } -} +cfg_unix! { + use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(unix, feature = "docs"))] { - impl AsRawFd for TcpListener { - fn as_raw_fd(&self) -> RawFd { - self.watcher.get_ref().as_raw_fd() - } + impl AsRawFd for TcpListener { + fn as_raw_fd(&self) -> RawFd { + self.watcher.get_ref().as_raw_fd() } + } - impl FromRawFd for TcpListener { - unsafe fn from_raw_fd(fd: RawFd) -> TcpListener { - std::net::TcpListener::from_raw_fd(fd).into() - } + impl FromRawFd for TcpListener { + unsafe fn from_raw_fd(fd: RawFd) -> TcpListener { + std::net::TcpListener::from_raw_fd(fd).into() } + } - impl IntoRawFd for TcpListener { - fn into_raw_fd(self) -> RawFd { - self.watcher.into_inner().into_raw_fd() - } + impl IntoRawFd for TcpListener { + fn into_raw_fd(self) -> RawFd { + self.watcher.into_inner().into_raw_fd() } } } -#[cfg_attr(feature = "docs", doc(cfg(windows)))] -cfg_if! { - if #[cfg(any(windows, feature = "docs"))] { - // impl AsRawSocket for TcpListener { - // fn as_raw_socket(&self) -> RawSocket { - // self.raw_socket - // } - // } - // - // impl FromRawSocket for TcpListener { - // unsafe fn from_raw_socket(handle: RawSocket) -> TcpListener { - // net::TcpListener::from_raw_socket(handle).try_into().unwrap() - // } - // } - // - // impl IntoRawSocket for TcpListener { - // fn into_raw_socket(self) -> RawSocket { - // self.raw_socket - // } - // } - } +cfg_windows! { + // use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; + // + // impl AsRawSocket for TcpListener { + // fn as_raw_socket(&self) -> RawSocket { + // self.raw_socket + // } + // } + // + // impl FromRawSocket for TcpListener { + // unsafe fn from_raw_socket(handle: RawSocket) -> TcpListener { + // net::TcpListener::from_raw_socket(handle).try_into().unwrap() + // } + // } + // + // impl IntoRawSocket for TcpListener { + // fn into_raw_socket(self) -> RawSocket { + // self.raw_socket + // } + // } } diff --git a/src/net/tcp/stream.rs b/src/net/tcp/stream.rs index 10569434..41317833 100644 --- a/src/net/tcp/stream.rs +++ b/src/net/tcp/stream.rs @@ -2,14 +2,12 @@ use std::io::{IoSlice, IoSliceMut, Read as _, Write as _}; use std::net::SocketAddr; use std::pin::Pin; -use cfg_if::cfg_if; - use crate::future; use crate::io::{self, Read, Write}; use crate::net::driver::Watcher; use crate::net::ToSocketAddrs; -use crate::task::blocking; -use crate::task::{Context, Poll}; +use crate::task::{spawn_blocking, Context, Poll}; +use crate::utils::Context as _; /// A TCP stream between a local and a remote socket. /// @@ -25,9 +23,9 @@ use crate::task::{Context, Poll}; /// [`connect`]: struct.TcpStream.html#method.connect /// [accepting]: struct.TcpListener.html#method.accept /// [listener]: struct.TcpListener.html -/// [`AsyncRead`]: https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncRead.html -/// [`AsyncWrite`]: https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncWrite.html -/// [`futures::io`]: https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/index.html +/// [`AsyncRead`]: https://docs.rs/futures/0.3/futures/io/trait.AsyncRead.html +/// [`AsyncWrite`]: https://docs.rs/futures/0.3/futures/io/trait.AsyncWrite.html +/// [`futures::io`]: https://docs.rs/futures/0.3/futures/io/index.html /// [`shutdown`]: struct.TcpStream.html#method.shutdown /// [`std::net::TcpStream`]: https://doc.rust-lang.org/std/net/struct.TcpStream.html /// @@ -74,11 +72,16 @@ impl TcpStream { /// ``` pub async fn connect(addrs: A) -> io::Result { let mut last_err = None; + let addrs = addrs + .to_socket_addrs() + .await?; - for addr in addrs.to_socket_addrs().await? { - let res = blocking::spawn(async move { - let std_stream = std::net::TcpStream::connect(addr)?; - let mio_stream = mio::net::TcpStream::from_stream(std_stream)?; + for addr in addrs { + let res = spawn_blocking(move || { + let std_stream = std::net::TcpStream::connect(addr) + .context(|| format!("could not connect to {}", addr))?; + let mio_stream = mio::net::TcpStream::from_stream(std_stream) + .context(|| format!("could not open async connection to {}", addr))?; Ok(TcpStream { watcher: Watcher::new(mio_stream), }) @@ -367,59 +370,46 @@ impl From for TcpStream { } } -cfg_if! { - if #[cfg(feature = "docs")] { - use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; - // use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; - } else if #[cfg(unix)] { - use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; - } else if #[cfg(windows)] { - // use std::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; - } -} +cfg_unix! { + use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(unix, feature = "docs"))] { - impl AsRawFd for TcpStream { - fn as_raw_fd(&self) -> RawFd { - self.watcher.get_ref().as_raw_fd() - } + impl AsRawFd for TcpStream { + fn as_raw_fd(&self) -> RawFd { + self.watcher.get_ref().as_raw_fd() } + } - impl FromRawFd for TcpStream { - unsafe fn from_raw_fd(fd: RawFd) -> TcpStream { - std::net::TcpStream::from_raw_fd(fd).into() - } + impl FromRawFd for TcpStream { + unsafe fn from_raw_fd(fd: RawFd) -> TcpStream { + std::net::TcpStream::from_raw_fd(fd).into() } + } - impl IntoRawFd for TcpStream { - fn into_raw_fd(self) -> RawFd { - self.watcher.into_inner().into_raw_fd() - } + impl IntoRawFd for TcpStream { + fn into_raw_fd(self) -> RawFd { + self.watcher.into_inner().into_raw_fd() } } } -#[cfg_attr(feature = "docs", doc(cfg(windows)))] -cfg_if! { - if #[cfg(any(windows, feature = "docs"))] { - // impl AsRawSocket for TcpStream { - // fn as_raw_socket(&self) -> RawSocket { - // self.raw_socket - // } - // } - // - // impl FromRawSocket for TcpStream { - // unsafe fn from_raw_socket(handle: RawSocket) -> TcpStream { - // net::TcpStream::from_raw_socket(handle).try_into().unwrap() - // } - // } - // - // impl IntoRawSocket for TcpListener { - // fn into_raw_socket(self) -> RawSocket { - // self.raw_socket - // } - // } - } +cfg_windows! { + // use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; + // + // impl AsRawSocket for TcpStream { + // fn as_raw_socket(&self) -> RawSocket { + // self.raw_socket + // } + // } + // + // impl FromRawSocket for TcpStream { + // unsafe fn from_raw_socket(handle: RawSocket) -> TcpStream { + // net::TcpStream::from_raw_socket(handle).try_into().unwrap() + // } + // } + // + // impl IntoRawSocket for TcpListener { + // fn into_raw_socket(self) -> RawSocket { + // self.raw_socket + // } + // } } diff --git a/src/net/udp/mod.rs b/src/net/udp/mod.rs index 4588be12..7fef1ed5 100644 --- a/src/net/udp/mod.rs +++ b/src/net/udp/mod.rs @@ -1,12 +1,11 @@ use std::io; use std::net::SocketAddr; - -use cfg_if::cfg_if; use std::net::{Ipv4Addr, Ipv6Addr}; use crate::future; use crate::net::driver::Watcher; use crate::net::ToSocketAddrs; +use crate::utils::Context as _; /// A UDP socket. /// @@ -68,10 +67,13 @@ impl UdpSocket { /// # /// # Ok(()) }) } /// ``` - pub async fn bind(addr: A) -> io::Result { + pub async fn bind(addrs: A) -> io::Result { let mut last_err = None; + let addrs = addrs + .to_socket_addrs() + .await?; - for addr in addr.to_socket_addrs().await? { + for addr in addrs { match mio::net::UdpSocket::bind(&addr) { Ok(mio_socket) => { return Ok(UdpSocket { @@ -108,7 +110,10 @@ impl UdpSocket { /// # Ok(()) }) } /// ``` pub fn local_addr(&self) -> io::Result { - self.watcher.get_ref().local_addr() + self.watcher + .get_ref() + .local_addr() + .context(|| String::from("could not get local address")) } /// Sends data on the socket to the given address. @@ -153,6 +158,7 @@ impl UdpSocket { .poll_write_with(cx, |inner| inner.send_to(buf, &addr)) }) .await + .context(|| format!("could not send packet to {}", addr)) } /// Receives data from the socket. @@ -180,6 +186,17 @@ impl UdpSocket { .poll_read_with(cx, |inner| inner.recv_from(buf)) }) .await + .context(|| { + use std::fmt::Write; + + let mut error = String::from("could not receive data on "); + if let Ok(addr) = self.local_addr() { + let _ = write!(&mut error, "{}", addr); + } else { + error.push_str("socket"); + } + error + }) } /// Connects the UDP socket to a remote address. @@ -197,7 +214,7 @@ impl UdpSocket { /// ```no_run /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { /// # - /// use async_std::net::UdpSocket; + /// use async_std::net::UdpSocket; /// /// let socket = UdpSocket::bind("127.0.0.1:0").await?; /// socket.connect("127.0.0.1:8080").await?; @@ -206,8 +223,12 @@ impl UdpSocket { /// ``` pub async fn connect(&self, addrs: A) -> io::Result<()> { let mut last_err = None; + let addrs = addrs + .to_socket_addrs() + .await + .context(|| String::from("could not resolve addresses"))?; - for addr in addrs.to_socket_addrs().await? { + for addr in addrs { // TODO(stjepang): connect on the blocking pool match self.watcher.get_ref().connect(addr) { Ok(()) => return Ok(()), @@ -250,7 +271,19 @@ impl UdpSocket { /// # Ok(()) }) } /// ``` pub async fn send(&self, buf: &[u8]) -> io::Result { - future::poll_fn(|cx| self.watcher.poll_write_with(cx, |inner| inner.send(buf))).await + future::poll_fn(|cx| self.watcher.poll_write_with(cx, |inner| inner.send(buf))) + .await + .context(|| { + use std::fmt::Write; + + let mut error = String::from("could not send data on "); + if let Ok(addr) = self.local_addr() { + let _ = write!(&mut error, "{}", addr); + } else { + error.push_str("socket"); + } + error + }) } /// Receives data from the socket. @@ -273,7 +306,19 @@ impl UdpSocket { /// # Ok(()) }) } /// ``` pub async fn recv(&self, buf: &mut [u8]) -> io::Result { - future::poll_fn(|cx| self.watcher.poll_read_with(cx, |inner| inner.recv(buf))).await + future::poll_fn(|cx| self.watcher.poll_read_with(cx, |inner| inner.recv(buf))) + .await + .context(|| { + use std::fmt::Write; + + let mut error = String::from("could not receive data on "); + if let Ok(addr) = self.local_addr() { + let _ = write!(&mut error, "{}", addr); + } else { + error.push_str("socket"); + } + error + }) } /// Gets the value of the `SO_BROADCAST` option for this socket. @@ -417,7 +462,7 @@ impl UdpSocket { /// use async_std::net::UdpSocket; /// /// let socket_addr = SocketAddr::new(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0).into(), 0); - /// let mdns_addr = Ipv6Addr::new(0xFF02, 0, 0, 0, 0, 0, 0, 0x0123) ; + /// let mdns_addr = Ipv6Addr::new(0xFF02, 0, 0, 0, 0, 0, 0, 0x0123); /// let socket = UdpSocket::bind(&socket_addr).await?; /// /// socket.join_multicast_v6(&mdns_addr, 0)?; @@ -463,61 +508,46 @@ impl From for UdpSocket { } } -cfg_if! { - if #[cfg(feature = "docs")] { - use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; - // use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; - } else if #[cfg(unix)] { - use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; - } else if #[cfg(windows)] { - // use std::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; - } -} +cfg_unix! { + use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -cfg_if! { - if #[cfg(any(unix, feature = "docs"))] { - impl AsRawFd for UdpSocket { - fn as_raw_fd(&self) -> RawFd { - self.watcher.get_ref().as_raw_fd() - } + impl AsRawFd for UdpSocket { + fn as_raw_fd(&self) -> RawFd { + self.watcher.get_ref().as_raw_fd() } + } - impl FromRawFd for UdpSocket { - unsafe fn from_raw_fd(fd: RawFd) -> UdpSocket { - std::net::UdpSocket::from_raw_fd(fd).into() - } + impl FromRawFd for UdpSocket { + unsafe fn from_raw_fd(fd: RawFd) -> UdpSocket { + std::net::UdpSocket::from_raw_fd(fd).into() } + } - impl IntoRawFd for UdpSocket { - fn into_raw_fd(self) -> RawFd { - self.watcher.into_inner().into_raw_fd() - } + impl IntoRawFd for UdpSocket { + fn into_raw_fd(self) -> RawFd { + self.watcher.into_inner().into_raw_fd() } } } -#[cfg_attr(feature = "docs", doc(cfg(windows)))] -cfg_if! { - if #[cfg(any(windows, feature = "docs"))] { - // use std::os::windows::io::{AsRawSocket, FromRawSocket, IntoRawSocket, RawSocket}; - // - // impl AsRawSocket for UdpSocket { - // fn as_raw_socket(&self) -> RawSocket { - // self.raw_socket - // } - // } - // - // impl FromRawSocket for UdpSocket { - // unsafe fn from_raw_socket(handle: RawSocket) -> UdpSocket { - // net::UdpSocket::from_raw_socket(handle).into() - // } - // } - // - // impl IntoRawSocket for UdpSocket { - // fn into_raw_socket(self) -> RawSocket { - // self.raw_socket - // } - // } - } +cfg_windows! { + // use crate::os::windows::io::{AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle}; + // + // impl AsRawSocket for UdpSocket { + // fn as_raw_socket(&self) -> RawSocket { + // self.raw_socket + // } + // } + // + // impl FromRawSocket for UdpSocket { + // unsafe fn from_raw_socket(handle: RawSocket) -> UdpSocket { + // net::UdpSocket::from_raw_socket(handle).into() + // } + // } + // + // impl IntoRawSocket for UdpSocket { + // fn into_raw_socket(self) -> RawSocket { + // self.raw_socket + // } + // } } diff --git a/src/option/from_stream.rs b/src/option/from_stream.rs index e4da809e..86791143 100644 --- a/src/option/from_stream.rs +++ b/src/option/from_stream.rs @@ -11,17 +11,12 @@ where /// elements are taken, and `None` is returned. Should no `None` /// occur, a container with the values of each `Option` is returned. #[inline] - fn from_stream<'a, S: IntoStream>>( + fn from_stream<'a, S: IntoStream> + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - // Using `scan` here because it is able to stop the stream early // if a failure occurs let mut found_error = false; diff --git a/src/option/mod.rs b/src/option/mod.rs index afb29adc..76f096b3 100644 --- a/src/option/mod.rs +++ b/src/option/mod.rs @@ -7,3 +7,8 @@ mod from_stream; #[doc(inline)] pub use std::option::Option; + +cfg_unstable! { + mod product; + mod sum; +} diff --git a/src/option/product.rs b/src/option/product.rs new file mode 100644 index 00000000..abaab73e --- /dev/null +++ b/src/option/product.rs @@ -0,0 +1,64 @@ +use std::pin::Pin; + +use crate::prelude::*; +use crate::stream::{Stream, Product}; + +impl Product> for Option +where + T: Product, +{ + #[doc = r#" + Takes each element in the `Stream`: if it is a `None`, no further + elements are taken, and the `None` is returned. Should no `None` occur, + the product of all elements is returned. + + # Examples + + This multiplies every integer in a vector, rejecting the product if a negative element is + encountered: + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let v = stream::from_iter(vec![1, 2, 4]); + let prod: Option = v.map(|x| + if x < 0 { + None + } else { + Some(x) + }).product().await; + assert_eq!(prod, Some(8)); + # + # }) } + ``` + "#] + fn product<'a, S>(stream: S) -> Pin> + 'a>> + where S: Stream> + 'a + { + Box::pin(async move { + // Using `scan` here because it is able to stop the stream early + // if a failure occurs + let mut found_none = false; + let out = >::product(stream + .scan((), |_, elem| { + match elem { + Some(elem) => Some(elem), + None => { + found_none = true; + // Stop processing the stream on error + None + } + } + })).await; + + if found_none { + None + } else { + Some(out) + } + }) + } +} diff --git a/src/option/sum.rs b/src/option/sum.rs new file mode 100644 index 00000000..d2e44830 --- /dev/null +++ b/src/option/sum.rs @@ -0,0 +1,59 @@ +use std::pin::Pin; + +use crate::prelude::*; +use crate::stream::{Stream, Sum}; + +impl Sum> for Option +where + T: Sum, +{ + #[doc = r#" + Takes each element in the `Iterator`: if it is a `None`, no further + elements are taken, and the `None` is returned. Should no `None` occur, + the sum of all elements is returned. + + # Examples + + This sums up the position of the character 'a' in a vector of strings, + if a word did not have the character 'a' the operation returns `None`: + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let words = stream::from_iter(vec!["have", "a", "great", "day"]); + let total: Option = words.map(|w| w.find('a')).sum().await; + assert_eq!(total, Some(5)); + # + # }) } + ``` + "#] + fn sum<'a, S>(stream: S) -> Pin> + 'a>> + where S: Stream> + 'a + { + Box::pin(async move { + // Using `scan` here because it is able to stop the stream early + // if a failure occurs + let mut found_none = false; + let out = >::sum(stream + .scan((), |_, elem| { + match elem { + Some(elem) => Some(elem), + None => { + found_none = true; + // Stop processing the stream on error + None + } + } + })).await; + + if found_none { + None + } else { + Some(out) + } + }) + } +} diff --git a/src/os/mod.rs b/src/os/mod.rs index fb9b9412..5b836aec 100644 --- a/src/os/mod.rs +++ b/src/os/mod.rs @@ -1,9 +1,9 @@ //! OS-specific extensions. -#[cfg(any(unix, feature = "docs"))] -#[cfg_attr(feature = "docs", doc(cfg(unix)))] -pub mod unix; +cfg_unix! { + pub mod unix; +} -#[cfg(any(windows, feature = "docs"))] -#[cfg_attr(feature = "docs", doc(cfg(windows)))] -pub mod windows; +cfg_windows! { + pub mod windows; +} diff --git a/src/os/unix/fs.rs b/src/os/unix/fs.rs index be8932c0..498b3a97 100644 --- a/src/os/unix/fs.rs +++ b/src/os/unix/fs.rs @@ -1,11 +1,8 @@ //! Unix-specific filesystem extensions. -use std::path::Path; - -use cfg_if::cfg_if; - use crate::io; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; /// Creates a new symbolic link on the filesystem. /// @@ -29,46 +26,46 @@ use crate::task::blocking; pub async fn symlink, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { let src = src.as_ref().to_owned(); let dst = dst.as_ref().to_owned(); - blocking::spawn(async move { std::os::unix::fs::symlink(&src, &dst) }).await + spawn_blocking(move || std::os::unix::fs::symlink(&src, &dst)).await +} + +cfg_not_docs! { + pub use std::os::unix::fs::{DirBuilderExt, DirEntryExt, OpenOptionsExt}; } -cfg_if! { - if #[cfg(feature = "docs")] { - /// Unix-specific extensions to `DirBuilder`. - pub trait DirBuilderExt { - /// Sets the mode to create new directories with. This option defaults to - /// `0o777`. - fn mode(&mut self, mode: u32) -> &mut Self; - } +cfg_docs! { + /// Unix-specific extensions to `DirBuilder`. + pub trait DirBuilderExt { + /// Sets the mode to create new directories with. This option defaults to + /// `0o777`. + fn mode(&mut self, mode: u32) -> &mut Self; + } - /// Unix-specific extension methods for `DirEntry`. - pub trait DirEntryExt { - /// Returns the underlying `d_ino` field in the contained `dirent` - /// structure. - fn ino(&self) -> u64; - } + /// Unix-specific extension methods for `DirEntry`. + pub trait DirEntryExt { + /// Returns the underlying `d_ino` field in the contained `dirent` + /// structure. + fn ino(&self) -> u64; + } - /// Unix-specific extensions to `OpenOptions`. - pub trait OpenOptionsExt { - /// Sets the mode bits that a new file will be created with. - /// - /// If a new file is created as part of a `File::open_opts` call then this - /// specified `mode` will be used as the permission bits for the new file. - /// If no `mode` is set, the default of `0o666` will be used. - /// The operating system masks out bits with the systems `umask`, to produce - /// the final permissions. - fn mode(&mut self, mode: u32) -> &mut Self; + /// Unix-specific extensions to `OpenOptions`. + pub trait OpenOptionsExt { + /// Sets the mode bits that a new file will be created with. + /// + /// If a new file is created as part of a `File::open_opts` call then this + /// specified `mode` will be used as the permission bits for the new file. + /// If no `mode` is set, the default of `0o666` will be used. + /// The operating system masks out bits with the systems `umask`, to produce + /// the final permissions. + fn mode(&mut self, mode: u32) -> &mut Self; - /// Pass custom flags to the `flags` argument of `open`. - /// - /// The bits that define the access mode are masked out with `O_ACCMODE`, to - /// ensure they do not interfere with the access mode set by Rusts options. - /// - /// Custom flags can only set flags, not remove flags set by Rusts options. - /// This options overwrites any previously set custom flags. - fn custom_flags(&mut self, flags: i32) -> &mut Self; - } - } else { - pub use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt}; + /// Pass custom flags to the `flags` argument of `open`. + /// + /// The bits that define the access mode are masked out with `O_ACCMODE`, to + /// ensure they do not interfere with the access mode set by Rusts options. + /// + /// Custom flags can only set flags, not remove flags set by Rusts options. + /// This options overwrites any previously set custom flags. + fn custom_flags(&mut self, flags: i32) -> &mut Self; } } diff --git a/src/os/unix/io.rs b/src/os/unix/io.rs index 820d509c..0b984607 100644 --- a/src/os/unix/io.rs +++ b/src/os/unix/io.rs @@ -1,56 +1,54 @@ //! Unix-specific I/O extensions. -use cfg_if::cfg_if; +cfg_not_docs! { + pub use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; +} -cfg_if! { - if #[cfg(feature = "docs")] { - /// Raw file descriptors. - pub type RawFd = std::os::raw::c_int; +cfg_docs! { + /// Raw file descriptors. + pub type RawFd = std::os::raw::c_int; - /// A trait to extract the raw unix file descriptor from an underlying - /// object. + /// A trait to extract the raw unix file descriptor from an underlying + /// object. + /// + /// This is only available on unix platforms and must be imported in order + /// to call the method. Windows platforms have a corresponding `AsRawHandle` + /// and `AsRawSocket` set of traits. + pub trait AsRawFd { + /// Extracts the raw file descriptor. /// - /// This is only available on unix platforms and must be imported in order - /// to call the method. Windows platforms have a corresponding `AsRawHandle` - /// and `AsRawSocket` set of traits. - pub trait AsRawFd { - /// Extracts the raw file descriptor. - /// - /// This method does **not** pass ownership of the raw file descriptor - /// to the caller. The descriptor is only guaranteed to be valid while - /// the original object has not yet been destroyed. - fn as_raw_fd(&self) -> RawFd; - } + /// This method does **not** pass ownership of the raw file descriptor + /// to the caller. The descriptor is only guaranteed to be valid while + /// the original object has not yet been destroyed. + fn as_raw_fd(&self) -> RawFd; + } - /// A trait to express the ability to construct an object from a raw file + /// A trait to express the ability to construct an object from a raw file + /// descriptor. + pub trait FromRawFd { + /// Constructs a new instance of `Self` from the given raw file /// descriptor. - pub trait FromRawFd { - /// Constructs a new instance of `Self` from the given raw file - /// descriptor. - /// - /// This function **consumes ownership** of the specified file - /// descriptor. The returned object will take responsibility for closing - /// it when the object goes out of scope. - /// - /// This function is also unsafe as the primitives currently returned - /// have the contract that they are the sole owner of the file - /// descriptor they are wrapping. Usage of this function could - /// accidentally allow violating this contract which can cause memory - /// unsafety in code that relies on it being true. - unsafe fn from_raw_fd(fd: RawFd) -> Self; - } + /// + /// This function **consumes ownership** of the specified file + /// descriptor. The returned object will take responsibility for closing + /// it when the object goes out of scope. + /// + /// This function is also unsafe as the primitives currently returned + /// have the contract that they are the sole owner of the file + /// descriptor they are wrapping. Usage of this function could + /// accidentally allow violating this contract which can cause memory + /// unsafety in code that relies on it being true. + unsafe fn from_raw_fd(fd: RawFd) -> Self; + } - /// A trait to express the ability to consume an object and acquire ownership of - /// its raw file descriptor. - pub trait IntoRawFd { - /// Consumes this object, returning the raw underlying file descriptor. - /// - /// This function **transfers ownership** of the underlying file descriptor - /// to the caller. Callers are then the unique owners of the file descriptor - /// and must close the descriptor once it's no longer needed. - fn into_raw_fd(self) -> RawFd; - } - } else { - pub use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; + /// A trait to express the ability to consume an object and acquire ownership of + /// its raw file descriptor. + pub trait IntoRawFd { + /// Consumes this object, returning the raw underlying file descriptor. + /// + /// This function **transfers ownership** of the underlying file descriptor + /// to the caller. Callers are then the unique owners of the file descriptor + /// and must close the descriptor once it's no longer needed. + fn into_raw_fd(self) -> RawFd; } } diff --git a/src/os/unix/mod.rs b/src/os/unix/mod.rs index 722cfe6b..c389d95a 100644 --- a/src/os/unix/mod.rs +++ b/src/os/unix/mod.rs @@ -1,5 +1,10 @@ //! Platform-specific extensions for Unix platforms. -pub mod fs; -pub mod io; -pub mod net; +cfg_std! { + pub mod io; +} + +cfg_default! { + pub mod fs; + pub mod net; +} diff --git a/src/os/unix/net/datagram.rs b/src/os/unix/net/datagram.rs index 1f971f7f..fc426b7c 100644 --- a/src/os/unix/net/datagram.rs +++ b/src/os/unix/net/datagram.rs @@ -2,7 +2,6 @@ use std::fmt; use std::net::Shutdown; -use std::path::Path; use mio_uds; @@ -11,7 +10,8 @@ use crate::future; use crate::io; use crate::net::driver::Watcher; use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; -use crate::task::blocking; +use crate::path::Path; +use crate::task::spawn_blocking; /// A Unix datagram socket. /// @@ -67,7 +67,7 @@ impl UnixDatagram { /// ``` pub async fn bind>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - let socket = blocking::spawn(async move { mio_uds::UnixDatagram::bind(path) }).await?; + let socket = spawn_blocking(move || mio_uds::UnixDatagram::bind(path)).await?; Ok(UnixDatagram::new(socket)) } diff --git a/src/os/unix/net/listener.rs b/src/os/unix/net/listener.rs index eba2fadc..675ef481 100644 --- a/src/os/unix/net/listener.rs +++ b/src/os/unix/net/listener.rs @@ -1,19 +1,20 @@ //! Unix-specific networking extensions. use std::fmt; -use std::path::Path; use std::pin::Pin; +use std::future::Future; use mio_uds; use super::SocketAddr; use super::UnixStream; -use crate::future::{self, Future}; +use crate::future; use crate::io; use crate::net::driver::Watcher; use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; +use crate::path::Path; use crate::stream::Stream; -use crate::task::{blocking, Context, Poll}; +use crate::task::{spawn_blocking, Context, Poll}; /// A Unix domain socket server, listening for connections. /// @@ -68,7 +69,7 @@ impl UnixListener { /// ``` pub async fn bind>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - let listener = blocking::spawn(async move { mio_uds::UnixListener::bind(path) }).await?; + let listener = spawn_blocking(move || mio_uds::UnixListener::bind(path)).await?; Ok(UnixListener { watcher: Watcher::new(listener), diff --git a/src/os/unix/net/mod.rs b/src/os/unix/net/mod.rs index a719a484..e0a3b9b6 100644 --- a/src/os/unix/net/mod.rs +++ b/src/os/unix/net/mod.rs @@ -1,7 +1,5 @@ //! Unix-specific networking extensions. -use cfg_if::cfg_if; - pub use datagram::UnixDatagram; pub use listener::{Incoming, UnixListener}; pub use stream::UnixStream; @@ -10,90 +8,90 @@ mod datagram; mod listener; mod stream; -cfg_if! { - if #[cfg(feature = "docs")] { - use std::fmt; - use std::path::Path; +cfg_not_docs! { + pub use std::os::unix::net::SocketAddr; +} + +cfg_docs! { + use std::fmt; + + use crate::path::Path; - /// An address associated with a Unix socket. + /// An address associated with a Unix socket. + /// + /// # Examples + /// + /// ``` + /// use async_std::os::unix::net::UnixListener; + /// + /// let socket = UnixListener::bind("/tmp/socket").await?; + /// let addr = socket.local_addr()?; + /// ``` + #[derive(Clone)] + pub struct SocketAddr { + _private: (), + } + + impl SocketAddr { + /// Returns `true` if the address is unnamed. /// /// # Examples /// - /// ``` + /// A named address: + /// + /// ```no_run /// use async_std::os::unix::net::UnixListener; /// /// let socket = UnixListener::bind("/tmp/socket").await?; /// let addr = socket.local_addr()?; + /// assert_eq!(addr.is_unnamed(), false); /// ``` - #[derive(Clone)] - pub struct SocketAddr { - _private: (), + /// + /// An unnamed address: + /// + /// ```no_run + /// use async_std::os::unix::net::UnixDatagram; + /// + /// let socket = UnixDatagram::unbound().await?; + /// let addr = socket.local_addr()?; + /// assert_eq!(addr.is_unnamed(), true); + /// ``` + pub fn is_unnamed(&self) -> bool { + unreachable!("this impl only appears in the rendered docs") } - impl SocketAddr { - /// Returns `true` if the address is unnamed. - /// - /// # Examples - /// - /// A named address: - /// - /// ```no_run - /// use async_std::os::unix::net::UnixListener; - /// - /// let socket = UnixListener::bind("/tmp/socket").await?; - /// let addr = socket.local_addr()?; - /// assert_eq!(addr.is_unnamed(), false); - /// ``` - /// - /// An unnamed address: - /// - /// ```no_run - /// use async_std::os::unix::net::UnixDatagram; - /// - /// let socket = UnixDatagram::unbound().await?; - /// let addr = socket.local_addr()?; - /// assert_eq!(addr.is_unnamed(), true); - /// ``` - pub fn is_unnamed(&self) -> bool { - unreachable!("this impl only appears in the rendered docs") - } - - /// Returns the contents of this address if it is a `pathname` address. - /// - /// # Examples - /// - /// With a pathname: - /// - /// ```no_run - /// use std::path::Path; - /// - /// use async_std::os::unix::net::UnixListener; - /// - /// let socket = UnixListener::bind("/tmp/socket").await?; - /// let addr = socket.local_addr()?; - /// assert_eq!(addr.as_pathname(), Some(Path::new("/tmp/socket"))); - /// ``` - /// - /// Without a pathname: - /// - /// ``` - /// use async_std::os::unix::net::UnixDatagram; - /// - /// let socket = UnixDatagram::unbound()?; - /// let addr = socket.local_addr()?; - /// assert_eq!(addr.as_pathname(), None); - /// ``` - pub fn as_pathname(&self) -> Option<&Path> { - unreachable!("this impl only appears in the rendered docs") - } + /// Returns the contents of this address if it is a `pathname` address. + /// + /// # Examples + /// + /// With a pathname: + /// + /// ```no_run + /// use async_std::os::unix::net::UnixListener; + /// use async_std::path::Path; + /// + /// let socket = UnixListener::bind("/tmp/socket").await?; + /// let addr = socket.local_addr()?; + /// assert_eq!(addr.as_pathname(), Some(Path::new("/tmp/socket"))); + /// ``` + /// + /// Without a pathname: + /// + /// ``` + /// use async_std::os::unix::net::UnixDatagram; + /// + /// let socket = UnixDatagram::unbound()?; + /// let addr = socket.local_addr()?; + /// assert_eq!(addr.as_pathname(), None); + /// ``` + pub fn as_pathname(&self) -> Option<&Path> { + unreachable!("this impl only appears in the rendered docs") } + } - impl fmt::Debug for SocketAddr { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - unreachable!("this impl only appears in the rendered docs") - } + impl fmt::Debug for SocketAddr { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + unreachable!("this impl only appears in the rendered docs") } - } else { - pub use std::os::unix::net::SocketAddr; } } diff --git a/src/os/unix/net/stream.rs b/src/os/unix/net/stream.rs index ae30b5bf..647edc96 100644 --- a/src/os/unix/net/stream.rs +++ b/src/os/unix/net/stream.rs @@ -3,7 +3,6 @@ use std::fmt; use std::io::{Read as _, Write as _}; use std::net::Shutdown; -use std::path::Path; use std::pin::Pin; use mio_uds; @@ -12,7 +11,8 @@ use super::SocketAddr; use crate::io::{self, Read, Write}; use crate::net::driver::Watcher; use crate::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; -use crate::task::{blocking, Context, Poll}; +use crate::path::Path; +use crate::task::{spawn_blocking, Context, Poll}; /// A Unix stream socket. /// @@ -58,7 +58,7 @@ impl UnixStream { pub async fn connect>(path: P) -> io::Result { let path = path.as_ref().to_owned(); - blocking::spawn(async move { + spawn_blocking(move || { let std_stream = std::os::unix::net::UnixStream::connect(path)?; let mio_stream = mio_uds::UnixStream::from_stream(std_stream)?; Ok(UnixStream { diff --git a/src/os/windows/fs.rs b/src/os/windows/fs.rs new file mode 100644 index 00000000..243f3819 --- /dev/null +++ b/src/os/windows/fs.rs @@ -0,0 +1,55 @@ +//! Windows-specific filesystem extensions. + +use crate::io; +use crate::path::Path; +use crate::task::spawn_blocking; + +/// Creates a new directory symbolic link on the filesystem. +/// +/// The `dst` path will be a directory symbolic link pointing to the `src` path. +/// +/// This function is an async version of [`std::os::windows::fs::symlink_dir`]. +/// +/// [`std::os::windows::fs::symlink_dir`]: https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_dir.html +/// +/// # Examples +/// +/// ```no_run +/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +/// # +/// use async_std::os::windows::fs::symlink_dir; +/// +/// symlink_dir("a", "b").await?; +/// # +/// # Ok(()) }) } +/// ``` +pub async fn symlink_dir, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { + let src = src.as_ref().to_owned(); + let dst = dst.as_ref().to_owned(); + spawn_blocking(move || std::os::windows::fs::symlink_dir(&src, &dst)).await +} + +/// Creates a new file symbolic link on the filesystem. +/// +/// The `dst` path will be a file symbolic link pointing to the `src` path. +/// +/// This function is an async version of [`std::os::windows::fs::symlink_file`]. +/// +/// [`std::os::windows::fs::symlink_file`]: https://doc.rust-lang.org/std/os/windows/fs/fn.symlink_file.html +/// +/// # Examples +/// +/// ```no_run +/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +/// # +/// use async_std::os::windows::fs::symlink_file; +/// +/// symlink_file("a.txt", "b.txt").await?; +/// # +/// # Ok(()) }) } +/// ``` +pub async fn symlink_file, Q: AsRef>(src: P, dst: Q) -> io::Result<()> { + let src = src.as_ref().to_owned(); + let dst = dst.as_ref().to_owned(); + spawn_blocking(move || std::os::windows::fs::symlink_file(&src, &dst)).await +} diff --git a/src/os/windows/io.rs b/src/os/windows/io.rs index 20f87d29..e83d5571 100644 --- a/src/os/windows/io.rs +++ b/src/os/windows/io.rs @@ -1,50 +1,48 @@ //! Windows-specific I/O extensions. -use cfg_if::cfg_if; +cfg_not_docs! { + pub use std::os::windows::io::{ + AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle, RawSocket, + }; +} -cfg_if! { - if #[cfg(feature = "docs")] { - /// Raw HANDLEs. - pub type RawHandle = *mut std::os::raw::c_void; +cfg_docs! { + /// Raw HANDLEs. + pub type RawHandle = *mut std::os::raw::c_void; - /// Raw SOCKETs. - pub type RawSocket = u64; + /// Raw SOCKETs. + pub type RawSocket = u64; - /// Extracts raw handles. - pub trait AsRawHandle { - /// Extracts the raw handle, without taking any ownership. - fn as_raw_handle(&self) -> RawHandle; - } + /// Extracts raw handles. + pub trait AsRawHandle { + /// Extracts the raw handle, without taking any ownership. + fn as_raw_handle(&self) -> RawHandle; + } - /// Construct I/O objects from raw handles. - pub trait FromRawHandle { - /// Constructs a new I/O object from the specified raw handle. - /// - /// This function will **consume ownership** of the handle given, - /// passing responsibility for closing the handle to the returned - /// object. - /// - /// This function is also unsafe as the primitives currently returned - /// have the contract that they are the sole owner of the file - /// descriptor they are wrapping. Usage of this function could - /// accidentally allow violating this contract which can cause memory - /// unsafety in code that relies on it being true. - unsafe fn from_raw_handle(handle: RawHandle) -> Self; - } + /// Construct I/O objects from raw handles. + pub trait FromRawHandle { + /// Constructs a new I/O object from the specified raw handle. + /// + /// This function will **consume ownership** of the handle given, + /// passing responsibility for closing the handle to the returned + /// object. + /// + /// This function is also unsafe as the primitives currently returned + /// have the contract that they are the sole owner of the file + /// descriptor they are wrapping. Usage of this function could + /// accidentally allow violating this contract which can cause memory + /// unsafety in code that relies on it being true. + unsafe fn from_raw_handle(handle: RawHandle) -> Self; + } - /// A trait to express the ability to consume an object and acquire ownership of - /// its raw `HANDLE`. - pub trait IntoRawHandle { - /// Consumes this object, returning the raw underlying handle. - /// - /// This function **transfers ownership** of the underlying handle to the - /// caller. Callers are then the unique owners of the handle and must close - /// it once it's no longer needed. - fn into_raw_handle(self) -> RawHandle; - } - } else { - pub use std::os::windows::io::{ - AsRawHandle, FromRawHandle, IntoRawHandle, RawHandle, RawSocket, - }; + /// A trait to express the ability to consume an object and acquire ownership of + /// its raw `HANDLE`. + pub trait IntoRawHandle { + /// Consumes this object, returning the raw underlying handle. + /// + /// This function **transfers ownership** of the underlying handle to the + /// caller. Callers are then the unique owners of the handle and must close + /// it once it's no longer needed. + fn into_raw_handle(self) -> RawHandle; } } diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs index 30218f0e..6dac11b6 100644 --- a/src/os/windows/mod.rs +++ b/src/os/windows/mod.rs @@ -1,3 +1,9 @@ //! Platform-specific extensions for Windows. -pub mod io; +cfg_std! { + pub mod io; +} + +cfg_unstable! { + pub mod fs; +} diff --git a/src/path/ancestors.rs b/src/path/ancestors.rs new file mode 100644 index 00000000..c7237ffd --- /dev/null +++ b/src/path/ancestors.rs @@ -0,0 +1,39 @@ +use std::iter::FusedIterator; + +use crate::path::Path; + +/// An iterator over [`Path`] and its ancestors. +/// +/// This `struct` is created by the [`ancestors`] method on [`Path`]. +/// See its documentation for more. +/// +/// # Examples +/// +/// ``` +/// use async_std::path::Path; +/// +/// let path = Path::new("/foo/bar"); +/// +/// for ancestor in path.ancestors() { +/// println!("{}", ancestor.display()); +/// } +/// ``` +/// +/// [`ancestors`]: struct.Path.html#method.ancestors +/// [`Path`]: struct.Path.html +#[derive(Copy, Clone, Debug)] +pub struct Ancestors<'a> { + pub(crate) next: Option<&'a Path>, +} + +impl<'a> Iterator for Ancestors<'a> { + type Item = &'a Path; + + fn next(&mut self) -> Option { + let next = self.next; + self.next = next.and_then(Path::parent); + next + } +} + +impl FusedIterator for Ancestors<'_> {} diff --git a/src/path/components.rs b/src/path/components.rs new file mode 100644 index 00000000..51649c55 --- /dev/null +++ b/src/path/components.rs @@ -0,0 +1,82 @@ +use std::ffi::OsStr; +use std::iter::FusedIterator; + +use crate::path::{Component, Path}; + +/// An iterator over the [`Component`]s of a [`Path`]. +/// +/// This `struct` is created by the [`components`] method on [`Path`]. +/// See its documentation for more. +/// +/// # Examples +/// +/// ``` +/// use async_std::path::Path; +/// +/// let path = Path::new("/tmp/foo/bar.txt"); +/// +/// for component in path.components() { +/// println!("{:?}", component); +/// } +/// ``` +/// +/// [`Component`]: enum.Component.html +/// [`components`]: struct.Path.html#method.components +/// [`Path`]: struct.Path.html +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct Components<'a> { + pub(crate) inner: std::path::Components<'a>, +} + +impl<'a> Components<'a> { + /// Extracts a slice corresponding to the portion of the path remaining for iteration. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let mut components = Path::new("/tmp/foo/bar.txt").components(); + /// components.next(); + /// components.next(); + /// + /// assert_eq!(Path::new("foo/bar.txt"), components.as_path()); + /// ``` + pub fn as_path(&self) -> &'a Path { + self.inner.as_path().into() + } +} + +impl AsRef for Components<'_> { + fn as_ref(&self) -> &Path { + self.as_path() + } +} + +impl AsRef for Components<'_> { + fn as_ref(&self) -> &OsStr { + self.as_path().as_os_str() + } +} + +impl<'a> Iterator for Components<'a> { + type Item = Component<'a>; + + fn next(&mut self) -> Option> { + self.inner.next() + } +} + +impl<'a> DoubleEndedIterator for Components<'a> { + fn next_back(&mut self) -> Option> { + self.inner.next_back() + } +} + +impl FusedIterator for Components<'_> {} + +impl AsRef for Component<'_> { + fn as_ref(&self) -> &Path { + self.as_os_str().as_ref() + } +} diff --git a/src/path/iter.rs b/src/path/iter.rs new file mode 100644 index 00000000..b4061003 --- /dev/null +++ b/src/path/iter.rs @@ -0,0 +1,82 @@ +use std::ffi::OsStr; +use std::fmt; +use std::iter::FusedIterator; + +use crate::path::{Component, Components, Path}; + +/// An iterator over the [`Component`]s of a [`Path`], as [`OsStr`] slices. +/// +/// This `struct` is created by the [`iter`] method on [`Path`]. +/// See its documentation for more. +/// +/// [`Component`]: enum.Component.html +/// [`iter`]: struct.Path.html#method.iter +/// [`OsStr`]: ../../std/ffi/struct.OsStr.html +/// [`Path`]: struct.Path.html +#[derive(Clone)] +pub struct Iter<'a> { + pub(crate) inner: Components<'a>, +} + +impl<'a> Iter<'a> { + /// Extracts a slice corresponding to the portion of the path remaining for iteration. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let mut iter = Path::new("/tmp/foo/bar.txt").iter(); + /// iter.next(); + /// iter.next(); + /// + /// assert_eq!(Path::new("foo/bar.txt"), iter.as_path()); + /// ``` + pub fn as_path(&self) -> &'a Path { + self.inner.as_path() + } +} + +impl<'a> Iterator for Iter<'a> { + type Item = &'a OsStr; + + fn next(&mut self) -> Option<&'a OsStr> { + self.inner.next().map(Component::as_os_str) + } +} + +impl fmt::Debug for Iter<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + struct DebugHelper<'a>(&'a Path); + + impl fmt::Debug for DebugHelper<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.0.iter()).finish() + } + } + + f.debug_tuple("Iter") + .field(&DebugHelper(self.as_path())) + .finish() + } +} + +impl AsRef for Iter<'_> { + fn as_ref(&self) -> &Path { + self.as_path() + } +} + +impl AsRef for Iter<'_> { + fn as_ref(&self) -> &OsStr { + self.as_path().as_os_str() + } +} + +impl<'a> DoubleEndedIterator for Iter<'a> { + fn next_back(&mut self) -> Option<&'a OsStr> { + self.inner.next_back().map(Component::as_os_str) + } +} + +impl FusedIterator for Iter<'_> {} diff --git a/src/path/mod.rs b/src/path/mod.rs index 91532439..7ce9b62d 100644 --- a/src/path/mod.rs +++ b/src/path/mod.rs @@ -2,20 +2,86 @@ //! //! This module is an async version of [`std::path`]. //! +//! This module provides two types, [`PathBuf`] and [`Path`][`Path`] (akin to [`String`] +//! and [`str`]), for working with paths abstractly. These types are thin wrappers +//! around [`OsString`] and [`OsStr`] respectively, meaning that they work directly +//! on strings according to the local platform's path syntax. +//! +//! Paths can be parsed into [`Component`]s by iterating over the structure +//! returned by the [`components`] method on [`Path`]. [`Component`]s roughly +//! correspond to the substrings between path separators (`/` or `\`). You can +//! reconstruct an equivalent path from components with the [`push`] method on +//! [`PathBuf`]; note that the paths may differ syntactically by the +//! normalization described in the documentation for the [`components`] method. +//! //! [`std::path`]: https://doc.rust-lang.org/std/path/index.html +//! +//! ## Simple usage +//! +//! Path manipulation includes both parsing components from slices and building +//! new owned paths. +//! +//! To parse a path, you can create a [`Path`] slice from a [`str`] +//! slice and start asking questions: +//! +//! ``` +//! use async_std::path::Path; +//! use std::ffi::OsStr; +//! +//! let path = Path::new("/tmp/foo/bar.txt"); +//! +//! let parent = path.parent(); +//! assert_eq!(parent, Some(Path::new("/tmp/foo"))); +//! +//! let file_stem = path.file_stem(); +//! assert_eq!(file_stem, Some(OsStr::new("bar"))); +//! +//! let extension = path.extension(); +//! assert_eq!(extension, Some(OsStr::new("txt"))); +//! ``` +//! +//! To build or modify paths, use [`PathBuf`]: +//! +//! ``` +//! use async_std::path::PathBuf; +//! +//! // This way works... +//! let mut path = PathBuf::from("c:\\"); +//! +//! path.push("windows"); +//! path.push("system32"); +//! +//! path.set_extension("dll"); +//! +//! // ... but push is best used if you don't know everything up +//! // front. If you do, this way is better: +//! let path: PathBuf = ["c:\\", "windows", "system32.dll"].iter().collect(); +//! ``` +//! +//! [`Component`]: enum.Component.html +//! [`components`]: struct.Path.html#method.components +//! [`PathBuf`]: struct.PathBuf.html +//! [`Path`]: struct.Path.html +//! [`push`]: struct.PathBuf.html#method.push +//! [`String`]: https://doc.rust-lang.org/std/string/struct.String.html +//! +//! [`str`]: https://doc.rust-lang.org/std/primitive.str.html +//! [`OsString`]: https://doc.rust-lang.org/std/ffi/struct.OsString.html +//! [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html -// Structs re-export -#[doc(inline)] -pub use std::path::{Ancestors, Components, Display, Iter, PrefixComponent, StripPrefixError}; - -// Enums re-export -#[doc(inline)] -pub use std::path::{Component, Prefix}; +mod ancestors; +mod components; +mod iter; +mod path; +mod pathbuf; -// Constants re-export #[doc(inline)] -pub use std::path::MAIN_SEPARATOR; +pub use std::path::{ + is_separator, Component, Display, Prefix, PrefixComponent, StripPrefixError, MAIN_SEPARATOR, +}; -// Functions re-export -#[doc(inline)] -pub use std::path::is_separator; +pub use ancestors::Ancestors; +pub use components::Components; +pub use iter::Iter; +pub use path::Path; +pub use pathbuf::PathBuf; diff --git a/src/path/path.rs b/src/path/path.rs new file mode 100644 index 00000000..dfe9426a --- /dev/null +++ b/src/path/path.rs @@ -0,0 +1,1033 @@ +use std::borrow::{Cow, ToOwned}; +use std::cmp::Ordering; +use std::ffi::{OsStr, OsString}; +use std::rc::Rc; +use std::sync::Arc; + +use crate::fs; +use crate::io; +use crate::path::{Ancestors, Components, Display, Iter, PathBuf, StripPrefixError}; + +/// A slice of a path. +/// +/// This struct is an async version of [`std::path::Path`]. +/// +/// This type supports a number of operations for inspecting a path, including +/// breaking the path into its components (separated by `/` on Unix and by either +/// `/` or `\` on Windows), extracting the file name, determining whether the path +/// is absolute, and so on. +/// +/// This is an *unsized* type, meaning that it must always be used behind a +/// pointer like `&` or `Box`. For an owned version of this type, +/// see [`PathBuf`]. +/// +/// [`PathBuf`]: struct.PathBuf.html +/// [`std::path::Path`]: https://doc.rust-lang.org/std/path/struct.Path.html +/// +/// More details about the overall approach can be found in +/// the [module documentation](index.html). +/// +/// # Examples +/// +/// ``` +/// use std::path::Path; +/// use std::ffi::OsStr; +/// +/// // Note: this example does work on Windows +/// let path = Path::new("./foo/bar.txt"); +/// +/// let parent = path.parent(); +/// assert_eq!(parent, Some(Path::new("./foo"))); +/// +/// let file_stem = path.file_stem(); +/// assert_eq!(file_stem, Some(OsStr::new("bar"))); +/// +/// let extension = path.extension(); +/// assert_eq!(extension, Some(OsStr::new("txt"))); +/// ``` +#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Path { + inner: std::path::Path, +} + +impl Path { + /// Directly wraps a string slice as a `Path` slice. + /// + /// This is a cost-free conversion. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// Path::new("foo.txt"); + /// ``` + /// + /// You can create `Path`s from `String`s, or even other `Path`s: + /// + /// ``` + /// use async_std::path::Path; + /// + /// let string = String::from("foo.txt"); + /// let from_string = Path::new(&string); + /// let from_path = Path::new(&from_string); + /// assert_eq!(from_string, from_path); + /// ``` + pub fn new + ?Sized>(s: &S) -> &Path { + unsafe { &*(std::path::Path::new(s) as *const std::path::Path as *const Path) } + } + + /// Returns the underlying [`OsStr`] slice. + /// + /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use async_std::path::Path; + /// + /// let os_str = Path::new("foo.txt").as_os_str(); + /// assert_eq!(os_str, OsStr::new("foo.txt")); + /// ``` + pub fn as_os_str(&self) -> &OsStr { + self.inner.as_os_str() + } + + /// Returns a [`&str`] slice if the `Path` is valid unicode. + /// + /// This conversion may entail doing a check for UTF-8 validity. + /// Note that validation is performed because non-UTF-8 strings are + /// perfectly valid for some OS. + /// + /// [`&str`]: https://doc.rust-lang.org/std/primitive.str.html + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path = Path::new("foo.txt"); + /// assert_eq!(path.to_str(), Some("foo.txt")); + /// ``` + pub fn to_str(&self) -> Option<&str> { + self.inner.to_str() + } + + /// Converts a `Path` to a [`Cow`]. + /// + /// Any non-Unicode sequences are replaced with + /// [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD]. + /// + /// [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html + /// [U+FFFD]: https://doc.rust-lang.org/std/char/constant.REPLACEMENT_CHARACTER.html + /// + /// # Examples + /// + /// Calling `to_string_lossy` on a `Path` with valid unicode: + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path = Path::new("foo.txt"); + /// assert_eq!(path.to_string_lossy(), "foo.txt"); + /// ``` + /// + /// Had `path` contained invalid unicode, the `to_string_lossy` call might + /// have returned `"fo�.txt"`. + pub fn to_string_lossy(&self) -> Cow<'_, str> { + self.inner.to_string_lossy() + } + + /// Converts a `Path` to an owned [`PathBuf`]. + /// + /// [`PathBuf`]: struct.PathBuf.html + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// let path_buf = Path::new("foo.txt").to_path_buf(); + /// assert_eq!(path_buf, PathBuf::from("foo.txt")); + /// ``` + pub fn to_path_buf(&self) -> PathBuf { + PathBuf::from(self.inner.to_path_buf()) + } + + /// Returns `true` if the `Path` is absolute, i.e. if it is independent of + /// the current directory. + /// + /// * On Unix, a path is absolute if it starts with the root, so + /// `is_absolute` and [`has_root`] are equivalent. + /// + /// * On Windows, a path is absolute if it has a prefix and starts with the + /// root: `c:\windows` is absolute, while `c:temp` and `\temp` are not. + /// + /// [`has_root`]: #method.has_root + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// assert!(!Path::new("foo.txt").is_absolute()); + /// ``` + pub fn is_absolute(&self) -> bool { + self.inner.is_absolute() + } + + /// Returns `true` if the `Path` is relative, i.e. not absolute. + /// + /// See [`is_absolute`]'s documentation for more details. + /// + /// [`is_absolute`]: #method.is_absolute + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// assert!(Path::new("foo.txt").is_relative()); + /// ``` + pub fn is_relative(&self) -> bool { + self.inner.is_relative() + } + + /// Returns `true` if the `Path` has a root. + /// + /// * On Unix, a path has a root if it begins with `/`. + /// + /// * On Windows, a path has a root if it: + /// * has no prefix and begins with a separator, e.g. `\windows` + /// * has a prefix followed by a separator, e.g. `c:\windows` but not `c:windows` + /// * has any non-disk prefix, e.g. `\\server\share` + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// assert!(Path::new("/etc/passwd").has_root()); + /// ``` + pub fn has_root(&self) -> bool { + self.inner.has_root() + } + + /// Returns the `Path` without its final component, if there is one. + /// + /// Returns [`None`] if the path terminates in a root or prefix. + /// + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path = Path::new("/foo/bar"); + /// let parent = path.parent().unwrap(); + /// assert_eq!(parent, Path::new("/foo")); + /// + /// let grand_parent = parent.parent().unwrap(); + /// assert_eq!(grand_parent, Path::new("/")); + /// assert_eq!(grand_parent.parent(), None); + /// ``` + pub fn parent(&self) -> Option<&Path> { + self.inner.parent().map(|p| p.into()) + } + + /// Produces an iterator over `Path` and its ancestors. + /// + /// The iterator will yield the `Path` that is returned if the [`parent`] method is used zero + /// or more times. That means, the iterator will yield `&self`, `&self.parent().unwrap()`, + /// `&self.parent().unwrap().parent().unwrap()` and so on. If the [`parent`] method returns + /// [`None`], the iterator will do likewise. The iterator will always yield at least one value, + /// namely `&self`. + /// + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html + /// [`parent`]: struct.Path.html#method.parent + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let mut ancestors = Path::new("/foo/bar").ancestors(); + /// assert_eq!(ancestors.next(), Some(Path::new("/foo/bar").into())); + /// assert_eq!(ancestors.next(), Some(Path::new("/foo").into())); + /// assert_eq!(ancestors.next(), Some(Path::new("/").into())); + /// assert_eq!(ancestors.next(), None); + /// ``` + pub fn ancestors(&self) -> Ancestors<'_> { + Ancestors { next: Some(&self) } + } + + /// Returns the final component of the `Path`, if there is one. + /// + /// If the path is a normal file, this is the file name. If it's the path of a directory, this + /// is the directory name. + /// + /// Returns [`None`] if the path terminates in `..`. + /// + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use async_std::path::Path; + /// + /// assert_eq!(Some(OsStr::new("bin")), Path::new("/usr/bin/").file_name()); + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("tmp/foo.txt").file_name()); + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("foo.txt/.").file_name()); + /// assert_eq!(Some(OsStr::new("foo.txt")), Path::new("foo.txt/.//").file_name()); + /// assert_eq!(None, Path::new("foo.txt/..").file_name()); + /// assert_eq!(None, Path::new("/").file_name()); + /// ``` + pub fn file_name(&self) -> Option<&OsStr> { + self.inner.file_name() + } + + /// Returns a path that becomes `self` when joined onto `base`. + /// + /// # Errors + /// + /// If `base` is not a prefix of `self` (i.e., [`starts_with`] + /// returns `false`), returns [`Err`]. + /// + /// [`starts_with`]: #method.starts_with + /// [`Err`]: https://doc.rust-lang.org/std/result/enum.Result.html#variant.Err + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// let path = Path::new("/test/haha/foo.txt"); + /// + /// assert_eq!(path.strip_prefix("/"), Ok(Path::new("test/haha/foo.txt"))); + /// assert_eq!(path.strip_prefix("/test"), Ok(Path::new("haha/foo.txt"))); + /// assert_eq!(path.strip_prefix("/test/"), Ok(Path::new("haha/foo.txt"))); + /// assert_eq!(path.strip_prefix("/test/haha/foo.txt"), Ok(Path::new(""))); + /// assert_eq!(path.strip_prefix("/test/haha/foo.txt/"), Ok(Path::new(""))); + /// assert_eq!(path.strip_prefix("test").is_ok(), false); + /// assert_eq!(path.strip_prefix("/haha").is_ok(), false); + /// + /// let prefix = PathBuf::from("/test/"); + /// assert_eq!(path.strip_prefix(prefix), Ok(Path::new("haha/foo.txt"))); + /// ``` + pub fn strip_prefix

(&self, base: P) -> Result<&Path, StripPrefixError> + where + P: AsRef, + { + Ok(self.inner.strip_prefix(base.as_ref())?.into()) + } + + /// Determines whether `base` is a prefix of `self`. + /// + /// Only considers whole path components to match. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path = Path::new("/etc/passwd"); + /// + /// assert!(path.starts_with("/etc")); + /// assert!(path.starts_with("/etc/")); + /// assert!(path.starts_with("/etc/passwd")); + /// assert!(path.starts_with("/etc/passwd/")); + /// + /// assert!(!path.starts_with("/e")); + /// ``` + pub fn starts_with>(&self, base: P) -> bool { + self.inner.starts_with(base.as_ref()) + } + + /// Determines whether `child` is a suffix of `self`. + /// + /// Only considers whole path components to match. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path = Path::new("/etc/passwd"); + /// + /// assert!(path.ends_with("passwd")); + /// ``` + pub fn ends_with>(&self, child: P) -> bool { + self.inner.ends_with(child.as_ref()) + } + + /// Extracts the stem (non-extension) portion of [`file_name`]. + /// + /// [`file_name`]: struct.Path.html#method.file_name + /// + /// The stem is: + /// + /// * [`None`], if there is no file name + /// * The entire file name if there is no embedded `.` + /// * The entire file name if the file name begins with `.` and has no other `.`s within + /// * Otherwise, the portion of the file name before the final `.` + /// + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path = Path::new("foo.rs"); + /// + /// assert_eq!("foo", path.file_stem().unwrap()); + /// ``` + pub fn file_stem(&self) -> Option<&OsStr> { + self.inner.file_stem() + } + + /// Extracts the extension of [`file_name`], if possible. + /// + /// The extension is: + /// + /// * [`None`], if there is no file name + /// * [`None`], if there is no embedded `.` + /// * [`None`], if the file name begins with `.` and has no other `.`s within + /// * Otherwise, the portion of the file name after the final `.` + /// + /// [`file_name`]: struct.Path.html#method.file_name + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path = Path::new("foo.rs"); + /// + /// assert_eq!("rs", path.extension().unwrap()); + /// ``` + pub fn extension(&self) -> Option<&OsStr> { + self.inner.extension() + } + + /// Creates an owned [`PathBuf`] with `path` adjoined to `self`. + /// + /// See [`PathBuf::push`] for more details on what it means to adjoin a path. + /// + /// [`PathBuf`]: struct.PathBuf.html + /// [`PathBuf::push`]: struct.PathBuf.html#method.push + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// assert_eq!(Path::new("/etc").join("passwd"), PathBuf::from("/etc/passwd")); + /// ``` + pub fn join>(&self, path: P) -> PathBuf { + self.inner.join(path.as_ref()).into() + } + + /// Creates an owned [`PathBuf`] like `self` but with the given file name. + /// + /// See [`PathBuf::set_file_name`] for more details. + /// + /// [`PathBuf`]: struct.PathBuf.html + /// [`PathBuf::set_file_name`]: struct.PathBuf.html#method.set_file_name + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// let path = Path::new("/tmp/foo.txt"); + /// assert_eq!(path.with_file_name("bar.txt"), PathBuf::from("/tmp/bar.txt")); + /// + /// let path = Path::new("/tmp"); + /// assert_eq!(path.with_file_name("var"), PathBuf::from("/var")); + /// ``` + pub fn with_file_name>(&self, file_name: S) -> PathBuf { + self.inner.with_file_name(file_name).into() + } + + /// Creates an owned [`PathBuf`] like `self` but with the given extension. + /// + /// See [`PathBuf::set_extension`] for more details. + /// + /// [`PathBuf`]: struct.PathBuf.html + /// [`PathBuf::set_extension`]: struct.PathBuf.html#method.set_extension + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// let path = Path::new("foo.rs"); + /// assert_eq!(path.with_extension("txt"), PathBuf::from("foo.txt")); + /// ``` + pub fn with_extension>(&self, extension: S) -> PathBuf { + self.inner.with_extension(extension).into() + } + + /// Produces an iterator over the [`Component`]s of the path. + /// + /// When parsing the path, there is a small amount of normalization: + /// + /// * Repeated separators are ignored, so `a/b` and `a//b` both have + /// `a` and `b` as components. + /// + /// * Occurrences of `.` are normalized away, except if they are at the + /// beginning of the path. For example, `a/./b`, `a/b/`, `a/b/.` and + /// `a/b` all have `a` and `b` as components, but `./a/b` starts with + /// an additional [`CurDir`] component. + /// + /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent. + /// + /// Note that no other normalization takes place; in particular, `a/c` + /// and `a/b/../c` are distinct, to account for the possibility that `b` + /// is a symbolic link (so its parent isn't `a`). + /// + /// [`Component`]: enum.Component.html + /// [`CurDir`]: enum.Component.html#variant.CurDir + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use async_std::path::{Path, Component}; + /// + /// let mut components = Path::new("/tmp/foo.txt").components(); + /// + /// assert_eq!(components.next(), Some(Component::RootDir)); + /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("tmp")))); + /// assert_eq!(components.next(), Some(Component::Normal(OsStr::new("foo.txt")))); + /// assert_eq!(components.next(), None); + /// ``` + pub fn components(&self) -> Components<'_> { + Components { + inner: self.inner.components(), + } + } + + /// Produces an iterator over the path's components viewed as [`OsStr`] + /// slices. + /// + /// For more information about the particulars of how the path is separated + /// into components, see [`components`]. + /// + /// [`components`]: #method.components + /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html + /// + /// # Examples + /// + /// ``` + /// use std::ffi::OsStr; + /// + /// use async_std::path::{self, Path}; + /// + /// let mut it = Path::new("/tmp/foo.txt").iter(); + /// assert_eq!(it.next(), Some(OsStr::new(&path::MAIN_SEPARATOR.to_string()))); + /// assert_eq!(it.next(), Some(OsStr::new("tmp"))); + /// assert_eq!(it.next(), Some(OsStr::new("foo.txt"))); + /// assert_eq!(it.next(), None) + /// ``` + pub fn iter(&self) -> Iter<'_> { + Iter { + inner: self.components(), + } + } + + /// Returns an object that implements [`Display`] for safely printing paths + /// that may contain non-Unicode data. + /// + /// [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path = Path::new("/tmp/foo.rs"); + /// + /// println!("{}", path.display()); + /// ``` + pub fn display(&self) -> Display<'_> { + self.inner.display() + } + + /// Reads the metadata of a file or directory. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. + /// + /// This is an alias to [`fs::metadata`]. + /// + /// [`fs::metadata`]: ../fs/fn.metadata.html + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::path::Path; + /// + /// let path = Path::new("/Minas/tirith"); + /// let metadata = path.metadata().await?; + /// println!("{:?}", metadata.file_type()); + /// # + /// # Ok(()) }) } + /// ``` + pub async fn metadata(&self) -> io::Result { + fs::metadata(self).await + } + + /// Reads the metadata of a file or directory without following symbolic links. + /// + /// This is an alias to [`fs::symlink_metadata`]. + /// + /// [`fs::symlink_metadata`]: ../fs/fn.symlink_metadata.html + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::path::Path; + /// + /// let path = Path::new("/Minas/tirith"); + /// let metadata = path.symlink_metadata().await?; + /// println!("{:?}", metadata.file_type()); + /// # + /// # Ok(()) }) } + /// ``` + pub async fn symlink_metadata(&self) -> io::Result { + fs::symlink_metadata(self).await + } + + /// Returns the canonical form of a path. + /// + /// The returned path is in absolute form with all intermediate components normalized and + /// symbolic links resolved. + /// + /// This is an alias to [`fs::canonicalize`]. + /// + /// [`fs::canonicalize`]: ../fs/fn.canonicalize.html + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::path::{Path, PathBuf}; + /// + /// let path = Path::new("/foo/test/../test/bar.rs"); + /// assert_eq!(path.canonicalize().await?, PathBuf::from("/foo/test/bar.rs")); + /// # + /// # Ok(()) }) } + /// ``` + pub async fn canonicalize(&self) -> io::Result { + fs::canonicalize(self).await + } + + /// Reads a symbolic link, returning the file that the link points to. + /// + /// This is an alias to [`fs::read_link`]. + /// + /// [`fs::read_link`]: ../fs/fn.read_link.html + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::path::Path; + /// + /// let path = Path::new("/laputa/sky_castle.rs"); + /// let path_link = path.read_link().await?; + /// # + /// # Ok(()) }) } + /// ``` + pub async fn read_link(&self) -> io::Result { + fs::read_link(self).await + } + + /// Returns a stream over the entries within a directory. + /// + /// The stream will yield instances of [`io::Result`]`<`[`DirEntry`]`>`. New + /// errors may be encountered after an iterator is initially constructed. + /// + /// This is an alias to [`fs::read_dir`]. + /// + /// [`io::Result`]: ../io/type.Result.html + /// [`DirEntry`]: ../fs/struct.DirEntry.html + /// [`fs::read_dir`]: ../fs/fn.read_dir.html + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::fs; + /// use async_std::path::Path; + /// use async_std::prelude::*; + /// + /// let path = Path::new("/laputa"); + /// let mut dir = fs::read_dir(&path).await?; + /// + /// while let Some(res) = dir.next().await { + /// let entry = res?; + /// println!("{}", entry.file_name().to_string_lossy()); + /// } + /// # + /// # Ok(()) }) } + /// ``` + pub async fn read_dir(&self) -> io::Result { + fs::read_dir(self).await + } + + /// Returns `true` if the path points at an existing entity. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. + /// + /// If you cannot access the directory containing the file, e.g., because of a + /// permission error, this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::path::Path; + /// assert_eq!(Path::new("does_not_exist.txt").exists().await, false); + /// # + /// # Ok(()) }) } + /// ``` + /// + /// # See Also + /// + /// This is a convenience function that coerces errors to false. If you want to + /// check errors, call [fs::metadata]. + /// + /// [fs::metadata]: ../fs/fn.metadata.html + pub async fn exists(&self) -> bool { + fs::metadata(self).await.is_ok() + } + + /// Returns `true` if the path exists on disk and is pointing at a regular file. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. + /// + /// If you cannot access the directory containing the file, e.g., because of a + /// permission error, this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::path::Path; + /// assert_eq!(Path::new("./is_a_directory/").is_file().await, false); + /// assert_eq!(Path::new("a_file.txt").is_file().await, true); + /// # + /// # Ok(()) }) } + /// ``` + /// + /// # See Also + /// + /// This is a convenience function that coerces errors to false. If you want to + /// check errors, call [fs::metadata] and handle its Result. Then call + /// [fs::Metadata::is_file] if it was Ok. + /// + /// [fs::metadata]: ../fs/fn.metadata.html + /// [fs::Metadata::is_file]: ../fs/struct.Metadata.html#method.is_file + pub async fn is_file(&self) -> bool { + fs::metadata(self) + .await + .map(|m| m.is_file()) + .unwrap_or(false) + } + + /// Returns `true` if the path exists on disk and is pointing at a directory. + /// + /// This function will traverse symbolic links to query information about the + /// destination file. In case of broken symbolic links this will return `false`. + /// + /// If you cannot access the directory containing the file, e.g., because of a + /// permission error, this will return `false`. + /// + /// # Examples + /// + /// ```no_run + /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + /// # + /// use async_std::path::Path; + /// + /// assert_eq!(Path::new("./is_a_directory/").is_dir().await, true); + /// assert_eq!(Path::new("a_file.txt").is_dir().await, false); + /// # + /// # Ok(()) }) } + /// ``` + /// + /// # See Also + /// + /// This is a convenience function that coerces errors to false. If you want to + /// check errors, call [fs::metadata] and handle its Result. Then call + /// [fs::Metadata::is_dir] if it was Ok. + /// + /// [fs::metadata]: ../fs/fn.metadata.html + /// [fs::Metadata::is_dir]: ../fs/struct.Metadata.html#method.is_dir + pub async fn is_dir(&self) -> bool { + fs::metadata(self) + .await + .map(|m| m.is_dir()) + .unwrap_or(false) + } + + /// Converts a [`Box`][`Box`] into a [`PathBuf`] without copying or + /// allocating. + /// + /// [`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html + /// [`PathBuf`]: struct.PathBuf.html + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// let path: Box = Path::new("foo.txt").into(); + /// let path_buf = path.into_path_buf(); + /// ``` + pub fn into_path_buf(self: Box) -> PathBuf { + let rw = Box::into_raw(self) as *mut std::path::Path; + let inner = unsafe { Box::from_raw(rw) }; + inner.into_path_buf().into() + } +} + +impl From<&Path> for Box { + fn from(path: &Path) -> Box { + let boxed: Box = path.inner.into(); + let rw = Box::into_raw(boxed) as *mut Path; + unsafe { Box::from_raw(rw) } + } +} + +impl From<&Path> for Arc { + /// Converts a Path into a Rc by copying the Path data into a new Rc buffer. + #[inline] + fn from(s: &Path) -> Arc { + let arc: Arc = Arc::from(s.as_os_str()); + unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Path) } + } +} + +impl From<&Path> for Rc { + #[inline] + fn from(s: &Path) -> Rc { + let rc: Rc = Rc::from(s.as_os_str()); + unsafe { Rc::from_raw(Rc::into_raw(rc) as *const Path) } + } +} + +impl ToOwned for Path { + type Owned = PathBuf; + + fn to_owned(&self) -> PathBuf { + self.to_path_buf() + } +} + +impl AsRef for Path { + fn as_ref(&self) -> &OsStr { + self.inner.as_ref() + } +} + +impl AsRef for Path { + fn as_ref(&self) -> &Path { + self + } +} + +impl AsRef for OsStr { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl<'a> From<&'a Path> for Cow<'a, Path> { + #[inline] + fn from(s: &'a Path) -> Cow<'a, Path> { + Cow::Borrowed(s) + } +} + +impl AsRef for Cow<'_, OsStr> { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for OsString { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for str { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for String { + fn as_ref(&self) -> &Path { + Path::new(self) + } +} + +impl AsRef for PathBuf { + fn as_ref(&self) -> &Path { + self + } +} + +impl<'a> IntoIterator for &'a PathBuf { + type Item = &'a OsStr; + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Iter<'a> { + self.iter() + } +} + +impl<'a> IntoIterator for &'a Path { + type Item = &'a OsStr; + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Iter<'a> { + self.iter() + } +} + +macro_rules! impl_cmp { + ($lhs:ty, $rhs: ty) => { + impl<'a, 'b> PartialEq<$rhs> for $lhs { + #[inline] + fn eq(&self, other: &$rhs) -> bool { + ::eq(self, other) + } + } + + impl<'a, 'b> PartialEq<$lhs> for $rhs { + #[inline] + fn eq(&self, other: &$lhs) -> bool { + ::eq(self, other) + } + } + + impl<'a, 'b> PartialOrd<$rhs> for $lhs { + #[inline] + fn partial_cmp(&self, other: &$rhs) -> Option { + ::partial_cmp(self, other) + } + } + + impl<'a, 'b> PartialOrd<$lhs> for $rhs { + #[inline] + fn partial_cmp(&self, other: &$lhs) -> Option { + ::partial_cmp(self, other) + } + } + }; +} + +impl_cmp!(PathBuf, Path); +impl_cmp!(PathBuf, &'a Path); +impl_cmp!(Cow<'a, Path>, Path); +impl_cmp!(Cow<'a, Path>, &'b Path); +impl_cmp!(Cow<'a, Path>, PathBuf); + +macro_rules! impl_cmp_os_str { + ($lhs:ty, $rhs: ty) => { + impl<'a, 'b> PartialEq<$rhs> for $lhs { + #[inline] + fn eq(&self, other: &$rhs) -> bool { + ::eq(self, other.as_ref()) + } + } + + impl<'a, 'b> PartialEq<$lhs> for $rhs { + #[inline] + fn eq(&self, other: &$lhs) -> bool { + ::eq(self.as_ref(), other) + } + } + + impl<'a, 'b> PartialOrd<$rhs> for $lhs { + #[inline] + fn partial_cmp(&self, other: &$rhs) -> Option { + ::partial_cmp(self, other.as_ref()) + } + } + + impl<'a, 'b> PartialOrd<$lhs> for $rhs { + #[inline] + fn partial_cmp(&self, other: &$lhs) -> Option { + ::partial_cmp(self.as_ref(), other) + } + } + }; +} + +impl_cmp_os_str!(PathBuf, OsStr); +impl_cmp_os_str!(PathBuf, &'a OsStr); +impl_cmp_os_str!(PathBuf, Cow<'a, OsStr>); +impl_cmp_os_str!(PathBuf, OsString); +impl_cmp_os_str!(Path, OsStr); +impl_cmp_os_str!(Path, &'a OsStr); +impl_cmp_os_str!(Path, Cow<'a, OsStr>); +impl_cmp_os_str!(Path, OsString); +impl_cmp_os_str!(&'a Path, OsStr); +impl_cmp_os_str!(&'a Path, Cow<'b, OsStr>); +impl_cmp_os_str!(&'a Path, OsString); + +impl<'a> From<&'a std::path::Path> for &'a Path { + fn from(path: &'a std::path::Path) -> &'a Path { + &Path::new(path.as_os_str()) + } +} + +impl<'a> Into<&'a std::path::Path> for &'a Path { + fn into(self) -> &'a std::path::Path { + std::path::Path::new(&self.inner) + } +} + +impl AsRef for Path { + fn as_ref(&self) -> &std::path::Path { + self.into() + } +} + +impl AsRef for std::path::Path { + fn as_ref(&self) -> &Path { + self.into() + } +} + +impl AsRef for std::path::PathBuf { + fn as_ref(&self) -> &Path { + let p: &std::path::Path = self.as_ref(); + p.into() + } +} diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs new file mode 100644 index 00000000..e684df89 --- /dev/null +++ b/src/path/pathbuf.rs @@ -0,0 +1,371 @@ +use std::borrow::{Borrow, Cow}; +use std::ffi::{OsStr, OsString}; +use std::iter::{self, FromIterator}; +use std::ops::Deref; +#[cfg(feature = "unstable")] +use std::pin::Pin; +use std::rc::Rc; +use std::str::FromStr; +use std::sync::Arc; + +use crate::path::Path; +#[cfg(feature = "unstable")] +use crate::prelude::*; +#[cfg(feature = "unstable")] +use crate::stream::{self, FromStream, IntoStream}; + +/// This struct is an async version of [`std::path::PathBuf`]. +/// +/// [`std::path::Path`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PathBuf { + inner: std::path::PathBuf, +} + +impl PathBuf { + /// Allocates an empty `PathBuf`. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::PathBuf; + /// + /// let path = PathBuf::new(); + /// ``` + pub fn new() -> PathBuf { + std::path::PathBuf::new().into() + } + + /// Coerces to a [`Path`] slice. + /// + /// [`Path`]: struct.Path.html + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// let p = PathBuf::from("/test"); + /// assert_eq!(Path::new("/test"), p.as_path()); + /// ``` + pub fn as_path(&self) -> &Path { + self.inner.as_path().into() + } + + /// Extends `self` with `path`. + /// + /// If `path` is absolute, it replaces the current path. + /// + /// On Windows: + /// + /// * if `path` has a root but no prefix (e.g., `\windows`), it + /// replaces everything except for the prefix (if any) of `self`. + /// * if `path` has a prefix but no root, it replaces `self`. + /// + /// # Examples + /// + /// Pushing a relative path extends the existing path: + /// + /// ``` + /// use async_std::path::PathBuf; + /// + /// let mut path = PathBuf::from("/tmp"); + /// path.push("file.bk"); + /// assert_eq!(path, PathBuf::from("/tmp/file.bk")); + /// ``` + /// + /// Pushing an absolute path replaces the existing path: + /// + /// ``` + /// use async_std::path::PathBuf; + /// + /// let mut path = PathBuf::from("/tmp"); + /// path.push("/etc"); + /// assert_eq!(path, PathBuf::from("/etc")); + /// ``` + pub fn push>(&mut self, path: P) { + self.inner.push(path.as_ref()) + } + + /// Truncates `self` to [`self.parent`]. + /// + /// Returns `false` and does nothing if [`self.parent`] is [`None`]. + /// Otherwise, returns `true`. + /// + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// [`self.parent`]: struct.PathBuf.html#method.parent + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// let mut p = PathBuf::from("/test/test.rs"); + /// + /// p.pop(); + /// assert_eq!(Path::new("/test"), p); + /// p.pop(); + /// assert_eq!(Path::new("/"), p); + /// ``` + pub fn pop(&mut self) -> bool { + self.inner.pop() + } + + /// Updates [`self.file_name`] to `file_name`. + /// + /// If [`self.file_name`] was [`None`], this is equivalent to pushing + /// `file_name`. + /// + /// Otherwise it is equivalent to calling [`pop`] and then pushing + /// `file_name`. The new path will be a sibling of the original path. + /// (That is, it will have the same parent.) + /// + /// [`self.file_name`]: struct.PathBuf.html#method.file_name + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// [`pop`]: struct.PathBuf.html#method.pop + /// + /// # Examples + /// + /// ``` + /// use async_std::path::PathBuf; + /// + /// let mut buf = PathBuf::from("/"); + /// assert!(buf.file_name() == None); + /// buf.set_file_name("bar"); + /// assert!(buf == PathBuf::from("/bar")); + /// assert!(buf.file_name().is_some()); + /// buf.set_file_name("baz.txt"); + /// assert!(buf == PathBuf::from("/baz.txt")); + /// ``` + pub fn set_file_name>(&mut self, file_name: S) { + self.inner.set_file_name(file_name) + } + + /// Updates [`self.extension`] to `extension`. + /// + /// Returns `false` and does nothing if [`self.file_name`] is [`None`], + /// returns `true` and updates the extension otherwise. + /// + /// If [`self.extension`] is [`None`], the extension is added; otherwise + /// it is replaced. + /// + /// [`self.file_name`]: struct.PathBuf.html#method.file_name + /// [`self.extension`]: struct.PathBuf.html#method.extension + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, PathBuf}; + /// + /// let mut p = PathBuf::from("/feel/the"); + /// + /// p.set_extension("force"); + /// assert_eq!(Path::new("/feel/the.force"), p.as_path()); + /// + /// p.set_extension("dark_side"); + /// assert_eq!(Path::new("/feel/the.dark_side"), p.as_path()); + /// ``` + pub fn set_extension>(&mut self, extension: S) -> bool { + self.inner.set_extension(extension) + } + + /// Consumes the `PathBuf`, returning its internal [`OsString`] storage. + /// + /// [`OsString`]: https://doc.rust-lang.org/std/ffi/struct.OsString.html + /// + /// # Examples + /// + /// ``` + /// use async_std::path::PathBuf; + /// + /// let p = PathBuf::from("/the/head"); + /// let os_str = p.into_os_string(); + /// ``` + pub fn into_os_string(self) -> OsString { + self.inner.into_os_string() + } + + /// Converts this `PathBuf` into a [boxed][`Box`] [`Path`]. + /// + /// [`Box`]: https://doc.rust-lang.org/std/boxed/struct.Box.html + /// [`Path`]: struct.Path.html + pub fn into_boxed_path(self) -> Box { + let rw = Box::into_raw(self.inner.into_boxed_path()) as *mut Path; + unsafe { Box::from_raw(rw) } + } +} + +impl From> for PathBuf { + fn from(boxed: Box) -> PathBuf { + boxed.into_path_buf() + } +} + +impl From for Box { + fn from(p: PathBuf) -> Box { + p.into_boxed_path() + } +} + +impl Clone for Box { + #[inline] + fn clone(&self) -> Self { + self.to_path_buf().into_boxed_path() + } +} + +impl> From<&T> for PathBuf { + fn from(s: &T) -> PathBuf { + PathBuf::from(s.as_ref().to_os_string()) + } +} + +impl From for PathBuf { + fn from(s: OsString) -> PathBuf { + PathBuf { inner: s.into() } + } +} + +impl From for OsString { + fn from(path_buf: PathBuf) -> OsString { + path_buf.inner.into() + } +} + +impl From for PathBuf { + fn from(s: String) -> PathBuf { + PathBuf::from(OsString::from(s)) + } +} + +impl FromStr for PathBuf { + type Err = core::convert::Infallible; + + fn from_str(s: &str) -> Result { + Ok(PathBuf::from(s)) + } +} + +impl> FromIterator

for PathBuf { + fn from_iter>(iter: I) -> PathBuf { + let mut buf = PathBuf::new(); + buf.extend(iter); + buf + } +} + +impl> iter::Extend

for PathBuf { + fn extend>(&mut self, iter: I) { + iter.into_iter().for_each(move |p| self.push(p.as_ref())); + } +} + +impl Deref for PathBuf { + type Target = Path; + + fn deref(&self) -> &Path { + Path::new(&self.inner) + } +} + +impl Borrow for PathBuf { + fn borrow(&self) -> &Path { + self.deref() + } +} + +impl<'a> From for Cow<'a, Path> { + #[inline] + fn from(s: PathBuf) -> Cow<'a, Path> { + Cow::Owned(s) + } +} + +impl<'a> From<&'a PathBuf> for Cow<'a, Path> { + #[inline] + fn from(p: &'a PathBuf) -> Cow<'a, Path> { + Cow::Borrowed(p.as_path()) + } +} + +impl<'a> From> for PathBuf { + #[inline] + fn from(p: Cow<'a, Path>) -> Self { + p.into_owned() + } +} + +impl From for Arc { + #[inline] + fn from(s: PathBuf) -> Arc { + let arc: Arc = Arc::from(s.into_os_string()); + unsafe { Arc::from_raw(Arc::into_raw(arc) as *const Path) } + } +} + +impl From for Rc { + #[inline] + fn from(s: PathBuf) -> Rc { + let rc: Rc = Rc::from(s.into_os_string()); + unsafe { Rc::from_raw(Rc::into_raw(rc) as *const Path) } + } +} + +impl AsRef for PathBuf { + fn as_ref(&self) -> &OsStr { + self.inner.as_ref() + } +} + +#[cfg(feature = "unstable")] +impl> stream::Extend

for PathBuf { + fn extend<'a, S: IntoStream + 'a>( + &'a mut self, + stream: S, + ) -> Pin + 'a>> { + let stream = stream.into_stream(); + + Box::pin(async move { + pin_utils::pin_mut!(stream); + + while let Some(item) = stream.next().await { + self.push(item.as_ref()); + } + }) + } +} + +#[cfg(feature = "unstable")] +impl<'b, P: AsRef + 'b> FromStream

for PathBuf { + #[inline] + fn from_stream<'a, S: IntoStream + 'a>( + stream: S, + ) -> Pin + 'a>> { + let stream = stream.into_stream(); + + Box::pin(async move { + let mut out = Self::new(); + stream::extend(&mut out, stream).await; + out + }) + } +} + +impl From for PathBuf { + fn from(path: std::path::PathBuf) -> PathBuf { + PathBuf { inner: path } + } +} + +impl Into for PathBuf { + fn into(self) -> std::path::PathBuf { + self.inner + } +} + +impl AsRef for PathBuf { + fn as_ref(&self) -> &std::path::Path { + self.inner.as_ref() + } +} diff --git a/src/pin.rs b/src/pin/mod.rs similarity index 100% rename from src/pin.rs rename to src/pin/mod.rs diff --git a/src/prelude.rs b/src/prelude.rs index 645d7a24..a2a14a18 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -11,28 +11,43 @@ //! use async_std::prelude::*; //! ``` -#[doc(no_inline)] -pub use crate::future::Future; -#[doc(no_inline)] -pub use crate::io::BufRead as _; -#[doc(no_inline)] -pub use crate::io::Read as _; -#[doc(no_inline)] -pub use crate::io::Seek as _; -#[doc(no_inline)] -pub use crate::io::Write as _; -#[doc(no_inline)] -pub use crate::stream::Stream; -#[doc(no_inline)] -pub use crate::task_local; +cfg_std! { + #[doc(no_inline)] + pub use std::future::Future; + #[doc(no_inline)] + pub use crate::stream::Stream; -#[doc(hidden)] -pub use crate::io::buf_read::BufReadExt as _; -#[doc(hidden)] -pub use crate::io::read::ReadExt as _; -#[doc(hidden)] -pub use crate::io::seek::SeekExt as _; -#[doc(hidden)] -pub use crate::io::write::WriteExt as _; -#[doc(hidden)] -pub use crate::stream::stream::StreamExt as _; + #[doc(inline)] + pub use crate::future::future::FutureExt; + #[doc(inline)] + pub use crate::stream::stream::StreamExt; + #[doc(no_inline)] + pub use crate::io::BufRead as _; + #[doc(no_inline)] + pub use crate::io::Read as _; + #[doc(no_inline)] + pub use crate::io::Seek as _; + #[doc(no_inline)] + pub use crate::io::Write as _; + + #[doc(no_inline)] + pub use crate::io::prelude::BufReadExt as _; + #[doc(no_inline)] + pub use crate::io::prelude::ReadExt as _; + #[doc(no_inline)] + pub use crate::io::prelude::SeekExt as _; + #[doc(no_inline)] + pub use crate::io::prelude::WriteExt as _; +} + +cfg_default! { + #[doc(no_inline)] + pub use crate::task_local; +} + +cfg_unstable! { + #[doc(no_inline)] + pub use crate::stream::DoubleEndedStream; + #[doc(no_inline)] + pub use crate::stream::ExactSizeStream; +} diff --git a/src/process/mod.rs b/src/process/mod.rs new file mode 100644 index 00000000..630c5b9b --- /dev/null +++ b/src/process/mod.rs @@ -0,0 +1,14 @@ +//! A module for working with processes. +//! +//! This module is mostly concerned with spawning and interacting with child processes, but it also +//! provides abort and exit for terminating the current process. +//! +//! This is an async version of [`std::process`]. +//! +//! [`std::process`]: https://doc.rust-lang.org/std/process/index.html + +// Re-export structs. +pub use std::process::{ExitStatus, Output}; + +// Re-export functions. +pub use std::process::{abort, exit, id}; diff --git a/src/result/from_stream.rs b/src/result/from_stream.rs index 6033eb97..a8490d69 100644 --- a/src/result/from_stream.rs +++ b/src/result/from_stream.rs @@ -11,17 +11,12 @@ where /// elements are taken, and the `Err` is returned. Should no `Err` /// occur, a container with the values of each `Result` is returned. #[inline] - fn from_stream<'a, S: IntoStream>>( + fn from_stream<'a, S: IntoStream> + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - // Using `scan` here because it is able to stop the stream early // if a failure occurs let mut found_error = None; diff --git a/src/result/mod.rs b/src/result/mod.rs index 908f9c4d..cae0ebd9 100644 --- a/src/result/mod.rs +++ b/src/result/mod.rs @@ -7,3 +7,8 @@ mod from_stream; #[doc(inline)] pub use std::result::Result; + +cfg_unstable! { + mod product; + mod sum; +} diff --git a/src/result/product.rs b/src/result/product.rs new file mode 100644 index 00000000..ec9d94a8 --- /dev/null +++ b/src/result/product.rs @@ -0,0 +1,62 @@ +use std::pin::Pin; + +use crate::prelude::*; +use crate::stream::{Stream, Product}; + +impl Product> for Result +where + T: Product, +{ + #[doc = r#" + Takes each element in the `Stream`: if it is an `Err`, no further + elements are taken, and the `Err` is returned. Should no `Err` occur, + the product of all elements is returned. + + # Examples + + This multiplies every integer in a vector, rejecting the product if a negative element is + encountered: + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let v = stream::from_iter(vec![1, 2, 4]); + let res: Result = v.map(|x| + if x < 0 { + Err("Negative element found") + } else { + Ok(x) + }).product().await; + assert_eq!(res, Ok(8)); + # + # }) } + ``` + "#] + fn product<'a, S>(stream: S) -> Pin> + 'a>> + where S: Stream> + 'a + { + Box::pin(async move { + // Using `scan` here because it is able to stop the stream early + // if a failure occurs + let mut found_error = None; + let out = >::product(stream + .scan((), |_, elem| { + match elem { + Ok(elem) => Some(elem), + Err(err) => { + found_error = Some(err); + // Stop processing the stream on error + None + } + } + })).await; + match found_error { + Some(err) => Err(err), + None => Ok(out) + } + }) + } +} diff --git a/src/result/sum.rs b/src/result/sum.rs new file mode 100644 index 00000000..ccc4240e --- /dev/null +++ b/src/result/sum.rs @@ -0,0 +1,62 @@ +use std::pin::Pin; + +use crate::prelude::*; +use crate::stream::{Stream, Sum}; + +impl Sum> for Result +where + T: Sum, +{ + #[doc = r#" + Takes each element in the `Stream`: if it is an `Err`, no further + elements are taken, and the `Err` is returned. Should no `Err` occur, + the sum of all elements is returned. + + # Examples + + This sums up every integer in a vector, rejecting the sum if a negative + element is encountered: + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let v = stream::from_iter(vec![1, 2]); + let res: Result = v.map(|x| + if x < 0 { + Err("Negative element found") + } else { + Ok(x) + }).sum().await; + assert_eq!(res, Ok(3)); + # + # }) } + ``` + "#] + fn sum<'a, S>(stream: S) -> Pin> + 'a>> + where S: Stream> + 'a + { + Box::pin(async move { + // Using `scan` here because it is able to stop the stream early + // if a failure occurs + let mut found_error = None; + let out = >::sum(stream + .scan((), |_, elem| { + match elem { + Ok(elem) => Some(elem), + Err(err) => { + found_error = Some(err); + // Stop processing the stream on error + None + } + } + })).await; + match found_error { + Some(err) => Err(err), + None => Ok(out) + } + }) + } +} diff --git a/src/stream/double_ended_stream.rs b/src/stream/double_ended_stream.rs index 6fab77c2..129bb1cd 100644 --- a/src/stream/double_ended_stream.rs +++ b/src/stream/double_ended_stream.rs @@ -10,8 +10,8 @@ use std::task::{Context, Poll}; /// `Item`s from the back, as well as the front. /// /// [`Stream`]: trait.Stream.html +#[cfg(feature = "unstable")] #[cfg_attr(feature = "docs", doc(cfg(unstable)))] -#[cfg(any(feature = "unstable", feature = "docs"))] pub trait DoubleEndedStream: Stream { /// Removes and returns an element from the end of the stream. /// diff --git a/src/stream/empty.rs b/src/stream/empty.rs index c9deea86..49090707 100644 --- a/src/stream/empty.rs +++ b/src/stream/empty.rs @@ -6,10 +6,15 @@ use crate::task::{Context, Poll}; /// Creates a stream that doesn't yield any items. /// +/// This `struct` is created by the [`empty`] function. See its +/// documentation for more. +/// +/// [`empty`]: fn.empty.html +/// /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # /// use async_std::prelude::*; /// use async_std::stream; @@ -18,7 +23,7 @@ use crate::task::{Context, Poll}; /// /// assert_eq!(s.next().await, None); /// # -/// # }) } +/// # }) /// ``` pub fn empty() -> Empty { Empty { diff --git a/src/stream/exact_size_stream.rs b/src/stream/exact_size_stream.rs new file mode 100644 index 00000000..8b6ba97d --- /dev/null +++ b/src/stream/exact_size_stream.rs @@ -0,0 +1,120 @@ +pub use crate::stream::Stream; + +/// A stream that knows its exact length. +/// +/// Many [`Stream`]s don't know how many times they will iterate, but some do. +/// If a stream knows how many times it can iterate, providing access to +/// that information can be useful. For example, if you want to iterate +/// backwards, a good start is to know where the end is. +/// +/// When implementing an `ExactSizeStream`, you must also implement +/// [`Stream`]. When doing so, the implementation of [`size_hint`] *must* +/// return the exact size of the stream. +/// +/// [`Stream`]: trait.Stream.html +/// [`size_hint`]: trait.Stream.html#method.size_hint +/// +/// The [`len`] method has a default implementation, so you usually shouldn't +/// implement it. However, you may be able to provide a more performant +/// implementation than the default, so overriding it in this case makes sense. +/// +/// [`len`]: #method.len +/// +/// # Examples +/// +/// Basic usage: +/// +/// ``` +/// // a finite range knows exactly how many times it will iterate +/// let five = 0..5; +/// +/// assert_eq!(5, five.len()); +/// ``` +/// +/// In the [module level docs][moddocs], we implemented an [`Stream`], +/// `Counter`. Let's implement `ExactSizeStream` for it as well: +/// +/// [moddocs]: index.html +/// +/// ``` +/// # use std::task::{Context, Poll}; +/// # use std::pin::Pin; +/// # use async_std::prelude::*; +/// # struct Counter { +/// # count: usize, +/// # } +/// # impl Counter { +/// # fn new() -> Counter { +/// # Counter { count: 0 } +/// # } +/// # } +/// # impl Stream for Counter { +/// # type Item = usize; +/// # fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { +/// # self.count += 1; +/// # if self.count < 6 { +/// # Poll::Ready(Some(self.count)) +/// # } else { +/// # Poll::Ready(None) +/// # } +/// # } +/// # } +/// # async_std::task::block_on(async { +/// # +/// impl ExactSizeStream for Counter { +/// // We can easily calculate the remaining number of iterations. +/// fn len(&self) -> usize { +/// 5 - self.count +/// } +/// } +/// +/// // And now we can use it! +/// +/// let counter = Counter::new(); +/// +/// assert_eq!(5, counter.len()); +/// # }); +/// ``` +#[allow(clippy::len_without_is_empty)] // ExactSizeIterator::is_empty is unstable +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +pub trait ExactSizeStream: Stream { + /// Returns the exact number of times the stream will iterate. + /// + /// This method has a default implementation, so you usually should not + /// implement it directly. However, if you can provide a more efficient + /// implementation, you can do so. See the [trait-level] docs for an + /// example. + /// + /// This function has the same safety guarantees as the [`size_hint`] + /// function. + /// + /// [trait-level]: trait.ExactSizeStream.html + /// [`size_hint`]: trait.Stream.html#method.size_hint + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// // a finite range knows exactly how many times it will iterate + /// let five = 0..5; + /// + /// assert_eq!(5, five.len()); + /// ``` + fn len(&self) -> usize { + let (lower, upper) = self.size_hint(); + // Note: This assertion is overly defensive, but it checks the invariant + // guaranteed by the trait. If this trait were rust-internal, + // we could use debug_assert!; assert_eq! will check all Rust user + // implementations too. + assert_eq!(upper, Some(lower)); + lower + } +} + +impl ExactSizeStream for &mut I { + fn len(&self) -> usize { + (**self).len() + } +} diff --git a/src/stream/extend.rs b/src/stream/extend.rs index 27efd8b7..c48fe1ed 100644 --- a/src/stream/extend.rs +++ b/src/stream/extend.rs @@ -3,7 +3,7 @@ use std::pin::Pin; use crate::prelude::*; use crate::stream::IntoStream; -/// Extend a collection with the contents of a stream. +/// Extends a collection with the contents of a stream. /// /// Streams produce a series of values asynchronously, and collections can also be thought of as a /// series of values. The `Extend` trait bridges this gap, allowing you to extend a collection @@ -14,39 +14,61 @@ use crate::stream::IntoStream; /// ## Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # /// use async_std::prelude::*; -/// use async_std::stream::{self, Extend}; +/// use async_std::stream; /// /// let mut v: Vec = vec![1, 2]; /// let s = stream::repeat(3usize).take(3); -/// v.stream_extend(s).await; +/// stream::Extend::extend(&mut v, s).await; /// /// assert_eq!(v, vec![1, 2, 3, 3, 3]); /// # -/// # }) } +/// # }) /// ``` +#[cfg(feature = "unstable")] #[cfg_attr(feature = "docs", doc(cfg(unstable)))] pub trait Extend { /// Extends a collection with the contents of a stream. - fn stream_extend<'a, T: IntoStream + 'a>( + fn extend<'a, T: IntoStream + 'a>( &'a mut self, stream: T, - ) -> Pin + 'a>> - where - A: 'a; + ) -> Pin + 'a>>; } -impl Extend<()> for () { - fn stream_extend<'a, T: IntoStream + 'a>( - &'a mut self, - stream: T, - ) -> Pin + 'a>> { - let stream = stream.into_stream(); - Box::pin(async move { - pin_utils::pin_mut!(stream); - while let Some(_) = stream.next().await {} - }) - } +/// Extends a collection with the contents of a stream. +/// +/// Streams produce a series of values asynchronously, and collections can also be thought of as a +/// series of values. The [`Extend`] trait bridges this gap, allowing you to extend a collection +/// asynchronously by including the contents of that stream. When extending a collection with an +/// already existing key, that entry is updated or, in the case of collections that permit multiple +/// entries with equal keys, that entry is inserted. +/// +/// [`Extend`]: trait.Extend.html +/// +/// ## Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::prelude::*; +/// use async_std::stream; +/// +/// let mut v: Vec = vec![1, 2]; +/// let s = stream::repeat(3usize).take(3); +/// stream::extend(&mut v, s).await; +/// +/// assert_eq!(v, vec![1, 2, 3, 3, 3]); +/// # +/// # }) +/// ``` +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +pub async fn extend<'a, C, T, S>(collection: &mut C, stream: S) +where + C: Extend, + S: IntoStream + 'a, +{ + Extend::extend(collection, stream).await } diff --git a/src/stream/from_fn.rs b/src/stream/from_fn.rs new file mode 100644 index 00000000..8067176e --- /dev/null +++ b/src/stream/from_fn.rs @@ -0,0 +1,68 @@ +use std::pin::Pin; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +/// A stream that yields elements by calling a closure. +/// +/// This stream is created by the [`from_fn`] function. See its +/// documentation for more. +/// +/// [`from_fn`]: fn.from_fn.html +#[derive(Clone, Debug)] +pub struct FromFn { + f: F, +} + +impl Unpin for FromFn {} + +/// Creates a new stream where to produce each new element a provided closure is called. +/// +/// This allows creating a custom stream with any behaviour without using the more verbose +/// syntax of creating a dedicated type and implementing a `Stream` trait for it. +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::prelude::*; +/// use async_std::stream; +/// +/// let mut count = 0u8; +/// let s = stream::from_fn(|| { +/// count += 1; +/// if count > 3 { +/// None +/// } else { +/// Some(count) +/// } +/// }); +/// +/// pin_utils::pin_mut!(s); +/// +/// assert_eq!(s.next().await, Some(1)); +/// assert_eq!(s.next().await, Some(2)); +/// assert_eq!(s.next().await, Some(3)); +/// assert_eq!(s.next().await, None); +/// # +/// # }) +/// ``` +pub fn from_fn(f: F) -> FromFn +where + F: FnMut() -> Option, +{ + FromFn { f } +} + +impl Stream for FromFn +where + F: FnMut() -> Option, +{ + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + let item = (&mut self.f)(); + Poll::Ready(item) + } +} diff --git a/src/stream/from_iter.rs b/src/stream/from_iter.rs new file mode 100644 index 00000000..d7a31d6c --- /dev/null +++ b/src/stream/from_iter.rs @@ -0,0 +1,53 @@ +use std::pin::Pin; + +use pin_project_lite::pin_project; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + /// A stream that was created from iterator. + /// + /// This stream is created by the [`from_iter`] function. + /// See it documentation for more. + /// + /// [`from_iter`]: fn.from_iter.html + #[derive(Clone, Debug)] + pub struct FromIter { + iter: I, + } +} + +/// Converts an iterator into a stream. +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::prelude::*; +/// use async_std::stream; +/// +/// let mut s = stream::from_iter(vec![0, 1, 2, 3]); +/// +/// assert_eq!(s.next().await, Some(0)); +/// assert_eq!(s.next().await, Some(1)); +/// assert_eq!(s.next().await, Some(2)); +/// assert_eq!(s.next().await, Some(3)); +/// assert_eq!(s.next().await, None); +/// # +/// # }) +/// ``` +pub fn from_iter(iter: I) -> FromIter { + FromIter { + iter: iter.into_iter(), + } +} + +impl Stream for FromIter { + type Item = I::Item; + + fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(self.iter.next()) + } +} diff --git a/src/stream/from_stream.rs b/src/stream/from_stream.rs index 047dab8f..67b9b3df 100644 --- a/src/stream/from_stream.rs +++ b/src/stream/from_stream.rs @@ -1,7 +1,8 @@ -use super::IntoStream; - +use std::future::Future; use std::pin::Pin; +use crate::stream::IntoStream; + /// Conversion from a `Stream`. /// /// By implementing `FromStream` for a type, you define how it will be created from a stream. @@ -15,22 +16,24 @@ use std::pin::Pin; /// /// ``` /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { -/// use crate::async_std::stream::FromStream; -/// use async_std::prelude::*; -/// use async_std::stream; +/// # +/// use async_std::prelude::*; +/// use async_std::stream::{self, FromStream}; /// -/// let five_fives = stream::repeat(5).take(5); +/// let five_fives = stream::repeat(5).take(5); /// -/// let v = Vec::from_stream(five_fives).await; +/// let v = Vec::from_stream(five_fives).await; /// -/// assert_eq!(v, vec![5, 5, 5, 5, 5]); +/// assert_eq!(v, vec![5, 5, 5, 5, 5]); +/// # /// # Ok(()) }) } /// ``` /// /// Using `collect` to implicitly use `FromStream` /// -///``` +/// ``` /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +/// # /// use async_std::prelude::*; /// use async_std::stream; /// let five_fives = stream::repeat(5).take(5); @@ -40,14 +43,13 @@ use std::pin::Pin; /// assert_eq!(v, vec![5, 5, 5, 5, 5]); /// # /// # Ok(()) }) } -///``` +/// ``` /// /// Implementing `FromStream` for your type: /// /// ``` /// use async_std::prelude::*; -/// use async_std::stream::{Extend, FromStream, IntoStream}; -/// use async_std::stream; +/// use async_std::stream::{self, FromStream, IntoStream}; /// use std::pin::Pin; /// /// // A sample collection, that's just a wrapper over Vec @@ -70,14 +72,14 @@ use std::pin::Pin; /// impl FromStream for MyCollection { /// fn from_stream<'a, S: IntoStream + 'a>( /// stream: S, -/// ) -> Pin + 'a>> { +/// ) -> Pin + 'a>> { /// let stream = stream.into_stream(); /// /// Box::pin(async move { /// let mut c = MyCollection::new(); /// /// let mut v = vec![]; -/// v.stream_extend(stream).await; +/// stream::extend(&mut v, stream).await; /// /// for i in v { /// c.add(i); @@ -88,6 +90,7 @@ use std::pin::Pin; /// } /// /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +/// # /// // Now we can make a new stream... /// let stream = stream::repeat(5).take(5); /// @@ -102,12 +105,13 @@ use std::pin::Pin; /// let c: MyCollection = stream.collect().await; /// /// assert_eq!(c.0, vec![5, 5, 5, 5, 5]); +/// # /// # Ok(()) }) } ///``` /// /// [`IntoStream`]: trait.IntoStream.html +#[cfg(feature = "unstable")] #[cfg_attr(feature = "docs", doc(cfg(unstable)))] -#[cfg(any(feature = "unstable", feature = "docs"))] pub trait FromStream { /// Creates a value from a stream. /// @@ -117,18 +121,19 @@ pub trait FromStream { /// /// ``` /// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { - /// use crate::async_std::stream::FromStream; - /// use async_std::prelude::*; - /// use async_std::stream; + /// # + /// use async_std::prelude::*; + /// use async_std::stream::{self, FromStream}; /// - /// let five_fives = stream::repeat(5).take(5); + /// let five_fives = stream::repeat(5).take(5); /// - /// let v = Vec::from_stream(five_fives).await; + /// let v = Vec::from_stream(five_fives).await; /// - /// assert_eq!(v, vec![5, 5, 5, 5, 5]); + /// assert_eq!(v, vec![5, 5, 5, 5, 5]); + /// # /// # Ok(()) }) } /// ``` fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>>; + ) -> Pin + 'a>>; } diff --git a/src/stream/fused_stream.rs b/src/stream/fused_stream.rs new file mode 100644 index 00000000..e14ab5b1 --- /dev/null +++ b/src/stream/fused_stream.rs @@ -0,0 +1,21 @@ +use crate::stream::Stream; + +/// A stream that always continues to yield `None` when exhausted. +/// +/// Calling next on a fused stream that has returned `None` once is guaranteed +/// to return [`None`] again. This trait should be implemented by all streams +/// that behave this way because it allows optimizing [`Stream::fuse`]. +/// +/// Note: In general, you should not use `FusedStream` in generic bounds if +/// you need a fused stream. Instead, you should just call [`Stream::fuse`] +/// on the stream. If the stream is already fused, the additional [`Fuse`] +/// wrapper will be a no-op with no performance penalty. +/// +/// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html#variant.None +/// [`Stream::fuse`]: trait.Stream.html#method.fuse +/// [`Fuse`]: struct.Fuse.html +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +pub trait FusedStream: Stream {} + +impl FusedStream for &mut S {} diff --git a/src/stream/interval.rs b/src/stream/interval.rs new file mode 100644 index 00000000..b0df7141 --- /dev/null +++ b/src/stream/interval.rs @@ -0,0 +1,195 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::{Duration, Instant}; + +use futures_timer::Delay; + +use crate::prelude::*; + +/// Creates a new stream that yields at a set interval. +/// +/// The stream first yields after `dur`, and continues to yield every +/// `dur` after that. The stream accounts for time elapsed between calls, and +/// will adjust accordingly to prevent time skews. +/// +/// Each interval may be slightly longer than the specified duration, but never +/// less. +/// +/// Note that intervals are not intended for high resolution timers, but rather +/// they will likely fire some granularity after the exact instant that they're +/// otherwise indicated to fire at. +/// +/// See also: [`task::sleep`]. +/// +/// [`task::sleep`]: ../task/fn.sleep.html +/// +/// # Examples +/// +/// Basic example: +/// +/// ```no_run +/// use async_std::prelude::*; +/// use async_std::stream; +/// use std::time::Duration; +/// +/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +/// # +/// let mut interval = stream::interval(Duration::from_secs(4)); +/// while let Some(_) = interval.next().await { +/// println!("prints every four seconds"); +/// } +/// # +/// # Ok(()) }) } +/// ``` +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +pub fn interval(dur: Duration) -> Interval { + Interval { + delay: Delay::new(dur), + interval: dur, + } +} + +/// A stream representing notifications at fixed interval +/// +/// This stream is created by the [`interval`] function. See its +/// documentation for more. +/// +/// [`interval`]: fn.interval.html +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[derive(Debug)] +pub struct Interval { + delay: Delay, + interval: Duration, +} + +impl Stream for Interval { + type Item = (); + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if Pin::new(&mut self.delay).poll(cx).is_pending() { + return Poll::Pending; + } + let when = Instant::now(); + let next = next_interval(when, Instant::now(), self.interval); + self.delay.reset(next); + Poll::Ready(Some(())) + } +} + +/// Converts Duration object to raw nanoseconds if possible +/// +/// This is useful to divide intervals. +/// +/// While technically for large duration it's impossible to represent any +/// duration as nanoseconds, the largest duration we can represent is about +/// 427_000 years. Large enough for any interval we would use or calculate in +/// tokio. +fn duration_to_nanos(dur: Duration) -> Option { + dur.as_secs() + .checked_mul(1_000_000_000) + .and_then(|v| v.checked_add(u64::from(dur.subsec_nanos()))) +} + +fn next_interval(prev: Instant, now: Instant, interval: Duration) -> Instant { + let new = prev + interval; + if new > now { + return new; + } + + let spent_ns = duration_to_nanos(now.duration_since(prev)).expect("interval should be expired"); + let interval_ns = + duration_to_nanos(interval).expect("interval is less that 427 thousand years"); + let mult = spent_ns / interval_ns + 1; + assert!( + mult < (1 << 32), + "can't skip more than 4 billion intervals of {:?} \ + (trying to skip {})", + interval, + mult + ); + prev + interval * (mult as u32) +} + +#[cfg(test)] +mod test { + use super::next_interval; + use std::cmp::Ordering; + use std::time::{Duration, Instant}; + + struct Timeline(Instant); + + impl Timeline { + fn new() -> Timeline { + Timeline(Instant::now()) + } + fn at(&self, millis: u64) -> Instant { + self.0 + Duration::from_millis(millis) + } + fn at_ns(&self, sec: u64, nanos: u32) -> Instant { + self.0 + Duration::new(sec, nanos) + } + } + + fn dur(millis: u64) -> Duration { + Duration::from_millis(millis) + } + + // The math around Instant/Duration isn't 100% precise due to rounding + // errors, see #249 for more info + fn almost_eq(a: Instant, b: Instant) -> bool { + match a.cmp(&b) { + Ordering::Equal => true, + Ordering::Greater => a - b < Duration::from_millis(1), + Ordering::Less => b - a < Duration::from_millis(1), + } + } + + #[test] + fn norm_next() { + let tm = Timeline::new(); + assert!(almost_eq( + next_interval(tm.at(1), tm.at(2), dur(10)), + tm.at(11) + )); + assert!(almost_eq( + next_interval(tm.at(7777), tm.at(7788), dur(100)), + tm.at(7877) + )); + assert!(almost_eq( + next_interval(tm.at(1), tm.at(1000), dur(2100)), + tm.at(2101) + )); + } + + #[test] + fn fast_forward() { + let tm = Timeline::new(); + assert!(almost_eq( + next_interval(tm.at(1), tm.at(1000), dur(10)), + tm.at(1001) + )); + assert!(almost_eq( + next_interval(tm.at(7777), tm.at(8888), dur(100)), + tm.at(8977) + )); + assert!(almost_eq( + next_interval(tm.at(1), tm.at(10000), dur(2100)), + tm.at(10501) + )); + } + + /// TODO: this test actually should be successful, but since we can't + /// multiply Duration on anything larger than u32 easily we decided + /// to allow it to fail for now + #[test] + #[should_panic(expected = "can't skip more than 4 billion intervals")] + fn large_skip() { + let tm = Timeline::new(); + assert_eq!( + next_interval(tm.at_ns(0, 1), tm.at_ns(25, 0), Duration::new(0, 2)), + tm.at_ns(25, 1) + ); + } +} diff --git a/src/stream/into_stream.rs b/src/stream/into_stream.rs index 92331814..f96dc59b 100644 --- a/src/stream/into_stream.rs +++ b/src/stream/into_stream.rs @@ -13,8 +13,8 @@ use crate::stream::Stream; /// See also: [`FromStream`]. /// /// [`FromStream`]: trait.FromStream.html +#[cfg(feature = "unstable")] #[cfg_attr(feature = "docs", doc(cfg(unstable)))] -#[cfg(any(feature = "unstable", feature = "docs"))] pub trait IntoStream { /// The type of the elements being iterated over. type Item; diff --git a/src/stream/mod.rs b/src/stream/mod.rs index 8aa12a2f..d8b96ec2 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -1,53 +1,343 @@ -//! Asynchronous iteration. +//! Composable asynchronous iteration. //! //! This module is an async version of [`std::iter`]. //! -//! [`std::iter`]: https://doc.rust-lang.org/std/iter/index.html +//! If you've found yourself with an asynchronous collection of some kind, +//! and needed to perform an operation on the elements of said collection, +//! you'll quickly run into 'streams'. Streams are heavily used in idiomatic +//! asynchronous Rust code, so it's worth becoming familiar with them. +//! +//! Before explaining more, let's talk about how this module is structured: +//! +//! # Organization +//! +//! This module is largely organized by type: +//! +//! * [Traits] are the core portion: these traits define what kind of streams +//! exist and what you can do with them. The methods of these traits are worth +//! putting some extra study time into. +//! * [Functions] provide some helpful ways to create some basic streams. +//! * [Structs] are often the return types of the various methods on this +//! module's traits. You'll usually want to look at the method that creates +//! the `struct`, rather than the `struct` itself. For more detail about why, +//! see '[Implementing Stream](#implementing-stream)'. +//! +//! [Traits]: #traits +//! [Functions]: #functions +//! [Structs]: #structs +//! +//! That's it! Let's dig into streams. //! -//! # Examples +//! # Stream //! +//! The heart and soul of this module is the [`Stream`] trait. The core of +//! [`Stream`] looks like this: +//! +//! ``` +//! # use async_std::task::{Context, Poll}; +//! # use std::pin::Pin; +//! trait Stream { +//! type Item; +//! fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; +//! } //! ``` -//! # fn main() { async_std::task::block_on(async { +//! +//! A stream has a method, [`next`], which when called, returns an +//! [`Poll`]<[`Option`]`>`. [`next`] will return `Ready(Some(Item))` +//! as long as there are elements, and once they've all been exhausted, will +//! return `None` to indicate that iteration is finished. If we're waiting on +//! something asynchronous to resolve `Pending` is returned. +//! +//! Individual streams may choose to resume iteration, and so calling +//! [`next`] again may or may not eventually start returning `Ready(Some(Item))` +//! again at some point. +//! +//! [`Stream`]'s full definition includes a number of other methods as well, +//! but they are default methods, built on top of [`next`], and so you get +//! them for free. +//! +//! Streams are also composable, and it's common to chain them together to do +//! more complex forms of processing. See the [Adapters](#adapters) section +//! below for more details. +//! +//! [`Poll`]: ../task/enum.Poll.html +//! [`Stream`]: trait.Stream.html +//! [`next`]: trait.Stream.html#tymethod.next +//! [`Option`]: ../../std/option/enum.Option.html +//! +//! # The three forms of streaming +//! +//! There are three common methods which can create streams from a collection: +//! +//! * `stream()`, which iterates over `&T`. +//! * `stream_mut()`, which iterates over `&mut T`. +//! * `into_stream()`, which iterates over `T`. +//! +//! Various things in async-std may implement one or more of the +//! three, where appropriate. +//! +//! # Implementing Stream +//! +//! Creating a stream of your own involves two steps: creating a `struct` to +//! hold the stream's state, and then `impl`ementing [`Stream`] for that +//! `struct`. This is why there are so many `struct`s in this module: there is +//! one for each stream and iterator adapter. +//! +//! Let's make a stream named `Counter` which counts from `1` to `5`: +//! +//! ``` +//! # use async_std::prelude::*; +//! # use async_std::task::{Context, Poll}; +//! # use std::pin::Pin; +//! // First, the struct: +//! +//! /// A stream which counts from one to five +//! struct Counter { +//! count: usize, +//! } +//! +//! // we want our count to start at one, so let's add a new() method to help. +//! // This isn't strictly necessary, but is convenient. Note that we start +//! // `count` at zero, we'll see why in `next()`'s implementation below. +//! impl Counter { +//! fn new() -> Counter { +//! Counter { count: 0 } +//! } +//! } +//! +//! // Then, we implement `Stream` for our `Counter`: +//! +//! impl Stream for Counter { +//! // we will be counting with usize +//! type Item = usize; +//! +//! // poll_next() is the only required method +//! fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { +//! // Increment our count. This is why we started at zero. +//! self.count += 1; +//! +//! // Check to see if we've finished counting or not. +//! if self.count < 6 { +//! Poll::Ready(Some(self.count)) +//! } else { +//! Poll::Ready(None) +//! } +//! } +//! } +//! +//! // And now we can use it! +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! let mut counter = Counter::new(); +//! +//! let x = counter.next().await.unwrap(); +//! println!("{}", x); +//! +//! let x = counter.next().await.unwrap(); +//! println!("{}", x); +//! +//! let x = counter.next().await.unwrap(); +//! println!("{}", x); +//! +//! let x = counter.next().await.unwrap(); +//! println!("{}", x); +//! +//! let x = counter.next().await.unwrap(); +//! println!("{}", x); //! # -//! use async_std::prelude::*; -//! use async_std::stream; +//! # Ok(()) }) } +//! ``` +//! +//! This will print `1` through `5`, each on their own line. +//! +//! Calling `next().await` this way gets repetitive. Rust has a construct which +//! can call `next()` on your stream, until it reaches `None`. Let's go over +//! that next. //! -//! let mut s = stream::repeat(9).take(3); +//! # while let Loops and IntoStream //! -//! while let Some(v) = s.next().await { -//! assert_eq!(v, 9); +//! Rust's `while let` loop syntax is an idiomatic way to iterate over streams. Here's a basic +//! example of `while let`: +//! +//! ``` +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! # use async_std::prelude::*; +//! # use async_std::stream; +//! let mut values = stream::repeat(1u8).take(5); +//! +//! while let Some(x) = values.next().await { +//! println!("{}", x); //! } //! # -//! # }) } +//! # Ok(()) }) } //! ``` - -use cfg_if::cfg_if; +//! +//! This will print the numbers one through five, each on their own line. But +//! you'll notice something here: we never called anything on our vector to +//! produce a stream. What gives? +//! +//! There's a trait in the standard library for converting something into an +//! stream: [`IntoStream`]. This trait has one method, [`into_stream`], +//! which converts the thing implementing [`IntoStream`] into a stream. +//! +//! Unlike `std::iter::IntoIterator`, `IntoStream` does not have compiler +//! support yet. This means that automatic conversions like with `for` loops +//! doesn't occur yet, and `into_stream` will always have to be called manually. +//! +//! [`IntoStream`]: trait.IntoStream.html +//! [`into_stream`]: trait.IntoStream.html#tymethod.into_stream +//! +//! # Adapters +//! +//! Functions which take an [`Stream`] and return another [`Stream`] are +//! often called 'stream adapters', as they are a form of the 'adapter +//! pattern'. +//! +//! Common stream adapters include [`map`], [`take`], and [`filter`]. +//! For more, see their documentation. +//! +//! [`map`]: trait.Stream.html#method.map +//! [`take`]: trait.Stream.html#method.take +//! [`filter`]: trait.Stream.html#method.filter +//! +//! # Laziness +//! +//! Streams (and stream [adapters](#adapters)) are *lazy*. This means that +//! just creating a stream doesn't _do_ a whole lot. Nothing really happens +//! until you call [`next`]. This is sometimes a source of confusion when +//! creating a stream solely for its side effects. For example, the [`map`] +//! method calls a closure on each element it iterates over: +//! +//! ``` +//! # #![allow(unused_must_use)] +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! # use async_std::prelude::*; +//! # use async_std::stream; +//! let v = stream::repeat(1u8).take(5); +//! v.map(|x| println!("{}", x)); +//! # +//! # Ok(()) }) } +//! ``` +//! +//! This will not print any values, as we only created a stream, rather than +//! using it. The compiler will warn us about this kind of behavior: +//! +//! ```text +//! warning: unused result that must be used: streams are lazy and +//! do nothing unless consumed +//! ``` +//! +//! The idiomatic way to write a [`map`] for its side effects is to use a +//! `while let` loop instead: +//! +//! ``` +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! # use async_std::prelude::*; +//! # use async_std::stream; +//! let mut v = stream::repeat(1u8).take(5); +//! +//! while let Some(x) = &v.next().await { +//! println!("{}", x); +//! } +//! # +//! # Ok(()) }) } +//! ``` +//! +//! [`map`]: trait.Stream.html#method.map +//! +//! The two most common ways to evaluate a stream are to use a `while let` loop +//! like this, or using the [`collect`] method to produce a new collection. +//! +//! [`collect`]: trait.Stream.html#method.collect +//! +//! # Infinity +//! +//! Streams do not have to be finite. As an example, an repeat stream is +//! an infinite stream: +//! +//! ``` +//! # use async_std::stream; +//! let numbers = stream::repeat(1u8); +//! ``` +//! +//! It is common to use the [`take`] stream adapter to turn an infinite +//! stream into a finite one: +//! +//! ``` +//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +//! # +//! # use async_std::prelude::*; +//! # use async_std::stream; +//! let numbers = stream::repeat(1u8); +//! let mut five_numbers = numbers.take(5); +//! +//! while let Some(number) = five_numbers.next().await { +//! println!("{}", number); +//! } +//! # +//! # Ok(()) }) } +//! ``` +//! +//! This will print the numbers `0` through `4`, each on their own line. +//! +//! Bear in mind that methods on infinite streams, even those for which a +//! result can be determined mathematically in finite time, may not terminate. +//! Specifically, methods such as [`min`], which in the general case require +//! traversing every element in the stream, are likely not to return +//! successfully for any infinite streams. +//! +//! ```ignore +//! let ones = async_std::stream::repeat(1); +//! let least = ones.min().await.unwrap(); // Oh no! An infinite loop! +//! // `ones.min()` causes an infinite loop, so we won't reach this point! +//! println!("The smallest number one is {}.", least); +//! ``` +//! +//! [`std::iter`]: https://doc.rust-lang.org/std/iter/index.html +//! [`take`]: trait.Stream.html#method.take +//! [`min`]: trait.Stream.html#method.min pub use empty::{empty, Empty}; +pub use from_fn::{from_fn, FromFn}; +pub use from_iter::{from_iter, FromIter}; pub use once::{once, Once}; pub use repeat::{repeat, Repeat}; -pub use stream::{Chain, Filter, Fuse, Inspect, Scan, Skip, SkipWhile, StepBy, Stream, Take, Zip}; +pub use repeat_with::{repeat_with, RepeatWith}; +pub use stream::*; pub(crate) mod stream; mod empty; +mod from_fn; +mod from_iter; mod once; mod repeat; +mod repeat_with; -cfg_if! { - if #[cfg(any(feature = "unstable", feature = "docs"))] { - mod double_ended_stream; - mod extend; - mod from_stream; - mod into_stream; - - pub use double_ended_stream::DoubleEndedStream; - pub use extend::Extend; - pub use from_stream::FromStream; - pub use into_stream::IntoStream; +cfg_unstable! { + mod double_ended_stream; + mod exact_size_stream; + mod extend; + mod from_stream; + mod fused_stream; + mod interval; + mod into_stream; + mod product; + mod successors; + mod sum; - #[cfg_attr(feature = "docs", doc(cfg(unstable)))] - #[doc(inline)] - pub use async_macros::{join_stream as join, JoinStream as Join}; - } + pub use double_ended_stream::DoubleEndedStream; + pub use exact_size_stream::ExactSizeStream; + pub use extend::{extend, Extend}; + pub use from_stream::FromStream; + pub use fused_stream::FusedStream; + pub use interval::{interval, Interval}; + pub use into_stream::IntoStream; + pub use product::Product; + pub use stream::Merge; + pub use successors::{successors, Successors}; + pub use sum::Sum; } diff --git a/src/stream/once.rs b/src/stream/once.rs index 133a155c..e4ac682c 100644 --- a/src/stream/once.rs +++ b/src/stream/once.rs @@ -1,5 +1,7 @@ use std::pin::Pin; +use pin_project_lite::pin_project; + use crate::stream::Stream; use crate::task::{Context, Poll}; @@ -8,7 +10,7 @@ use crate::task::{Context, Poll}; /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # /// use async_std::prelude::*; /// use async_std::stream; @@ -18,26 +20,29 @@ use crate::task::{Context, Poll}; /// assert_eq!(s.next().await, Some(7)); /// assert_eq!(s.next().await, None); /// # -/// # }) } +/// # }) /// ``` pub fn once(t: T) -> Once { Once { value: Some(t) } } -/// A stream that yields a single item. -/// -/// This stream is constructed by the [`once`] function. -/// -/// [`once`]: fn.once.html -#[derive(Debug)] -pub struct Once { - value: Option, +pin_project! { + /// A stream that yields a single item. + /// + /// This stream is created by the [`once`] function. See its + /// documentation for more. + /// + /// [`once`]: fn.once.html + #[derive(Clone, Debug)] + pub struct Once { + value: Option, + } } -impl Stream for Once { +impl Stream for Once { type Item = T; - fn poll_next(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - Poll::Ready(self.value.take()) + fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(self.project().value.take()) } } diff --git a/src/stream/product.rs b/src/stream/product.rs new file mode 100644 index 00000000..2f5bf4c3 --- /dev/null +++ b/src/stream/product.rs @@ -0,0 +1,79 @@ +use std::pin::Pin; +use std::future::Future; + +use crate::stream::Stream; + +/// Trait to represent types that can be created by multiplying the elements of a stream. +/// +/// This trait is used to implement the [`product`] method on streams. Types which +/// implement the trait can be generated by the [`product`] method. Like +/// [`FromStream`] this trait should rarely be called directly and instead +/// interacted with through [`Stream::product`]. +/// +/// [`product`]: trait.Product.html#tymethod.product +/// [`FromStream`]: trait.FromStream.html +/// [`Stream::product`]: trait.Stream.html#method.product +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +pub trait Product: Sized { + /// Method which takes a stream and generates `Self` from the elements by + /// multiplying the items. + fn product<'a, S>(stream: S) -> Pin + 'a>> + where + S: Stream + 'a; +} + +use core::ops::Mul; +use core::num::Wrapping; +use crate::stream::stream::StreamExt; + +macro_rules! integer_product { + (@impls $one: expr, $($a:ty)*) => ($( + impl Product for $a { + fn product<'a, S>(stream: S) -> Pin+ 'a>> + where + S: Stream + 'a, + { + Box::pin(async move { stream.fold($one, Mul::mul).await } ) + } + } + impl<'a> Product<&'a $a> for $a { + fn product<'b, S>(stream: S) -> Pin + 'b>> + where + S: Stream + 'b, + { + Box::pin(async move { stream.fold($one, Mul::mul).await } ) + } + } + )*); + ($($a:ty)*) => ( + integer_product!(@impls 1, $($a)*); + integer_product!(@impls Wrapping(1), $(Wrapping<$a>)*); + ); +} + +macro_rules! float_product { + ($($a:ty)*) => ($( + impl Product for $a { + fn product<'a, S>(stream: S) -> Pin+ 'a>> + where S: Stream + 'a, + { + Box::pin(async move { stream.fold(1.0, |a, b| a * b).await } ) + } + } + impl<'a> Product<&'a $a> for $a { + fn product<'b, S>(stream: S) -> Pin+ 'b>> + where S: Stream + 'b, + { + Box::pin(async move { stream.fold(1.0, |a, b| a * b).await } ) + } + } + )*); + ($($a:ty)*) => ( + float_product!($($a)*); + float_product!($(Wrapping<$a>)*); + ); +} + +integer_product!{ i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize } +float_product!{ f32 f64 } diff --git a/src/stream/repeat.rs b/src/stream/repeat.rs index 1a6da411..f3dfdbd8 100644 --- a/src/stream/repeat.rs +++ b/src/stream/repeat.rs @@ -8,7 +8,7 @@ use crate::task::{Context, Poll}; /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # /// use async_std::prelude::*; /// use async_std::stream; @@ -18,7 +18,7 @@ use crate::task::{Context, Poll}; /// assert_eq!(s.next().await, Some(7)); /// assert_eq!(s.next().await, Some(7)); /// # -/// # }) } +/// # }) /// ``` pub fn repeat(item: T) -> Repeat where @@ -29,10 +29,11 @@ where /// A stream that yields the same item repeatedly. /// -/// This stream is constructed by the [`repeat`] function. +/// This stream is created by the [`repeat`] function. See its +/// documentation for more. /// /// [`repeat`]: fn.repeat.html -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Repeat { item: T, } diff --git a/src/stream/repeat_with.rs b/src/stream/repeat_with.rs new file mode 100644 index 00000000..e183a77c --- /dev/null +++ b/src/stream/repeat_with.rs @@ -0,0 +1,84 @@ +use std::pin::Pin; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +/// A stream that repeats elements of type `T` endlessly by applying a provided closure. +/// +/// This stream is created by the [`repeat_with`] function. See its +/// documentation for more. +/// +/// [`repeat_with`]: fn.repeat_with.html +#[derive(Clone, Debug)] +pub struct RepeatWith { + f: F, +} + +impl Unpin for RepeatWith {} + +/// Creates a new stream that repeats elements of type `A` endlessly by applying the provided closure. +/// +/// # Examples +/// +/// Basic usage: +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::prelude::*; +/// use async_std::stream; +/// +/// let s = stream::repeat_with(|| 1); +/// +/// pin_utils::pin_mut!(s); +/// +/// assert_eq!(s.next().await, Some(1)); +/// assert_eq!(s.next().await, Some(1)); +/// assert_eq!(s.next().await, Some(1)); +/// assert_eq!(s.next().await, Some(1)); +/// # }) +/// ``` +/// +/// Going finite: +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::prelude::*; +/// use async_std::stream; +/// +/// let mut n = 1; +/// let s = stream::repeat_with(|| { +/// let item = n; +/// n *= 2; +/// item +/// }) +/// .take(4); +/// +/// pin_utils::pin_mut!(s); +/// +/// assert_eq!(s.next().await, Some(1)); +/// assert_eq!(s.next().await, Some(2)); +/// assert_eq!(s.next().await, Some(4)); +/// assert_eq!(s.next().await, Some(8)); +/// assert_eq!(s.next().await, None); +/// # }) +/// ``` +pub fn repeat_with(repeater: F) -> RepeatWith +where + F: FnMut() -> T, +{ + RepeatWith { f: repeater } +} + +impl Stream for RepeatWith +where + F: FnMut() -> T, +{ + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + let item = (&mut self.f)(); + Poll::Ready(Some(item)) + } +} diff --git a/src/stream/stream/all.rs b/src/stream/stream/all.rs index 3b65fc76..d2ac5cac 100644 --- a/src/stream/stream/all.rs +++ b/src/stream/stream/all.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use std::pin::Pin; +use std::future::Future; -use crate::future::Future; use crate::stream::Stream; use crate::task::{Context, Poll}; @@ -10,10 +10,19 @@ use crate::task::{Context, Poll}; pub struct AllFuture<'a, S, F, T> { pub(crate) stream: &'a mut S, pub(crate) f: F, - pub(crate) result: bool, pub(crate) _marker: PhantomData, } +impl<'a, S, F, T> AllFuture<'a, S, F, T> { + pub(crate) fn new(stream: &'a mut S, f: F) -> Self { + Self { + stream, + f, + _marker: PhantomData, + } + } +} + impl Unpin for AllFuture<'_, S, F, T> {} impl Future for AllFuture<'_, S, F, S::Item> @@ -29,7 +38,6 @@ where match next { Some(v) => { let result = (&mut self.f)(v); - self.result = result; if result { // don't forget to wake this task again to pull the next item from stream @@ -39,7 +47,7 @@ where Poll::Ready(false) } } - None => Poll::Ready(self.result), + None => Poll::Ready(true), } } } diff --git a/src/stream/stream/any.rs b/src/stream/stream/any.rs index a23adf4b..34168e67 100644 --- a/src/stream/stream/any.rs +++ b/src/stream/stream/any.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use std::pin::Pin; +use std::future::Future; -use crate::future::Future; use crate::stream::Stream; use crate::task::{Context, Poll}; @@ -10,10 +10,19 @@ use crate::task::{Context, Poll}; pub struct AnyFuture<'a, S, F, T> { pub(crate) stream: &'a mut S, pub(crate) f: F, - pub(crate) result: bool, pub(crate) _marker: PhantomData, } +impl<'a, S, F, T> AnyFuture<'a, S, F, T> { + pub(crate) fn new(stream: &'a mut S, f: F) -> Self { + Self { + stream, + f, + _marker: PhantomData, + } + } +} + impl Unpin for AnyFuture<'_, S, F, T> {} impl Future for AnyFuture<'_, S, F, S::Item> @@ -29,7 +38,6 @@ where match next { Some(v) => { let result = (&mut self.f)(v); - self.result = result; if result { Poll::Ready(true) @@ -39,7 +47,7 @@ where Poll::Pending } } - None => Poll::Ready(self.result), + None => Poll::Ready(false), } } } diff --git a/src/stream/stream/chain.rs b/src/stream/stream/chain.rs index 2693382e..909fc19b 100644 --- a/src/stream/stream/chain.rs +++ b/src/stream/stream/chain.rs @@ -1,22 +1,31 @@ use std::pin::Pin; +use pin_project_lite::pin_project; + use super::fuse::Fuse; use crate::prelude::*; use crate::task::{Context, Poll}; -/// Chains two streams one after another. -#[derive(Debug)] -pub struct Chain { - first: Fuse, - second: Fuse, +pin_project! { + /// A stream that chains two streams one after another. + /// + /// This `struct` is created by the [`chain`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`chain`]: trait.Stream.html#method.chain + /// [`Stream`]: trait.Stream.html + #[derive(Debug)] + pub struct Chain { + #[pin] + first: Fuse, + #[pin] + second: Fuse, + } } impl Chain { - pin_utils::unsafe_pinned!(first: Fuse); - pin_utils::unsafe_pinned!(second: Fuse); - pub(super) fn new(first: S, second: U) -> Self { - Chain { + Self { first: first.fuse(), second: second.fuse(), } @@ -26,22 +35,23 @@ impl Chain { impl> Stream for Chain { type Item = S::Item; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if !self.first.done { - let next = futures_core::ready!(self.as_mut().first().poll_next(cx)); + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + if !this.first.done { + let next = futures_core::ready!(this.first.as_mut().poll_next(cx)); if let Some(next) = next { return Poll::Ready(Some(next)); } } - if !self.second.done { - let next = futures_core::ready!(self.as_mut().second().poll_next(cx)); + if !this.second.done { + let next = futures_core::ready!(this.second.as_mut().poll_next(cx)); if let Some(next) = next { return Poll::Ready(Some(next)); } } - if self.first.done && self.second.done { + if this.first.done && this.second.done { return Poll::Ready(None); } diff --git a/src/stream/stream/cloned.rs b/src/stream/stream/cloned.rs new file mode 100644 index 00000000..4c77c5c9 --- /dev/null +++ b/src/stream/stream/cloned.rs @@ -0,0 +1,33 @@ +use crate::stream::Stream; +use crate::task::{Context, Poll}; +use pin_project_lite::pin_project; +use std::pin::Pin; + +pin_project! { + /// A stream that clones the elements of an underlying stream. + #[derive(Debug)] + pub struct Cloned { + #[pin] + stream: S, + } +} + +impl Cloned { + pub(super) fn new(stream: S) -> Self { + Self { stream } + } +} + +impl<'a, S, T: 'a> Stream for Cloned +where + S: Stream, + T: Clone, +{ + type Item = T; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); + Poll::Ready(next.cloned()) + } +} diff --git a/src/stream/stream/cmp.rs b/src/stream/stream/cmp.rs new file mode 100644 index 00000000..2be0c1a3 --- /dev/null +++ b/src/stream/stream/cmp.rs @@ -0,0 +1,93 @@ +use std::cmp::Ordering; +use std::pin::Pin; +use std::future::Future; + +use pin_project_lite::pin_project; + +use super::fuse::Fuse; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + // Lexicographically compares the elements of this `Stream` with those + // of another using `Ord`. + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct CmpFuture { + #[pin] + l: Fuse, + #[pin] + r: Fuse, + l_cache: Option, + r_cache: Option, + } +} + +impl CmpFuture { + pub(super) fn new(l: L, r: R) -> Self { + Self { + l: l.fuse(), + r: r.fuse(), + l_cache: None, + r_cache: None, + } + } +} + +impl Future for CmpFuture +where + L: Stream + Sized, + R: Stream + Sized, + L::Item: Ord, +{ + type Output = Ordering; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + loop { + // Stream that completes earliest can be considered Less, etc + let l_complete = this.l.done && this.l_cache.is_none(); + let r_complete = this.r.done && this.r_cache.is_none(); + + if l_complete && r_complete { + return Poll::Ready(Ordering::Equal); + } else if l_complete { + return Poll::Ready(Ordering::Less); + } else if r_complete { + return Poll::Ready(Ordering::Greater); + } + + // Get next value if possible and necesary + if !this.l.done && this.l_cache.is_none() { + let l_next = futures_core::ready!(this.l.as_mut().poll_next(cx)); + if let Some(item) = l_next { + *this.l_cache = Some(item); + } + } + + if !this.r.done && this.r_cache.is_none() { + let r_next = futures_core::ready!(this.r.as_mut().poll_next(cx)); + if let Some(item) = r_next { + *this.r_cache = Some(item); + } + } + + // Compare if both values are available. + if this.l_cache.is_some() && this.r_cache.is_some() { + let l_value = this.l_cache.take().unwrap(); + let r_value = this.r_cache.take().unwrap(); + let result = l_value.cmp(&r_value); + + if let Ordering::Equal = result { + // Reset cache to prepare for next comparison + *this.l_cache = None; + *this.r_cache = None; + } else { + // Return non equal value + return Poll::Ready(result); + } + } + } + } +} diff --git a/src/stream/stream/copied.rs b/src/stream/stream/copied.rs new file mode 100644 index 00000000..651c31b6 --- /dev/null +++ b/src/stream/stream/copied.rs @@ -0,0 +1,33 @@ +use crate::stream::Stream; +use crate::task::{Context, Poll}; +use pin_project_lite::pin_project; +use std::pin::Pin; + +pin_project! { + /// A stream that copies the elements of an underlying stream. + #[derive(Debug)] + pub struct Copied { + #[pin] + stream: S, + } +} + +impl Copied { + pub(super) fn new(stream: S) -> Self { + Self { stream } + } +} + +impl<'a, S, T: 'a> Stream for Copied +where + S: Stream, + T: Copy, +{ + type Item = T; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); + Poll::Ready(next.copied()) + } +} diff --git a/src/stream/stream/count.rs b/src/stream/stream/count.rs new file mode 100644 index 00000000..ebf2a2f1 --- /dev/null +++ b/src/stream/stream/count.rs @@ -0,0 +1,46 @@ +use std::future::Future; +use std::pin::Pin; + +use pin_project_lite::pin_project; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + #[doc(hidden)] + #[allow(missing_debug_implementations)] + #[cfg(all(feature = "default", feature = "unstable"))] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + pub struct CountFuture { + #[pin] + stream: S, + count: usize, + } +} + +impl CountFuture { + pub(crate) fn new(stream: S) -> Self { + Self { stream, count: 0 } + } +} + +impl Future for CountFuture +where + S: Stream, +{ + type Output = usize; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); + + match next { + Some(_) => { + cx.waker().wake_by_ref(); + *this.count += 1; + Poll::Pending + } + None => Poll::Ready(*this.count), + } + } +} diff --git a/src/stream/stream/cycle.rs b/src/stream/stream/cycle.rs new file mode 100644 index 00000000..5f8eaa20 --- /dev/null +++ b/src/stream/stream/cycle.rs @@ -0,0 +1,54 @@ +use std::mem::ManuallyDrop; +use std::pin::Pin; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +/// A stream that will repeatedly yield the same list of elements. +#[derive(Debug)] +pub struct Cycle { + orig: S, + source: ManuallyDrop, +} + +impl Cycle +where + S: Stream + Clone, +{ + pub(crate) fn new(source: S) -> Self { + Self { + orig: source.clone(), + source: ManuallyDrop::new(source), + } + } +} + +impl Drop for Cycle { + fn drop(&mut self) { + unsafe { + ManuallyDrop::drop(&mut self.source); + } + } +} + +impl Stream for Cycle +where + S: Stream + Clone, +{ + type Item = S::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + unsafe { + let this = self.get_unchecked_mut(); + + match futures_core::ready!(Pin::new_unchecked(&mut *this.source).poll_next(cx)) { + Some(item) => Poll::Ready(Some(item)), + None => { + ManuallyDrop::drop(&mut this.source); + this.source = ManuallyDrop::new(this.orig.clone()); + Pin::new_unchecked(&mut *this.source).poll_next(cx) + } + } + } + } +} diff --git a/src/stream/stream/enumerate.rs b/src/stream/stream/enumerate.rs index 7d5a3d68..c4a37d6e 100644 --- a/src/stream/stream/enumerate.rs +++ b/src/stream/stream/enumerate.rs @@ -1,21 +1,22 @@ -use crate::task::{Context, Poll}; use std::pin::Pin; +use pin_project_lite::pin_project; + use crate::stream::Stream; +use crate::task::{Context, Poll}; -#[doc(hidden)] -#[allow(missing_debug_implementations)] -pub struct Enumerate { - stream: S, - i: usize, +pin_project! { + #[derive(Debug)] + pub struct Enumerate { + #[pin] + stream: S, + i: usize, + } } impl Enumerate { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(i: usize); - pub(super) fn new(stream: S) -> Self { - Enumerate { stream, i: 0 } + Self { stream, i: 0 } } } @@ -25,13 +26,14 @@ where { type Item = (usize, S::Item); - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); match next { Some(v) => { - let ret = (self.i, v); - *self.as_mut().i() += 1; + let ret = (*this.i, v); + *this.i += 1; Poll::Ready(Some(ret)) } None => Poll::Ready(None), diff --git a/src/stream/stream/eq.rs b/src/stream/stream/eq.rs new file mode 100644 index 00000000..58ccc90e --- /dev/null +++ b/src/stream/stream/eq.rs @@ -0,0 +1,63 @@ +use std::pin::Pin; +use std::future::Future; + +use pin_project_lite::pin_project; + +use super::fuse::Fuse; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + // Lexicographically compares the elements of this `Stream` with those + // of another. + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct EqFuture { + #[pin] + l: Fuse, + #[pin] + r: Fuse, + } +} + +impl EqFuture +where + L::Item: PartialEq, +{ + pub(super) fn new(l: L, r: R) -> Self { + Self { + l: l.fuse(), + r: r.fuse(), + } + } +} + +impl Future for EqFuture +where + L: Stream + Sized, + R: Stream + Sized, + L::Item: PartialEq, +{ + type Output = bool; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + + loop { + let l_val = futures_core::ready!(this.l.as_mut().poll_next(cx)); + let r_val = futures_core::ready!(this.r.as_mut().poll_next(cx)); + + if this.l.done && this.r.done { + return Poll::Ready(true); + } + + match (l_val, r_val) { + (Some(l), Some(r)) if l != r => { + return Poll::Ready(false); + } + _ => {} + } + } + } +} diff --git a/src/stream/stream/filter.rs b/src/stream/stream/filter.rs index 8ed282ce..00344b0e 100644 --- a/src/stream/stream/filter.rs +++ b/src/stream/stream/filter.rs @@ -1,42 +1,48 @@ -use std::marker::PhantomData; use std::pin::Pin; +use pin_project_lite::pin_project; + use crate::stream::Stream; use crate::task::{Context, Poll}; -/// A stream to filter elements of another stream with a predicate. -#[derive(Debug)] -pub struct Filter { - stream: S, - predicate: P, - __t: PhantomData, +pin_project! { + /// A stream to filter elements of another stream with a predicate. + /// + /// This `struct` is created by the [`filter`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`filter`]: trait.Stream.html#method.filter + /// [`Stream`]: trait.Stream.html + #[derive(Debug)] + pub struct Filter { + #[pin] + stream: S, + predicate: P, + } } -impl Filter { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(predicate: P); - +impl Filter { pub(super) fn new(stream: S, predicate: P) -> Self { - Filter { + Self { stream, predicate, - __t: PhantomData, } } } -impl Stream for Filter +impl Stream for Filter where S: Stream, P: FnMut(&S::Item) -> bool, { type Item = S::Item; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); match next { - Some(v) if (self.as_mut().predicate())(&v) => Poll::Ready(Some(v)), + Some(v) if (this.predicate)(&v) => Poll::Ready(Some(v)), Some(_) => { cx.waker().wake_by_ref(); Poll::Pending diff --git a/src/stream/stream/filter_map.rs b/src/stream/stream/filter_map.rs index 756efff1..3cd1e47a 100644 --- a/src/stream/stream/filter_map.rs +++ b/src/stream/stream/filter_map.rs @@ -1,43 +1,37 @@ -use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; +use pin_project_lite::pin_project; + use crate::stream::Stream; -#[doc(hidden)] -#[allow(missing_debug_implementations)] -pub struct FilterMap { - stream: S, - f: F, - __from: PhantomData, - __to: PhantomData, +pin_project! { + #[derive(Debug)] + pub struct FilterMap { + #[pin] + stream: S, + f: F, + } } -impl FilterMap { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(f: F); - +impl FilterMap { pub(crate) fn new(stream: S, f: F) -> Self { - FilterMap { - stream, - f, - __from: PhantomData, - __to: PhantomData, - } + Self { stream, f } } } -impl Stream for FilterMap +impl Stream for FilterMap where S: Stream, F: FnMut(S::Item) -> Option, { type Item = B; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); match next { - Some(v) => match (self.as_mut().f())(v) { + Some(v) => match (this.f)(v) { Some(b) => Poll::Ready(Some(b)), None => { cx.waker().wake_by_ref(); diff --git a/src/stream/stream/find.rs b/src/stream/stream/find.rs index 93624c03..4a0749b1 100644 --- a/src/stream/stream/find.rs +++ b/src/stream/stream/find.rs @@ -1,31 +1,25 @@ -use std::marker::PhantomData; +use std::future::Future; use std::pin::Pin; -use crate::future::Future; use crate::stream::Stream; use crate::task::{Context, Poll}; #[doc(hidden)] #[allow(missing_debug_implementations)] -pub struct FindFuture<'a, S, P, T> { +pub struct FindFuture<'a, S, P> { stream: &'a mut S, p: P, - __t: PhantomData, } -impl<'a, S, P, T> FindFuture<'a, S, P, T> { +impl<'a, S, P> FindFuture<'a, S, P> { pub(super) fn new(stream: &'a mut S, p: P) -> Self { - FindFuture { - stream, - p, - __t: PhantomData, - } + Self { stream, p } } } -impl Unpin for FindFuture<'_, S, P, T> {} +impl Unpin for FindFuture<'_, S, P> {} -impl<'a, S, P> Future for FindFuture<'a, S, P, S::Item> +impl<'a, S, P> Future for FindFuture<'a, S, P> where S: Stream + Unpin + Sized, P: FnMut(&S::Item) -> bool, diff --git a/src/stream/stream/find_map.rs b/src/stream/stream/find_map.rs index dfcf92d6..c7949439 100644 --- a/src/stream/stream/find_map.rs +++ b/src/stream/stream/find_map.rs @@ -1,33 +1,25 @@ -use std::marker::PhantomData; +use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; -use crate::future::Future; use crate::stream::Stream; #[doc(hidden)] #[allow(missing_debug_implementations)] -pub struct FindMapFuture<'a, S, F, T, B> { +pub struct FindMapFuture<'a, S, F> { stream: &'a mut S, f: F, - __b: PhantomData, - __t: PhantomData, } -impl<'a, S, B, F, T> FindMapFuture<'a, S, F, T, B> { +impl<'a, S, F> FindMapFuture<'a, S, F> { pub(super) fn new(stream: &'a mut S, f: F) -> Self { - FindMapFuture { - stream, - f, - __b: PhantomData, - __t: PhantomData, - } + Self { stream, f } } } -impl Unpin for FindMapFuture<'_, S, F, T, B> {} +impl Unpin for FindMapFuture<'_, S, F> {} -impl<'a, S, B, F> Future for FindMapFuture<'a, S, F, S::Item, B> +impl<'a, S, B, F> Future for FindMapFuture<'a, S, F> where S: Stream + Unpin + Sized, F: FnMut(S::Item) -> Option, diff --git a/src/stream/stream/flat_map.rs b/src/stream/stream/flat_map.rs new file mode 100644 index 00000000..6c828c92 --- /dev/null +++ b/src/stream/stream/flat_map.rs @@ -0,0 +1,65 @@ +use std::pin::Pin; + +use pin_project_lite::pin_project; + +use crate::prelude::*; +use crate::stream::stream::map::Map; +use crate::stream::{IntoStream, Stream}; +use crate::task::{Context, Poll}; + +pin_project! { + /// A stream that maps each element to a stream, and yields the elements of the produced + /// streams. + /// + /// This `struct` is created by the [`flat_map`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`flat_map`]: trait.Stream.html#method.flat_map + /// [`Stream`]: trait.Stream.html + pub struct FlatMap { + #[pin] + stream: Map, + #[pin] + inner_stream: Option, + } +} + +impl FlatMap +where + S: Stream, + U: IntoStream, + F: FnMut(S::Item) -> U, +{ + pub(super) fn new(stream: S, f: F) -> Self { + Self { + stream: stream.map(f), + inner_stream: None, + } + } +} + +impl Stream for FlatMap +where + S: Stream, + S::Item: IntoStream, + U: Stream, + F: FnMut(S::Item) -> U, +{ + type Item = U::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + loop { + if let Some(inner) = this.inner_stream.as_mut().as_pin_mut() { + if let item @ Some(_) = futures_core::ready!(inner.poll_next(cx)) { + return Poll::Ready(item); + } + } + + match futures_core::ready!(this.stream.as_mut().poll_next(cx)) { + None => return Poll::Ready(None), + Some(inner) => this.inner_stream.set(Some(inner.into_stream())), + } + } + } +} diff --git a/src/stream/stream/flatten.rs b/src/stream/stream/flatten.rs new file mode 100644 index 00000000..1d6fcae6 --- /dev/null +++ b/src/stream/stream/flatten.rs @@ -0,0 +1,79 @@ +use std::fmt; +use std::pin::Pin; + +use pin_project_lite::pin_project; + +use crate::stream::{IntoStream, Stream}; +use crate::task::{Context, Poll}; + +pin_project! { + /// A stream that flattens one level of nesting in an stream of things that can be turned into + /// streams. + /// + /// This `struct` is created by the [`flatten`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`flatten`]: trait.Stream.html#method.flatten + /// [`Stream`]: trait.Stream.html + pub struct Flatten + where + S: Stream, + S::Item: IntoStream, + { + #[pin] + stream: S, + #[pin] + inner_stream: Option<::IntoStream>, + } +} + +impl Flatten +where + S: Stream, + S::Item: IntoStream, +{ + pub(super) fn new(stream: S) -> Self { + Self { + stream, + inner_stream: None, + } + } +} + +impl Stream for Flatten +where + S: Stream, + S::Item: IntoStream, + U: Stream, +{ + type Item = U::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + loop { + if let Some(inner) = this.inner_stream.as_mut().as_pin_mut() { + if let item @ Some(_) = futures_core::ready!(inner.poll_next(cx)) { + return Poll::Ready(item); + } + } + + match futures_core::ready!(this.stream.as_mut().poll_next(cx)) { + None => return Poll::Ready(None), + Some(inner) => this.inner_stream.set(Some(inner.into_stream())), + } + } + } +} + +impl fmt::Debug for Flatten +where + S: fmt::Debug + Stream, + S::Item: IntoStream, + U: fmt::Debug + Stream, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Flatten") + .field("inner", &self.stream) + .finish() + } +} diff --git a/src/stream/stream/fold.rs b/src/stream/stream/fold.rs index 18ddcd81..a346eb67 100644 --- a/src/stream/stream/fold.rs +++ b/src/stream/stream/fold.rs @@ -1,52 +1,50 @@ -use std::marker::PhantomData; +use std::future::Future; use std::pin::Pin; -use crate::future::Future; +use pin_project_lite::pin_project; + use crate::stream::Stream; use crate::task::{Context, Poll}; -#[doc(hidden)] -#[allow(missing_debug_implementations)] -pub struct FoldFuture { - stream: S, - f: F, - acc: Option, - __t: PhantomData, +pin_project! { + #[derive(Debug)] + pub struct FoldFuture { + #[pin] + stream: S, + f: F, + acc: Option, + } } -impl FoldFuture { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(f: F); - pin_utils::unsafe_unpinned!(acc: Option); - +impl FoldFuture { pub(super) fn new(stream: S, init: B, f: F) -> Self { - FoldFuture { + Self { stream, f, acc: Some(init), - __t: PhantomData, } } } -impl Future for FoldFuture +impl Future for FoldFuture where S: Stream + Sized, F: FnMut(B, S::Item) -> B, { type Output = B; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); loop { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + let next = futures_core::ready!(this.stream.as_mut().poll_next(cx)); match next { Some(v) => { - let old = self.as_mut().acc().take().unwrap(); - let new = (self.as_mut().f())(old, v); - *self.as_mut().acc() = Some(new); + let old = this.acc.take().unwrap(); + let new = (this.f)(old, v); + *this.acc = Some(new); } - None => return Poll::Ready(self.as_mut().acc().take().unwrap()), + None => return Poll::Ready(this.acc.take().unwrap()), } } } diff --git a/src/stream/stream/for_each.rs b/src/stream/stream/for_each.rs index 0406a507..dce5cdae 100644 --- a/src/stream/stream/for_each.rs +++ b/src/stream/stream/for_each.rs @@ -1,44 +1,44 @@ -use std::marker::PhantomData; use std::pin::Pin; +use std::future::Future; + +use pin_project_lite::pin_project; -use crate::future::Future; use crate::stream::Stream; use crate::task::{Context, Poll}; -#[doc(hidden)] -#[allow(missing_debug_implementations)] -pub struct ForEachFuture { - stream: S, - f: F, - __t: PhantomData, +pin_project! { + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct ForEachFuture { + #[pin] + stream: S, + f: F, + } } -impl ForEachFuture { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(f: F); - +impl ForEachFuture { pub(super) fn new(stream: S, f: F) -> Self { - ForEachFuture { + Self { stream, f, - __t: PhantomData, } } } -impl Future for ForEachFuture +impl Future for ForEachFuture where S: Stream + Sized, F: FnMut(S::Item), { type Output = (); - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); loop { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + let next = futures_core::ready!(this.stream.as_mut().poll_next(cx)); match next { - Some(v) => (self.as_mut().f())(v), + Some(v) => (this.f)(v), None => return Poll::Ready(()), } } diff --git a/src/stream/stream/fuse.rs b/src/stream/stream/fuse.rs index ff5bdab1..c7449c27 100644 --- a/src/stream/stream/fuse.rs +++ b/src/stream/stream/fuse.rs @@ -1,33 +1,46 @@ use std::pin::Pin; +use pin_project_lite::pin_project; + use crate::stream::Stream; use crate::task::{Context, Poll}; -/// A `Stream` that is permanently closed once a single call to `poll` results in -/// `Poll::Ready(None)`, returning `Poll::Ready(None)` for all future calls to `poll`. -#[derive(Clone, Debug)] -pub struct Fuse { - pub(crate) stream: S, - pub(crate) done: bool, +pin_project! { + /// A stream that yields `None` forever after the underlying stream yields `None` once. + /// + /// This `struct` is created by the [`fuse`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`fuse`]: trait.Stream.html#method.fuse + /// [`Stream`]: trait.Stream.html + #[derive(Clone, Debug)] + pub struct Fuse { + #[pin] + pub(crate) stream: S, + pub(crate) done: bool, + } } -impl Unpin for Fuse {} - -impl Fuse { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(done: bool); +impl Fuse { + pub(super) fn new(stream: S) -> Self { + Self { + stream, + done: false, + } + } } impl Stream for Fuse { type Item = S::Item; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if self.done { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + if *this.done { Poll::Ready(None) } else { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + let next = futures_core::ready!(this.stream.poll_next(cx)); if next.is_none() { - *self.as_mut().done() = true; + *this.done = true; } Poll::Ready(next) } diff --git a/src/stream/stream/ge.rs b/src/stream/stream/ge.rs new file mode 100644 index 00000000..67b20bed --- /dev/null +++ b/src/stream/stream/ge.rs @@ -0,0 +1,50 @@ +use std::cmp::Ordering; +use std::pin::Pin; +use std::future::Future; + +use pin_project_lite::pin_project; + +use super::partial_cmp::PartialCmpFuture; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + // Determines if the elements of this `Stream` are lexicographically + // greater than or equal to those of another. + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct GeFuture { + #[pin] + partial_cmp: PartialCmpFuture, + } +} + +impl GeFuture +where + L::Item: PartialOrd, +{ + pub(super) fn new(l: L, r: R) -> Self { + Self { + partial_cmp: l.partial_cmp(r), + } + } +} + +impl Future for GeFuture +where + L: Stream, + R: Stream, + L::Item: PartialOrd, +{ + type Output = bool; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let result = futures_core::ready!(self.project().partial_cmp.poll(cx)); + + match result { + Some(Ordering::Greater) | Some(Ordering::Equal) => Poll::Ready(true), + _ => Poll::Ready(false), + } + } +} diff --git a/src/stream/stream/gt.rs b/src/stream/stream/gt.rs new file mode 100644 index 00000000..1c121891 --- /dev/null +++ b/src/stream/stream/gt.rs @@ -0,0 +1,50 @@ +use std::cmp::Ordering; +use std::pin::Pin; +use std::future::Future; + +use pin_project_lite::pin_project; + +use super::partial_cmp::PartialCmpFuture; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + // Determines if the elements of this `Stream` are lexicographically + // greater than those of another. + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct GtFuture { + #[pin] + partial_cmp: PartialCmpFuture, + } +} + +impl GtFuture +where + L::Item: PartialOrd, +{ + pub(super) fn new(l: L, r: R) -> Self { + Self { + partial_cmp: l.partial_cmp(r), + } + } +} + +impl Future for GtFuture +where + L: Stream + Sized, + R: Stream + Sized, + L::Item: PartialOrd, +{ + type Output = bool; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let result = futures_core::ready!(self.project().partial_cmp.poll(cx)); + + match result { + Some(Ordering::Greater) => Poll::Ready(true), + _ => Poll::Ready(false), + } + } +} diff --git a/src/stream/stream/inspect.rs b/src/stream/stream/inspect.rs index e63b5849..bb39662b 100644 --- a/src/stream/stream/inspect.rs +++ b/src/stream/stream/inspect.rs @@ -1,42 +1,48 @@ -use std::marker::PhantomData; use std::pin::Pin; +use pin_project_lite::pin_project; + use crate::stream::Stream; use crate::task::{Context, Poll}; -/// A stream that does something with each element of another stream. -#[derive(Debug)] -pub struct Inspect { - stream: S, - f: F, - __t: PhantomData, +pin_project! { + /// A stream that does something with each element of another stream. + /// + /// This `struct` is created by the [`inspect`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`inspect`]: trait.Stream.html#method.inspect + /// [`Stream`]: trait.Stream.html + #[derive(Debug)] + pub struct Inspect { + #[pin] + stream: S, + f: F, + } } -impl Inspect { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(f: F); - +impl Inspect { pub(super) fn new(stream: S, f: F) -> Self { - Inspect { + Self { stream, f, - __t: PhantomData, } } } -impl Stream for Inspect +impl Stream for Inspect where S: Stream, F: FnMut(&S::Item), { type Item = S::Item; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + let next = futures_core::ready!(this.stream.as_mut().poll_next(cx)); Poll::Ready(next.and_then(|x| { - (self.as_mut().f())(&x); + (this.f)(&x); Some(x) })) } diff --git a/src/stream/stream/last.rs b/src/stream/stream/last.rs new file mode 100644 index 00000000..d037efcb --- /dev/null +++ b/src/stream/stream/last.rs @@ -0,0 +1,45 @@ +use std::future::Future; +use std::pin::Pin; + +use pin_project_lite::pin_project; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct LastFuture { + #[pin] + stream: S, + last: Option, + } +} + +impl LastFuture { + pub(crate) fn new(stream: S) -> Self { + Self { stream, last: None } + } +} + +impl Future for LastFuture +where + S: Stream + Unpin + Sized, + S::Item: Copy, +{ + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); + + match next { + Some(new) => { + cx.waker().wake_by_ref(); + *this.last = Some(new); + Poll::Pending + } + None => Poll::Ready(*this.last), + } + } +} diff --git a/src/stream/stream/le.rs b/src/stream/stream/le.rs new file mode 100644 index 00000000..7b86161c --- /dev/null +++ b/src/stream/stream/le.rs @@ -0,0 +1,50 @@ +use std::cmp::Ordering; +use std::pin::Pin; +use std::future::Future; + +use pin_project_lite::pin_project; + +use super::partial_cmp::PartialCmpFuture; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + /// Determines if the elements of this `Stream` are lexicographically + /// less or equal to those of another. + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct LeFuture { + #[pin] + partial_cmp: PartialCmpFuture, + } +} + +impl LeFuture +where + L::Item: PartialOrd, +{ + pub(super) fn new(l: L, r: R) -> Self { + Self { + partial_cmp: l.partial_cmp(r), + } + } +} + +impl Future for LeFuture +where + L: Stream + Sized, + R: Stream + Sized, + L::Item: PartialOrd, +{ + type Output = bool; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let result = futures_core::ready!(self.project().partial_cmp.poll(cx)); + + match result { + Some(Ordering::Less) | Some(Ordering::Equal) => Poll::Ready(true), + _ => Poll::Ready(false), + } + } +} diff --git a/src/stream/stream/lt.rs b/src/stream/stream/lt.rs new file mode 100644 index 00000000..100a0034 --- /dev/null +++ b/src/stream/stream/lt.rs @@ -0,0 +1,50 @@ +use std::cmp::Ordering; +use std::pin::Pin; +use std::future::Future; + +use pin_project_lite::pin_project; + +use super::partial_cmp::PartialCmpFuture; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + // Determines if the elements of this `Stream` are lexicographically + // less than those of another. + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct LtFuture { + #[pin] + partial_cmp: PartialCmpFuture, + } +} + +impl LtFuture +where + L::Item: PartialOrd, +{ + pub(super) fn new(l: L, r: R) -> Self { + Self { + partial_cmp: l.partial_cmp(r), + } + } +} + +impl Future for LtFuture +where + L: Stream + Sized, + R: Stream + Sized, + L::Item: PartialOrd, +{ + type Output = bool; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let result = futures_core::ready!(self.project().partial_cmp.poll(cx)); + + match result { + Some(Ordering::Less) => Poll::Ready(true), + _ => Poll::Ready(false), + } + } +} diff --git a/src/stream/stream/map.rs b/src/stream/stream/map.rs index 4bc2e366..8e074a75 100644 --- a/src/stream/stream/map.rs +++ b/src/stream/stream/map.rs @@ -1,41 +1,39 @@ -use std::marker::PhantomData; use std::pin::Pin; +use pin_project_lite::pin_project; + use crate::stream::Stream; use crate::task::{Context, Poll}; -#[doc(hidden)] -#[allow(missing_debug_implementations)] -pub struct Map { - stream: S, - f: F, - __from: PhantomData, - __to: PhantomData, +pin_project! { + /// A stream that maps value of another stream with a function. + #[derive(Debug)] + pub struct Map { + #[pin] + stream: S, + f: F, + } } -impl Map { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(f: F); - +impl Map { pub(crate) fn new(stream: S, f: F) -> Self { - Map { + Self { stream, f, - __from: PhantomData, - __to: PhantomData, } } } -impl Stream for Map +impl Stream for Map where S: Stream, F: FnMut(S::Item) -> B, { type Item = B; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); - Poll::Ready(next.map(self.as_mut().f())) + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); + Poll::Ready(next.map(this.f)) } } diff --git a/src/stream/stream/max.rs b/src/stream/stream/max.rs new file mode 100644 index 00000000..d8ff119d --- /dev/null +++ b/src/stream/stream/max.rs @@ -0,0 +1,60 @@ +use std::cmp::{Ord, Ordering}; +use std::marker::PhantomData; +use std::pin::Pin; +use std::future::Future; + +use pin_project_lite::pin_project; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct MaxFuture { + #[pin] + stream: S, + _compare: PhantomData, + max: Option, + } +} + +impl MaxFuture { + pub(super) fn new(stream: S) -> Self { + Self { + stream, + _compare: PhantomData, + max: None, + } + } +} + +impl Future for MaxFuture +where + S: Stream, + S::Item: Ord, + F: FnMut(&S::Item, &S::Item) -> Ordering, +{ + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); + + match next { + Some(new) => { + cx.waker().wake_by_ref(); + match this.max.take() { + None => *this.max = Some(new), + + Some(old) => match new.cmp(&old) { + Ordering::Greater => *this.max = Some(new), + _ => *this.max = Some(old), + }, + } + Poll::Pending + } + None => Poll::Ready(this.max.take()), + } + } +} diff --git a/src/stream/stream/max_by.rs b/src/stream/stream/max_by.rs new file mode 100644 index 00000000..36b876bb --- /dev/null +++ b/src/stream/stream/max_by.rs @@ -0,0 +1,57 @@ +use std::cmp::Ordering; +use std::pin::Pin; +use std::future::Future; + +use pin_project_lite::pin_project; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct MaxByFuture { + #[pin] + stream: S, + compare: F, + max: Option, + } +} + +impl MaxByFuture { + pub(super) fn new(stream: S, compare: F) -> Self { + Self { + stream, + compare, + max: None, + } + } +} + +impl Future for MaxByFuture +where + S: Stream, + F: FnMut(&S::Item, &S::Item) -> Ordering, +{ + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); + + match next { + Some(new) => { + cx.waker().wake_by_ref(); + match this.max.take() { + None => *this.max = Some(new), + Some(old) => match (this.compare)(&new, &old) { + Ordering::Greater => *this.max = Some(new), + _ => *this.max = Some(old), + }, + } + Poll::Pending + } + None => Poll::Ready(this.max.take()), + } + } +} diff --git a/src/stream/stream/max_by_key.rs b/src/stream/stream/max_by_key.rs new file mode 100644 index 00000000..e421f94a --- /dev/null +++ b/src/stream/stream/max_by_key.rs @@ -0,0 +1,65 @@ +use std::cmp::Ordering; +use std::future::Future; +use std::pin::Pin; + +use pin_project_lite::pin_project; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct MaxByKeyFuture { + #[pin] + stream: S, + max: Option<(T, T)>, + key_by: K, + } +} + +impl MaxByKeyFuture { + pub(super) fn new(stream: S, key_by: K) -> Self { + Self { + stream, + max: None, + key_by, + } + } +} + +impl Future for MaxByKeyFuture +where + S: Stream, + K: FnMut(&S::Item) -> S::Item, + S::Item: Ord, +{ + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + fn key(mut f: impl FnMut(&T) -> B) -> impl FnMut(T) -> (B, T) { + move |x| (f(&x), x) + } + + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); + + match next { + Some(new) => { + let (key, value) = key(this.key_by)(new); + cx.waker().wake_by_ref(); + + match this.max.take() { + None => *this.max = Some((key, value)), + + Some(old) => match key.cmp(&old.0) { + Ordering::Greater => *this.max = Some((key, value)), + _ => *this.max = Some(old), + }, + } + Poll::Pending + } + None => Poll::Ready(this.max.take().map(|max| max.1)), + } + } +} diff --git a/src/stream/stream/merge.rs b/src/stream/stream/merge.rs new file mode 100644 index 00000000..84ac4322 --- /dev/null +++ b/src/stream/stream/merge.rs @@ -0,0 +1,72 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; + +use pin_project_lite::pin_project; + +use crate::prelude::*; +use crate::stream::Fuse; +use crate::utils; + +pin_project! { + /// A stream that merges two other streams into a single stream. + /// + /// This `struct` is created by the [`merge`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`merge`]: trait.Stream.html#method.merge + /// [`Stream`]: trait.Stream.html + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + #[derive(Debug)] + pub struct Merge { + #[pin] + left: Fuse, + #[pin] + right: Fuse, + } +} + +impl Merge { + pub(crate) fn new(left: L, right: R) -> Self { + Self { + left: left.fuse(), + right: right.fuse(), + } + } +} + +impl Stream for Merge +where + L: Stream, + R: Stream, +{ + type Item = T; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + if utils::random(2) == 0 { + poll_next_in_order(this.left, this.right, cx) + } else { + poll_next_in_order(this.right, this.left, cx) + } + } +} + +fn poll_next_in_order( + first: Pin<&mut F>, + second: Pin<&mut S>, + cx: &mut Context<'_>, +) -> Poll> +where + F: Stream, + S: Stream, +{ + match first.poll_next(cx) { + Poll::Ready(None) => second.poll_next(cx), + Poll::Ready(item) => Poll::Ready(item), + Poll::Pending => match second.poll_next(cx) { + Poll::Ready(None) | Poll::Pending => Poll::Pending, + Poll::Ready(item) => Poll::Ready(item), + }, + } +} diff --git a/src/stream/stream/min.rs b/src/stream/stream/min.rs new file mode 100644 index 00000000..4ce52be9 --- /dev/null +++ b/src/stream/stream/min.rs @@ -0,0 +1,60 @@ +use std::cmp::{Ord, Ordering}; +use std::marker::PhantomData; +use std::pin::Pin; +use std::future::Future; + +use pin_project_lite::pin_project; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct MinFuture { + #[pin] + stream: S, + _compare: PhantomData, + min: Option, + } +} + +impl MinFuture { + pub(super) fn new(stream: S) -> Self { + Self { + stream, + _compare: PhantomData, + min: None, + } + } +} + +impl Future for MinFuture +where + S: Stream, + S::Item: Ord, + F: FnMut(&S::Item, &S::Item) -> Ordering, +{ + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); + + match next { + Some(new) => { + cx.waker().wake_by_ref(); + match this.min.take() { + None => *this.min = Some(new), + + Some(old) => match new.cmp(&old) { + Ordering::Less => *this.min = Some(new), + _ => *this.min = Some(old), + }, + } + Poll::Pending + } + None => Poll::Ready(this.min.take()), + } + } +} diff --git a/src/stream/stream/min_by.rs b/src/stream/stream/min_by.rs index a68cf313..e35719e6 100644 --- a/src/stream/stream/min_by.rs +++ b/src/stream/stream/min_by.rs @@ -1,25 +1,26 @@ use std::cmp::Ordering; use std::pin::Pin; +use std::future::Future; + +use pin_project_lite::pin_project; -use crate::future::Future; use crate::stream::Stream; use crate::task::{Context, Poll}; -#[doc(hidden)] -#[allow(missing_debug_implementations)] -pub struct MinByFuture { - stream: S, - compare: F, - min: Option, +pin_project! { + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct MinByFuture { + #[pin] + stream: S, + compare: F, + min: Option, + } } impl MinByFuture { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(compare: F); - pin_utils::unsafe_unpinned!(min: Option); - pub(super) fn new(stream: S, compare: F) -> Self { - MinByFuture { + Self { stream, compare, min: None, @@ -35,22 +36,23 @@ where { type Output = Option; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); match next { Some(new) => { cx.waker().wake_by_ref(); - match self.as_mut().min().take() { - None => *self.as_mut().min() = Some(new), - Some(old) => match (&mut self.as_mut().compare())(&new, &old) { - Ordering::Less => *self.as_mut().min() = Some(new), - _ => *self.as_mut().min() = Some(old), + match this.min.take() { + None => *this.min = Some(new), + Some(old) => match (this.compare)(&new, &old) { + Ordering::Less => *this.min = Some(new), + _ => *this.min = Some(old), }, } Poll::Pending } - None => Poll::Ready(self.min), + None => Poll::Ready(*this.min), } } } diff --git a/src/stream/stream/min_by_key.rs b/src/stream/stream/min_by_key.rs new file mode 100644 index 00000000..07c3642a --- /dev/null +++ b/src/stream/stream/min_by_key.rs @@ -0,0 +1,65 @@ +use std::cmp::Ordering; +use std::future::Future; +use std::pin::Pin; + +use pin_project_lite::pin_project; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct MinByKeyFuture { + #[pin] + stream: S, + min: Option<(T, T)>, + key_by: K, + } +} + +impl MinByKeyFuture { + pub(super) fn new(stream: S, key_by: K) -> Self { + Self { + stream, + min: None, + key_by, + } + } +} + +impl Future for MinByKeyFuture +where + S: Stream, + K: FnMut(&S::Item) -> S::Item, + S::Item: Ord, +{ + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + fn key(mut f: impl FnMut(&T) -> B) -> impl FnMut(T) -> (B, T) { + move |x| (f(&x), x) + } + + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); + + match next { + Some(new) => { + let (key, value) = key(this.key_by)(new); + cx.waker().wake_by_ref(); + + match this.min.take() { + None => *this.min = Some((key, value)), + + Some(old) => match key.cmp(&old.0) { + Ordering::Less => *this.min = Some((key, value)), + _ => *this.min = Some(old), + }, + } + Poll::Pending + } + None => Poll::Ready(this.min.take().map(|min| min.1)), + } + } +} diff --git a/src/stream/stream/mod.rs b/src/stream/stream/mod.rs index 80e31969..ef1e5ecc 100644 --- a/src/stream/stream/mod.rs +++ b/src/stream/stream/mod.rs @@ -7,7 +7,7 @@ //! # Examples //! //! ``` -//! # fn main() { async_std::task::block_on(async { +//! # async_std::task::block_on(async { //! # //! use async_std::prelude::*; //! use async_std::stream; @@ -18,14 +18,19 @@ //! assert_eq!(v, 9); //! } //! # -//! # }) } +//! # }) //! ``` mod all; mod any; mod chain; +mod cloned; +mod cmp; +mod copied; +mod cycle; mod delay; mod enumerate; +mod eq; mod filter; mod filter_map; mod find; @@ -33,36 +38,70 @@ mod find_map; mod fold; mod for_each; mod fuse; +mod ge; +mod gt; mod inspect; +mod last; +mod le; +mod lt; mod map; +mod max; +mod max_by; +mod max_by_key; +mod min; mod min_by; +mod min_by_key; +mod ne; mod next; mod nth; +mod partial_cmp; +mod position; mod scan; mod skip; mod skip_while; mod step_by; mod take; +mod take_while; +mod try_fold; mod try_for_each; mod zip; use all::AllFuture; use any::AnyFuture; +use cmp::CmpFuture; +use cycle::Cycle; use enumerate::Enumerate; +use eq::EqFuture; use filter_map::FilterMap; use find::FindFuture; use find_map::FindMapFuture; use fold::FoldFuture; use for_each::ForEachFuture; +use ge::GeFuture; +use gt::GtFuture; +use last::LastFuture; +use le::LeFuture; +use lt::LtFuture; +use max::MaxFuture; +use max_by::MaxByFuture; +use max_by_key::MaxByKeyFuture; +use min::MinFuture; use min_by::MinByFuture; +use min_by_key::MinByKeyFuture; +use ne::NeFuture; use next::NextFuture; use nth::NthFuture; -use try_for_each::TryForEeachFuture; +use partial_cmp::PartialCmpFuture; +use position::PositionFuture; +use try_fold::TryFoldFuture; +use try_for_each::TryForEachFuture; pub use chain::Chain; +pub use cloned::Cloned; +pub use copied::Copied; +pub use delay::Delay; pub use filter::Filter; pub use fuse::Fuse; -pub use delay::Delay; pub use inspect::Inspect; pub use map::Map; pub use scan::Scan; @@ -70,33 +109,45 @@ pub use skip::Skip; pub use skip_while::SkipWhile; pub use step_by::StepBy; pub use take::Take; +pub use take_while::TakeWhile; pub use zip::Zip; use std::cmp::Ordering; -use std::marker::PhantomData; - -use cfg_if::cfg_if; - -use crate::utils::extension_trait; -cfg_if! { - if #[cfg(feature = "docs")] { - use std::ops::{Deref, DerefMut}; - - use crate::task::{Context, Poll}; - } +cfg_unstable! { + use std::future::Future; + use std::pin::Pin; + use std::time::Duration; + + use crate::stream::into_stream::IntoStream; + use crate::stream::{FromStream, Product, Sum}; + use crate::stream::Extend; + + use count::CountFuture; + use partition::PartitionFuture; + use unzip::UnzipFuture; + + pub use merge::Merge; + pub use flatten::Flatten; + pub use flat_map::FlatMap; + pub use timeout::{TimeoutError, Timeout}; + pub use throttle::Throttle; + + mod count; + mod merge; + mod flatten; + mod flat_map; + mod partition; + mod timeout; + mod throttle; + mod unzip; } -cfg_if! { - if #[cfg(any(feature = "unstable", feature = "docs"))] { - use std::pin::Pin; +extension_trait! { + use std::ops::{Deref, DerefMut}; - use crate::future::Future; - use crate::stream::FromStream; - } -} + use crate::task::{Context, Poll}; -extension_trait! { #[doc = r#" An asynchronous stream of values. @@ -104,7 +155,7 @@ extension_trait! { [`std::iter::Iterator`]. The [provided methods] do not really exist in the trait itself, but they become - available when the prelude is imported: + available when [`StreamExt`] from the [prelude] is imported: ``` # #[allow(unused_imports)] @@ -113,10 +164,12 @@ extension_trait! { [`std::iter::Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html [`futures::stream::Stream`]: - https://docs.rs/futures-preview/0.3.0-alpha.17/futures/stream/trait.Stream.html + https://docs.rs/futures/0.3/futures/stream/trait.Stream.html [provided methods]: #provided-methods + [`StreamExt`]: ../prelude/trait.StreamExt.html + [prelude]: ../prelude/index.html "#] - pub trait Stream [StreamExt: futures_core::stream::Stream] { + pub trait Stream { #[doc = r#" The type of items yielded by this stream. "#] @@ -174,7 +227,14 @@ extension_trait! { ``` "#] fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; + } + + #[doc = r#" + Extension methods for [`Stream`]. + [`Stream`]: ../stream/trait.Stream.html + "#] + pub trait StreamExt: futures_core::stream::Stream { #[doc = r#" Advances the stream and returns the next value. @@ -231,10 +291,86 @@ extension_trait! { where Self: Sized, { - Take { - stream: self, - remaining: n, - } + Take::new(self, n) + } + + #[doc = r#" + Creates a stream that yields elements based on a predicate. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let s = stream::from_iter(vec![1, 2, 3, 4]); + let mut s = s.take_while(|x| x < &3 ); + + assert_eq!(s.next().await, Some(1)); + assert_eq!(s.next().await, Some(2)); + assert_eq!(s.next().await, None); + # + # }) } + ``` + "#] + fn take_while

(self, predicate: P) -> TakeWhile + where + Self: Sized, + P: FnMut(&Self::Item) -> bool, + { + TakeWhile::new(self, predicate) + } + + #[doc = r#" + Limit the amount of items yielded per timeslice in a stream. + + This stream does not drop any items, but will only limit the rate at which items pass through. + # Examples + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + use std::time::{Duration, Instant}; + + let start = Instant::now(); + + // emit value every 5 milliseconds + let s = stream::interval(Duration::from_millis(5)) + .enumerate() + .take(3); + + // throttle for 10 milliseconds + let mut s = s.throttle(Duration::from_millis(10)); + + assert_eq!(s.next().await, Some((0, ()))); + let duration_ms = start.elapsed().as_millis(); + assert!(duration_ms >= 5); + + assert_eq!(s.next().await, Some((1, ()))); + let duration_ms = start.elapsed().as_millis(); + assert!(duration_ms >= 15); + + assert_eq!(s.next().await, Some((2, ()))); + let duration_ms = start.elapsed().as_millis(); + assert!(duration_ms >= 25); + + assert_eq!(s.next().await, None); + let duration_ms = start.elapsed().as_millis(); + assert!(duration_ms >= 35); + # + # }) } + ``` + "#] + #[cfg(all(feature = "default", feature = "unstable"))] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn throttle(self, d: Duration) -> Throttle + where + Self: Sized, + { + Throttle::new(self, d) } #[doc = r#" @@ -252,9 +388,9 @@ extension_trait! { # fn main() { async_std::task::block_on(async { # use async_std::prelude::*; - use std::collections::VecDeque; + use async_std::stream; - let s: VecDeque<_> = vec![0u8, 1, 2, 3, 4].into_iter().collect(); + let s = stream::from_iter(vec![0u8, 1, 2, 3, 4]); let mut stepped = s.step_by(2); assert_eq!(stepped.next().await, Some(0)); @@ -284,10 +420,10 @@ extension_trait! { # fn main() { async_std::task::block_on(async { # use async_std::prelude::*; - use std::collections::VecDeque; + use async_std::stream; - let first: VecDeque<_> = vec![0u8, 1].into_iter().collect(); - let second: VecDeque<_> = vec![2, 3].into_iter().collect(); + let first = stream::from_iter(vec![0u8, 1]); + let second = stream::from_iter(vec![2, 3]); let mut c = first.chain(second); assert_eq!(c.next().await, Some(0)); @@ -308,6 +444,104 @@ extension_trait! { Chain::new(self, other) } + #[doc = r#" + Creates an stream which copies all of its elements. + + # Examples + + Basic usage: + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let v = stream::from_iter(vec![&1, &2, &3]); + + let mut v_cloned = v.cloned(); + + assert_eq!(v_cloned.next().await, Some(1)); + assert_eq!(v_cloned.next().await, Some(2)); + assert_eq!(v_cloned.next().await, Some(3)); + assert_eq!(v_cloned.next().await, None); + + # + # }) } + ``` + "#] + fn cloned<'a, T>(self) -> Cloned + where + Self: Sized + Stream, + T: Clone + 'a, + { + Cloned::new(self) + } + + + #[doc = r#" + Creates an stream which copies all of its elements. + + # Examples + + Basic usage: + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let s = stream::from_iter(vec![&1, &2, &3]); + let mut s_copied = s.copied(); + + assert_eq!(s_copied.next().await, Some(1)); + assert_eq!(s_copied.next().await, Some(2)); + assert_eq!(s_copied.next().await, Some(3)); + assert_eq!(s_copied.next().await, None); + # + # }) } + ``` + "#] + fn copied<'a, T>(self) -> Copied + where + Self: Sized + Stream, + T: Copy + 'a, + { + Copied::new(self) + } + + #[doc = r#" + Creates a stream that yields the provided values infinitely and in order. + + # Examples + + Basic usage: + + ``` + # async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let mut s = stream::once(7).cycle(); + + assert_eq!(s.next().await, Some(7)); + assert_eq!(s.next().await, Some(7)); + assert_eq!(s.next().await, Some(7)); + assert_eq!(s.next().await, Some(7)); + assert_eq!(s.next().await, Some(7)); + # + # }) + ``` + "#] + fn cycle(self) -> Cycle + where + Self: Clone + Sized, + { + Cycle::new(self) + } + #[doc = r#" Creates a stream that gives the current element's count as well as the next value. @@ -321,16 +555,15 @@ extension_trait! { # fn main() { async_std::task::block_on(async { # use async_std::prelude::*; - use std::collections::VecDeque; + use async_std::stream; - let s: VecDeque<_> = vec!['a', 'b', 'c'].into_iter().collect(); + let s = stream::from_iter(vec!['a', 'b', 'c']); let mut s = s.enumerate(); assert_eq!(s.next().await, Some((0, 'a'))); assert_eq!(s.next().await, Some((1, 'b'))); assert_eq!(s.next().await, Some((2, 'c'))); assert_eq!(s.next().await, None); - # # }) } ``` @@ -386,9 +619,9 @@ extension_trait! { # fn main() { async_std::task::block_on(async { # use async_std::prelude::*; - use std::collections::VecDeque; + use async_std::stream; - let s: VecDeque<_> = vec![1, 2, 3].into_iter().collect(); + let s = stream::from_iter(vec![1, 2, 3]); let mut s = s.map(|x| 2 * x); assert_eq!(s.next().await, Some(2)); @@ -400,7 +633,7 @@ extension_trait! { # }) } ``` "#] - fn map(self, f: F) -> Map + fn map(self, f: F) -> Map where Self: Sized, F: FnMut(Self::Item) -> B, @@ -420,21 +653,23 @@ extension_trait! { # fn main() { async_std::task::block_on(async { # use async_std::prelude::*; - use std::collections::VecDeque; + use async_std::stream; + + let s = stream::from_iter(vec![1, 2, 3, 4, 5]); - let a: VecDeque<_> = vec![1u8, 2, 3, 4, 5].into_iter().collect(); - let sum = a - .inspect(|x| println!("about to filter {}", x)) - .filter(|x| x % 2 == 0) - .inspect(|x| println!("made it through filter: {}", x)) - .fold(0, |sum, i| sum + i).await; + let sum = s + .inspect(|x| println!("about to filter {}", x)) + .filter(|x| x % 2 == 0) + .inspect(|x| println!("made it through filter: {}", x)) + .fold(0, |sum, i| sum + i) + .await; assert_eq!(sum, 6); # # }) } ``` "#] - fn inspect(self, f: F) -> Inspect + fn inspect(self, f: F) -> Inspect where Self: Sized, F: FnMut(&Self::Item), @@ -443,9 +678,56 @@ extension_trait! { } #[doc = r#" - Transforms this `Stream` into a "fused" `Stream` such that after the first time - `poll` returns `Poll::Ready(None)`, all future calls to `poll` will also return - `Poll::Ready(None)`. + Returns the last element of the stream. + + # Examples + + Basic usage: + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let s = stream::from_iter(vec![1, 2, 3]); + + let last = s.last().await; + assert_eq!(last, Some(3)); + # + # }) } + ``` + + An empty stream will return `None: + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::stream; + use crate::async_std::prelude::*; + + let s = stream::empty::<()>(); + + let last = s.last().await; + assert_eq!(last, None); + # + # }) } + ``` + "#] + fn last( + self, + ) -> impl Future> [LastFuture] + where + Self: Sized, + { + LastFuture::new(self) + } + + #[doc = r#" + Creates a stream which ends after the first `None`. + + After a stream returns `None`, future calls may or may not yield `Some(T)` again. + `fuse()` adapts an iterator, ensuring that after a `None` is given, it will always + return `None` forever. # Examples @@ -467,10 +749,7 @@ extension_trait! { where Self: Sized, { - Fuse { - stream: self, - done: false, - } + Fuse::new(self) } #[doc = r#" @@ -483,11 +762,10 @@ extension_trait! { ``` # fn main() { async_std::task::block_on(async { # - use std::collections::VecDeque; - use async_std::prelude::*; + use async_std::stream; - let s: VecDeque = vec![1, 2, 3, 4].into_iter().collect(); + let s = stream::from_iter(vec![1, 2, 3, 4]); let mut s = s.filter(|i| i % 2 == 0); assert_eq!(s.next().await, Some(2)); @@ -497,7 +775,7 @@ extension_trait! { # }) } ``` "#] - fn filter

(self, predicate: P) -> Filter + fn filter

(self, predicate: P) -> Filter where Self: Sized, P: FnMut(&Self::Item) -> bool, @@ -505,6 +783,76 @@ extension_trait! { Filter::new(self, predicate) } + #[doc= r#" + Creates an stream that works like map, but flattens nested structure. + + # Examples + + Basic usage: + + ``` + # async_std::task::block_on(async { + + use async_std::prelude::*; + use async_std::stream::IntoStream; + use async_std::stream; + + let inner1 = stream::from_iter(vec![1,2,3]); + let inner2 = stream::from_iter(vec![4,5,6]); + + let s = stream::from_iter(vec![inner1, inner2]); + + let v :Vec<_> = s.flat_map(|s| s.into_stream()).collect().await; + + assert_eq!(v, vec![1,2,3,4,5,6]); + + # }); + ``` + "#] + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn flat_map(self, f: F) -> FlatMap + where + Self: Sized, + U: IntoStream, + F: FnMut(Self::Item) -> U, + { + FlatMap::new(self, f) + } + + #[doc = r#" + Creates an stream that flattens nested structure. + + # Examples + + Basic usage: + + ``` + # async_std::task::block_on(async { + + use async_std::prelude::*; + use async_std::stream; + + let inner1 = stream::from_iter(vec![1u8,2,3]); + let inner2 = stream::from_iter(vec![4u8,5,6]); + let s = stream::from_iter(vec![inner1, inner2]); + + let v: Vec<_> = s.flatten().collect().await; + + assert_eq!(v, vec![1,2,3,4,5,6]); + + # }); + "#] + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn flatten(self) -> Flatten + where + Self: Sized, + Self::Item: IntoStream, + { + Flatten::new(self) + } + #[doc = r#" Both filters and maps a stream. @@ -515,11 +863,11 @@ extension_trait! { ``` # fn main() { async_std::task::block_on(async { # - use std::collections::VecDeque; use async_std::prelude::*; + use async_std::stream; - let s: VecDeque<&str> = vec!["1", "lol", "3", "NaN", "5"].into_iter().collect(); + let s = stream::from_iter(vec!["1", "lol", "3", "NaN", "5"]); let mut parsed = s.filter_map(|a| a.parse::().ok()); @@ -538,7 +886,7 @@ extension_trait! { # }) } ``` "#] - fn filter_map(self, f: F) -> FilterMap + fn filter_map(self, f: F) -> FilterMap where Self: Sized, F: FnMut(Self::Item) -> Option, @@ -548,7 +896,7 @@ extension_trait! { #[doc = r#" Returns the element that gives the minimum value with respect to the - specified comparison function. If several elements are equally minimum, + specified key function. If several elements are equally minimum, the first element is returned. If the stream is empty, `None` is returned. # Examples @@ -556,66 +904,239 @@ extension_trait! { ``` # fn main() { async_std::task::block_on(async { # - use std::collections::VecDeque; - use async_std::prelude::*; + use async_std::stream; - let s: VecDeque = vec![1, 2, 3].into_iter().collect(); - - let min = s.clone().min_by(|x, y| x.cmp(y)).await; - assert_eq!(min, Some(1)); + let s = stream::from_iter(vec![-1isize, 2, -3]); - let min = s.min_by(|x, y| y.cmp(x)).await; - assert_eq!(min, Some(3)); + let min = s.clone().min_by_key(|x| x.abs()).await; + assert_eq!(min, Some(-1)); - let min = VecDeque::::new().min_by(|x, y| x.cmp(y)).await; + let min = stream::empty::().min_by_key(|x| x.abs()).await; assert_eq!(min, None); # # }) } ``` "#] - fn min_by( + fn min_by_key( self, - compare: F, - ) -> impl Future> [MinByFuture] + key_by: F, + ) -> impl Future> [MinByKeyFuture] where Self: Sized, - F: FnMut(&Self::Item, &Self::Item) -> Ordering, + B: Ord, + F: FnMut(&Self::Item) -> B, { - MinByFuture::new(self, compare) + MinByKeyFuture::new(self, key_by) } #[doc = r#" - Returns the nth element of the stream. + Returns the element that gives the maximum value with respect to the + specified key function. If several elements are equally maximum, + the first element is returned. If the stream is empty, `None` is returned. # Examples - Basic usage: - ``` # fn main() { async_std::task::block_on(async { # - use std::collections::VecDeque; - use async_std::prelude::*; + use async_std::stream; - let mut s: VecDeque = vec![1, 2, 3].into_iter().collect(); + let s = stream::from_iter(vec![-3_i32, 0, 1, 5, -10]); - let second = s.nth(1).await; - assert_eq!(second, Some(2)); + let max = s.clone().max_by_key(|x| x.abs()).await; + assert_eq!(max, Some(-10)); + + let max = stream::empty::().max_by_key(|x| x.abs()).await; + assert_eq!(max, None); # # }) } ``` - Calling `nth()` multiple times: + "#] + fn max_by_key( + self, + key_by: F, + ) -> impl Future> [MaxByKeyFuture] + where + Self: Sized, + B: Ord, + F: FnMut(&Self::Item) -> B, + { + MaxByKeyFuture::new(self, key_by) + } + + #[doc = r#" + Returns the element that gives the minimum value with respect to the + specified comparison function. If several elements are equally minimum, + the first element is returned. If the stream is empty, `None` is returned. + + # Examples ``` # fn main() { async_std::task::block_on(async { # - use std::collections::VecDeque; - use async_std::prelude::*; + use async_std::stream; + + let s = stream::from_iter(vec![1u8, 2, 3]); + + let min = s.clone().min_by(|x, y| x.cmp(y)).await; + assert_eq!(min, Some(1)); + + let min = s.min_by(|x, y| y.cmp(x)).await; + assert_eq!(min, Some(3)); + + let min = stream::empty::().min_by(|x, y| x.cmp(y)).await; + assert_eq!(min, None); + # + # }) } + ``` + "#] + fn min_by( + self, + compare: F, + ) -> impl Future> [MinByFuture] + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, + { + MinByFuture::new(self, compare) + } + + #[doc = r#" + Returns the element that gives the maximum value. If several elements are equally maximum, + the first element is returned. If the stream is empty, `None` is returned. + + # Examples + + ```ignore + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let s = stream::from_iter(vec![1usize, 2, 3]); + + let max = s.clone().max().await; + assert_eq!(max, Some(3)); + + let max = stream::empty::().max().await; + assert_eq!(max, None); + # + # }) } + ``` + "#] + fn max( + self, + ) -> impl Future> [MaxFuture] + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, + { + MaxFuture::new(self) + } + + #[doc = r#" + Returns the element that gives the minimum value. If several elements are equally minimum, + the first element is returned. If the stream is empty, `None` is returned. + + # Examples + + ```ignore + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let s = stream::from_iter(vec![1usize, 2, 3]); + + let min = s.clone().min().await; + assert_eq!(min, Some(1)); + + let min = stream::empty::().min().await; + assert_eq!(min, None); + # + # }) } + ``` + "#] + fn min( + self, + ) -> impl Future> [MinFuture] + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, + { + MinFuture::new(self) + } + + #[doc = r#" + Returns the element that gives the maximum value with respect to the + specified comparison function. If several elements are equally maximum, + the first element is returned. If the stream is empty, `None` is returned. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let s = stream::from_iter(vec![1u8, 2, 3]); + + let max = s.clone().max_by(|x, y| x.cmp(y)).await; + assert_eq!(max, Some(3)); + + let max = s.max_by(|x, y| y.cmp(x)).await; + assert_eq!(max, Some(1)); + + let max = stream::empty::().max_by(|x, y| x.cmp(y)).await; + assert_eq!(max, None); + # + # }) } + ``` + "#] + fn max_by( + self, + compare: F, + ) -> impl Future> [MaxByFuture] + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, + { + MaxByFuture::new(self, compare) + } - let mut s: VecDeque = vec![1, 2, 3].into_iter().collect(); + #[doc = r#" + Returns the nth element of the stream. + + # Examples + + Basic usage: + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let mut s = stream::from_iter(vec![1u8, 2, 3]); + + let second = s.nth(1).await; + assert_eq!(second, Some(2)); + # + # }) } + ``` + Calling `nth()` multiple times: + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::stream; + use async_std::prelude::*; + + let mut s = stream::from_iter(vec![1u8, 2, 3]); let second = s.nth(0).await; assert_eq!(second, Some(1)); @@ -629,11 +1150,10 @@ extension_trait! { ``` # fn main() { async_std::task::block_on(async { # - use std::collections::VecDeque; - use async_std::prelude::*; + use async_std::stream; - let mut s: VecDeque = vec![1, 2, 3].into_iter().collect(); + let mut s = stream::from_iter(vec![1u8, 2, 3]); let fourth = s.nth(4).await; assert_eq!(fourth, None); @@ -646,7 +1166,7 @@ extension_trait! { n: usize, ) -> impl Future> + '_ [NthFuture<'_, Self>] where - Self: Sized, + Self: Unpin + Sized, { NthFuture::new(self, n) } @@ -705,12 +1225,7 @@ extension_trait! { Self: Unpin + Sized, F: FnMut(Self::Item) -> bool, { - AllFuture { - stream: self, - result: true, // the default if the empty stream - _marker: PhantomData, - f, - } + AllFuture::new(self, f) } #[doc = r#" @@ -724,9 +1239,9 @@ extension_trait! { # fn main() { async_std::task::block_on(async { # use async_std::prelude::*; - use std::collections::VecDeque; + use async_std::stream; - let mut s: VecDeque = vec![1, 2, 3].into_iter().collect(); + let mut s = stream::from_iter(vec![1u8, 2, 3]); let res = s.find(|x| *x == 2).await; assert_eq!(res, Some(2)); # @@ -739,9 +1254,9 @@ extension_trait! { # fn main() { async_std::task::block_on(async { # use async_std::prelude::*; - use std::collections::VecDeque; + use async_std::stream; - let mut s: VecDeque = vec![1, 2, 3].into_iter().collect(); + let mut s= stream::from_iter(vec![1, 2, 3]); let res = s.find(|x| *x == 2).await; assert_eq!(res, Some(2)); @@ -754,9 +1269,9 @@ extension_trait! { fn find

( &mut self, p: P, - ) -> impl Future> + '_ [FindFuture<'_, Self, P, Self::Item>] + ) -> impl Future> + '_ [FindFuture<'_, Self, P>] where - Self: Sized, + Self: Unpin + Sized, P: FnMut(&Self::Item) -> bool, { FindFuture::new(self, p) @@ -769,9 +1284,9 @@ extension_trait! { # fn main() { async_std::task::block_on(async { # use async_std::prelude::*; - use std::collections::VecDeque; + use async_std::stream; - let mut s: VecDeque<&str> = vec!["lol", "NaN", "2", "5"].into_iter().collect(); + let mut s = stream::from_iter(vec!["lol", "NaN", "2", "5"]); let first_number = s.find_map(|s| s.parse().ok()).await; assert_eq!(first_number, Some(2)); @@ -782,9 +1297,9 @@ extension_trait! { fn find_map( &mut self, f: F, - ) -> impl Future> + '_ [FindMapFuture<'_, Self, F, Self::Item, B>] + ) -> impl Future> + '_ [FindMapFuture<'_, Self, F>] where - Self: Sized, + Self: Unpin + Sized, F: FnMut(Self::Item) -> Option, { FindMapFuture::new(self, f) @@ -802,9 +1317,9 @@ extension_trait! { # fn main() { async_std::task::block_on(async { # use async_std::prelude::*; - use std::collections::VecDeque; + use async_std::stream; - let s: VecDeque = vec![1, 2, 3].into_iter().collect(); + let s = stream::from_iter(vec![1u8, 2, 3]); let sum = s.fold(0, |acc, x| acc + x).await; assert_eq!(sum, 6); @@ -816,7 +1331,7 @@ extension_trait! { self, init: B, f: F, - ) -> impl Future [FoldFuture] + ) -> impl Future [FoldFuture] where Self: Sized, F: FnMut(B, Self::Item) -> B, @@ -824,6 +1339,44 @@ extension_trait! { FoldFuture::new(self, init, f) } + #[doc = r#" + A combinator that applies a function to every element in a stream + creating two collections from it. + + # Examples + + Basic usage: + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let (even, odd): (Vec, Vec) = stream::from_iter(vec![1, 2, 3]) + .partition(|&n| n % 2 == 0).await; + + assert_eq!(even, vec![2]); + assert_eq!(odd, vec![1, 3]); + + # + # }) } + ``` + "#] + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn partition( + self, + f: F, + ) -> impl Future [PartitionFuture] + where + Self: Sized, + F: FnMut(&Self::Item) -> bool, + B: Default + Extend, + { + PartitionFuture::new(self, f) + } + #[doc = r#" Call a closure on each element of the stream. @@ -833,12 +1386,12 @@ extension_trait! { # fn main() { async_std::task::block_on(async { # use async_std::prelude::*; - use std::collections::VecDeque; + use async_std::stream; use std::sync::mpsc::channel; let (tx, rx) = channel(); - let s: VecDeque = vec![1, 2, 3].into_iter().collect(); + let s = stream::from_iter(vec![1usize, 2, 3]); let sum = s.for_each(move |x| tx.clone().send(x).unwrap()).await; let v: Vec<_> = rx.iter().collect(); @@ -851,7 +1404,7 @@ extension_trait! { fn for_each( self, f: F, - ) -> impl Future [ForEachFuture] + ) -> impl Future [ForEachFuture] where Self: Sized, F: FnMut(Self::Item), @@ -912,12 +1465,55 @@ extension_trait! { Self: Unpin + Sized, F: FnMut(Self::Item) -> bool, { - AnyFuture { - stream: self, - result: false, // the default if the empty stream - _marker: PhantomData, - f, - } + AnyFuture::new(self, f) + } + + #[doc = r#" + Borrows an stream, rather than consuming it. + + This is useful to allow applying stream adaptors while still retaining ownership of the original stream. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let a = vec![1isize, 2, 3]; + + let stream = stream::from_iter(a); + + let sum: isize = stream.take(5).sum().await; + + assert_eq!(sum, 6); + + // if we try to use stream again, it won't work. The following line + // gives error: use of moved value: `stream` + // assert_eq!(stream.next(), None); + + // let's try that again + let a = vec![1isize, 2, 3]; + + let mut stream = stream::from_iter(a); + + // instead, we add in a .by_ref() + let sum: isize = stream.by_ref().take(2).sum().await; + + assert_eq!(sum, 3); + + // now this is just fine: + assert_eq!(stream.next().await, Some(3)); + assert_eq!(stream.next().await, None); + # + # }) } + ``` + "#] + #[cfg(all(feature = "default", feature = "unstable"))] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn by_ref(&mut self) -> &mut Self { + self } #[doc = r#" @@ -939,11 +1535,10 @@ extension_trait! { ``` # fn main() { async_std::task::block_on(async { # - use std::collections::VecDeque; - use async_std::prelude::*; + use async_std::stream; - let s: VecDeque = vec![1, 2, 3].into_iter().collect(); + let s = stream::from_iter(vec![1isize, 2, 3]); let mut s = s.scan(1, |state, x| { *state = *state * x; Some(-*state) @@ -980,11 +1575,10 @@ extension_trait! { ``` # fn main() { async_std::task::block_on(async { # - use std::collections::VecDeque; - use async_std::prelude::*; + use async_std::stream; - let a: VecDeque<_> = vec![-1i32, 0, 1].into_iter().collect(); + let a = stream::from_iter(vec![-1i32, 0, 1]); let mut s = a.skip_while(|x| x.is_negative()); assert_eq!(s.next().await, Some(0)); @@ -994,7 +1588,7 @@ extension_trait! { # }) } ``` "#] - fn skip_while

(self, predicate: P) -> SkipWhile + fn skip_while

(self, predicate: P) -> SkipWhile where Self: Sized, P: FnMut(&Self::Item) -> bool, @@ -1010,11 +1604,10 @@ extension_trait! { ``` # fn main() { async_std::task::block_on(async { # - use std::collections::VecDeque; - use async_std::prelude::*; + use async_std::stream; - let s: VecDeque = vec![1, 2, 3].into_iter().collect(); + let s = stream::from_iter(vec![1u8, 2, 3]); let mut skipped = s.skip(2); assert_eq!(skipped.next().await, Some(3)); @@ -1030,6 +1623,80 @@ extension_trait! { Skip::new(self, n) } + #[doc=r#" + Await a stream or times out after a duration of time. + + If you want to await an I/O future consider using + [`io::timeout`](../io/fn.timeout.html) instead. + + # Examples + + ``` + # fn main() -> std::io::Result<()> { async_std::task::block_on(async { + # + use std::time::Duration; + + use async_std::stream; + use async_std::prelude::*; + + let mut s = stream::repeat(1).take(3).timeout(Duration::from_secs(1)); + + while let Some(v) = s.next().await { + assert_eq!(v, Ok(1)); + } + # + # Ok(()) }) } + ``` + "#] + #[cfg(any(feature = "unstable", feature = "docs"))] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn timeout(self, dur: Duration) -> Timeout + where + Self: Stream + Sized, + { + Timeout::new(self, dur) + } + + #[doc = r#" + A combinator that applies a function as long as it returns successfully, producing a single, final value. + Immediately returns the error when the function returns unsuccessfully. + + # Examples + + Basic usage: + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let mut s = stream::from_iter(vec![1usize, 2, 3]); + let sum = s.try_fold(0, |acc, v| { + if (acc+v) % 2 == 1 { + Ok(v+3) + } else { + Err("fail") + } + }).await; + + assert_eq!(sum, Err("fail")); + # + # }) } + ``` + "#] + fn try_fold( + &mut self, + init: T, + f: F, + ) -> impl Future> + '_ [TryFoldFuture<'_, Self, F, T>] + where + Self: Unpin + Sized, + F: FnMut(B, Self::Item) -> Result, + { + TryFoldFuture::new(self, init, f) + } + #[doc = r#" Applies a falliable function to each element in a stream, stopping at first error and returning it. @@ -1038,13 +1705,13 @@ extension_trait! { ``` # fn main() { async_std::task::block_on(async { # - use std::collections::VecDeque; use std::sync::mpsc::channel; use async_std::prelude::*; + use async_std::stream; let (tx, rx) = channel(); - let s: VecDeque = vec![1, 2, 3].into_iter().collect(); + let mut s = stream::from_iter(vec![1u8, 2, 3]); let s = s.try_for_each(|v| { if v % 2 == 1 { tx.clone().send(v).unwrap(); @@ -1065,14 +1732,14 @@ extension_trait! { ``` "#] fn try_for_each( - self, + &mut self, f: F, - ) -> impl Future [TryForEeachFuture] + ) -> impl Future + 'a [TryForEachFuture<'_, Self, F>] where - Self: Sized, + Self: Unpin + Sized, F: FnMut(Self::Item) -> Result<(), E>, { - TryForEeachFuture::new(self, f) + TryForEachFuture::new(self, f) } #[doc = r#" @@ -1096,12 +1763,11 @@ extension_trait! { ``` # fn main() { async_std::task::block_on(async { # - use std::collections::VecDeque; - use async_std::prelude::*; + use async_std::stream; - let l: VecDeque = vec![1, 2, 3].into_iter().collect(); - let r: VecDeque = vec![4, 5, 6, 7].into_iter().collect(); + let l = stream::from_iter(vec![1u8, 2, 3]); + let r = stream::from_iter(vec![4u8, 5, 6, 7]); let mut s = l.zip(r); assert_eq!(s.next().await, Some((1, 4))); @@ -1115,12 +1781,50 @@ extension_trait! { #[inline] fn zip(self, other: U) -> Zip where - Self: Sized + Stream, + Self: Sized, U: Stream, { Zip::new(self, other) } + #[doc = r#" + Converts an stream of pairs into a pair of containers. + + `unzip()` consumes an entire stream of pairs, producing two collections: one from the left elements of the pairs, and one from the right elements. + + This function is, in some sense, the opposite of [`zip`]. + + [`zip`]: trait.Stream.html#method.zip + + # Example + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let s = stream::from_iter(vec![(1,2), (3,4)]); + + let (left, right): (Vec<_>, Vec<_>) = s.unzip().await; + + assert_eq!(left, [1, 3]); + assert_eq!(right, [2, 4]); + # + # }) } + ``` + "#] + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn unzip(self) -> impl Future [UnzipFuture] + where + FromA: Default + Extend, + FromB: Default + Extend, + Self: Stream + Sized, + { + UnzipFuture::new(self) + } + #[doc = r#" Transforms a stream into a collection. @@ -1129,7 +1833,7 @@ extension_trait! { standard library, used in a variety of contexts. The most basic pattern in which `collect()` is used is to turn one - collection into another. You take a collection, call [`stream`] on it, + collection into another. You take a collection, call [`into_stream`] on it, do a bunch of transformations, and then `collect()` at the end. Because `collect()` is so general, it can cause problems with type @@ -1170,11 +1874,10 @@ extension_trait! { # }) } ``` - [`stream`]: trait.Stream.html#tymethod.next + [`into_stream`]: trait.IntoStream.html#tymethod.into_stream "#] - #[cfg(any(feature = "unstable", feature = "docs"))] + #[cfg(feature = "unstable")] #[cfg_attr(feature = "docs", doc(cfg(unstable)))] - #[must_use = "if you really need to exhaust the iterator, consider `.for_each(drop)` instead (TODO)"] fn collect<'a, B>( self, ) -> impl Future + 'a [Pin + 'a>>] @@ -1184,6 +1887,498 @@ extension_trait! { { FromStream::from_stream(self) } + + #[doc = r#" + Combines multiple streams into a single stream of all their outputs. + + Items are yielded as soon as they're received, and the stream continues yield until + both streams have been exhausted. The output ordering between streams is not guaranteed. + + # Examples + + ``` + # async_std::task::block_on(async { + use async_std::prelude::*; + use async_std::stream::{self, FromStream}; + + let a = stream::once(1u8); + let b = stream::once(2u8); + let c = stream::once(3u8); + + let s = a.merge(b).merge(c); + let mut lst = Vec::from_stream(s).await; + + lst.sort_unstable(); + assert_eq!(&lst, &[1u8, 2u8, 3u8]); + # }); + ``` + "#] + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn merge(self, other: U) -> Merge + where + Self: Sized, + U: Stream + Sized, + { + Merge::new(self, other) + } + + #[doc = r#" + Lexicographically compares the elements of this `Stream` with those + of another. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + use std::cmp::Ordering; + + let s1 = stream::from_iter(vec![1]); + let s2 = stream::from_iter(vec![1, 2]); + let s3 = stream::from_iter(vec![1, 2, 3]); + let s4 = stream::from_iter(vec![1, 2, 4]); + assert_eq!(s1.clone().partial_cmp(s1.clone()).await, Some(Ordering::Equal)); + assert_eq!(s1.clone().partial_cmp(s2.clone()).await, Some(Ordering::Less)); + assert_eq!(s2.clone().partial_cmp(s1.clone()).await, Some(Ordering::Greater)); + assert_eq!(s3.clone().partial_cmp(s4.clone()).await, Some(Ordering::Less)); + assert_eq!(s4.clone().partial_cmp(s3.clone()).await, Some(Ordering::Greater)); + # + # }) } + ``` + "#] + fn partial_cmp( + self, + other: S + ) -> impl Future> [PartialCmpFuture] + where + Self: Sized + Stream, + S: Stream, + ::Item: PartialOrd, + { + PartialCmpFuture::new(self, other) + } + + #[doc = r#" + Searches for an element in a Stream that satisfies a predicate, returning + its index. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let s = stream::from_iter(vec![1usize, 2, 3]); + let res = s.clone().position(|x| x == 1).await; + assert_eq!(res, Some(0)); + + let res = s.clone().position(|x| x == 2).await; + assert_eq!(res, Some(1)); + + let res = s.clone().position(|x| x == 3).await; + assert_eq!(res, Some(2)); + + let res = s.clone().position(|x| x == 4).await; + assert_eq!(res, None); + # + # }) } + ``` + "#] + fn position

( + &mut self, + predicate: P, + ) -> impl Future> + '_ [PositionFuture<'_, Self, P>] + where + Self: Unpin + Sized, + P: FnMut(Self::Item) -> bool, + { + PositionFuture::new(self, predicate) + } + + #[doc = r#" + Lexicographically compares the elements of this `Stream` with those + of another using 'Ord'. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + use std::cmp::Ordering; + + let s1 = stream::from_iter(vec![1]); + let s2 = stream::from_iter(vec![1, 2]); + let s3 = stream::from_iter(vec![1, 2, 3]); + let s4 = stream::from_iter(vec![1, 2, 4]); + + assert_eq!(s1.clone().cmp(s1.clone()).await, Ordering::Equal); + assert_eq!(s1.clone().cmp(s2.clone()).await, Ordering::Less); + assert_eq!(s2.clone().cmp(s1.clone()).await, Ordering::Greater); + assert_eq!(s3.clone().cmp(s4.clone()).await, Ordering::Less); + assert_eq!(s4.clone().cmp(s3.clone()).await, Ordering::Greater); + # + # }) } + ``` + "#] + fn cmp( + self, + other: S + ) -> impl Future [CmpFuture] + where + Self: Sized + Stream, + S: Stream, + ::Item: Ord + { + CmpFuture::new(self, other) + } + + #[doc = r#" + Counts the number of elements in the stream. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let s1 = stream::from_iter(vec![0]); + let s2 = stream::from_iter(vec![1, 2, 3]); + + assert_eq!(s1.count().await, 1); + assert_eq!(s2.count().await, 3); + # + # }) } + ``` + "#] + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn count(self) -> impl Future [CountFuture] + where + Self: Sized, + { + CountFuture::new(self) + } + + #[doc = r#" + Determines if the elements of this `Stream` are lexicographically + not equal to those of another. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let single = stream::from_iter(vec![1usize]); + let single_ne = stream::from_iter(vec![10usize]); + let multi = stream::from_iter(vec![1usize,2]); + let multi_ne = stream::from_iter(vec![1usize,5]); + + assert_eq!(single.clone().ne(single.clone()).await, false); + assert_eq!(single_ne.clone().ne(single.clone()).await, true); + assert_eq!(multi.clone().ne(single_ne.clone()).await, true); + assert_eq!(multi_ne.clone().ne(multi.clone()).await, true); + # + # }) } + ``` + "#] + fn ne( + self, + other: S + ) -> impl Future [NeFuture] + where + Self: Sized, + S: Sized + Stream, + ::Item: PartialEq, + { + NeFuture::new(self, other) + } + + #[doc = r#" + Determines if the elements of this `Stream` are lexicographically + greater than or equal to those of another. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let single = stream::from_iter(vec![1]); + let single_gt = stream::from_iter(vec![10]); + let multi = stream::from_iter(vec![1,2]); + let multi_gt = stream::from_iter(vec![1,5]); + + assert_eq!(single.clone().ge(single.clone()).await, true); + assert_eq!(single_gt.clone().ge(single.clone()).await, true); + assert_eq!(multi.clone().ge(single_gt.clone()).await, false); + assert_eq!(multi_gt.clone().ge(multi.clone()).await, true); + # + # }) } + ``` + "#] + fn ge( + self, + other: S + ) -> impl Future [GeFuture] + where + Self: Sized + Stream, + S: Stream, + ::Item: PartialOrd, + { + GeFuture::new(self, other) + } + + #[doc = r#" + Determines if the elements of this `Stream` are lexicographically + equal to those of another. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let single = stream::from_iter(vec![1]); + let single_eq = stream::from_iter(vec![10]); + let multi = stream::from_iter(vec![1,2]); + let multi_eq = stream::from_iter(vec![1,5]); + + assert_eq!(single.clone().eq(single.clone()).await, true); + assert_eq!(single_eq.clone().eq(single.clone()).await, false); + assert_eq!(multi.clone().eq(single_eq.clone()).await, false); + assert_eq!(multi_eq.clone().eq(multi.clone()).await, false); + # + # }) } + ``` + "#] + fn eq( + self, + other: S + ) -> impl Future [EqFuture] + where + Self: Sized + Stream, + S: Sized + Stream, + ::Item: PartialEq, + { + EqFuture::new(self, other) + } + + #[doc = r#" + Determines if the elements of this `Stream` are lexicographically + greater than those of another. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let single = stream::from_iter(vec![1]); + let single_gt = stream::from_iter(vec![10]); + let multi = stream::from_iter(vec![1,2]); + let multi_gt = stream::from_iter(vec![1,5]); + + assert_eq!(single.clone().gt(single.clone()).await, false); + assert_eq!(single_gt.clone().gt(single.clone()).await, true); + assert_eq!(multi.clone().gt(single_gt.clone()).await, false); + assert_eq!(multi_gt.clone().gt(multi.clone()).await, true); + # + # }) } + ``` + "#] + fn gt( + self, + other: S + ) -> impl Future [GtFuture] + where + Self: Sized + Stream, + S: Stream, + ::Item: PartialOrd, + { + GtFuture::new(self, other) + } + + #[doc = r#" + Determines if the elements of this `Stream` are lexicographically + less or equal to those of another. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let single = stream::from_iter(vec![1]); + let single_gt = stream::from_iter(vec![10]); + let multi = stream::from_iter(vec![1,2]); + let multi_gt = stream::from_iter(vec![1,5]); + + assert_eq!(single.clone().le(single.clone()).await, true); + assert_eq!(single.clone().le(single_gt.clone()).await, true); + assert_eq!(multi.clone().le(single_gt.clone()).await, true); + assert_eq!(multi_gt.clone().le(multi.clone()).await, false); + # + # }) } + ``` + "#] + fn le( + self, + other: S + ) -> impl Future [LeFuture] + where + Self: Sized + Stream, + S: Stream, + ::Item: PartialOrd, + { + LeFuture::new(self, other) + } + + #[doc = r#" + Determines if the elements of this `Stream` are lexicographically + less than those of another. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let single = stream::from_iter(vec![1]); + let single_gt = stream::from_iter(vec![10]); + let multi = stream::from_iter(vec![1,2]); + let multi_gt = stream::from_iter(vec![1,5]); + + assert_eq!(single.clone().lt(single.clone()).await, false); + assert_eq!(single.clone().lt(single_gt.clone()).await, true); + assert_eq!(multi.clone().lt(single_gt.clone()).await, true); + assert_eq!(multi_gt.clone().lt(multi.clone()).await, false); + # + # }) } + ``` + "#] + fn lt( + self, + other: S + ) -> impl Future [LtFuture] + where + Self: Sized + Stream, + S: Stream, + ::Item: PartialOrd, + { + LtFuture::new(self, other) + } + + #[doc = r#" + Sums the elements of a stream. + + Takes each element, adds them together, and returns the result. + + An empty streams returns the zero value of the type. + + # Panics + + When calling `sum()` and a primitive integer type is being returned, this + method will panic if the computation overflows and debug assertions are + enabled. + + # Examples + + Basic usage: + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use async_std::stream; + + let s = stream::from_iter(vec![0u8, 1, 2, 3, 4]); + let sum: u8 = s.sum().await; + + assert_eq!(sum, 10); + # + # }) } + ``` + "#] + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn sum<'a, S>( + self, + ) -> impl Future + 'a [Pin + 'a>>] + where + Self: Sized + Stream + 'a, + S: Sum, + { + Sum::sum(self) + } + + #[doc = r#" + Multiplies all elements of the stream. + + An empty stream returns the one value of the type. + + # Panics + + When calling `product()` and a primitive integer type is being returned, + method will panic if the computation overflows and debug assertions are + enabled. + + # Examples + + This example calculates the factorial of n (i.e. the product of the numbers from 1 to + n, inclusive): + + ``` + # fn main() { async_std::task::block_on(async { + # + async fn factorial(n: u32) -> u32 { + use async_std::prelude::*; + use async_std::stream; + + let s = stream::from_iter(1..=n); + s.product().await + } + + assert_eq!(factorial(0).await, 1); + assert_eq!(factorial(1).await, 1); + assert_eq!(factorial(5).await, 120); + # + # }) } + ``` + "#] + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + fn product<'a, P>( + self, + ) -> impl Future + 'a [Pin + 'a>>] + where + Self: Sized + Stream + 'a, + P: Product, + { + Product::product(self) + } } impl Stream for Box { @@ -1214,14 +2409,6 @@ extension_trait! { } } - impl Stream for std::collections::VecDeque { - type Item = T; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - unreachable!("this impl only appears in the rendered docs") - } - } - impl Stream for std::panic::AssertUnwindSafe { type Item = S::Item; diff --git a/src/stream/stream/ne.rs b/src/stream/stream/ne.rs new file mode 100644 index 00000000..ec11d1fd --- /dev/null +++ b/src/stream/stream/ne.rs @@ -0,0 +1,65 @@ +use std::pin::Pin; +use std::future::Future; + +use pin_project_lite::pin_project; + +use super::fuse::Fuse; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + // Lexicographically compares the elements of this `Stream` with those + // of another. + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct NeFuture { + #[pin] + l: Fuse, + #[pin] + r: Fuse, + } +} + +impl NeFuture +where + L::Item: PartialEq, +{ + pub(super) fn new(l: L, r: R) -> Self { + Self { + l: l.fuse(), + r: r.fuse(), + } + } +} + +impl Future for NeFuture +where + L: Stream + Sized, + R: Stream + Sized, + L::Item: PartialEq, +{ + type Output = bool; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + + loop { + let l_val = futures_core::ready!(this.l.as_mut().poll_next(cx)); + let r_val = futures_core::ready!(this.r.as_mut().poll_next(cx)); + + if this.l.done || this.r.done { + return Poll::Ready(false); + } + + match (l_val, r_val) { + (Some(l), Some(r)) if l == r => { + continue; + } + _ => { + return Poll::Ready(true); + } + } + } + } +} diff --git a/src/stream/stream/next.rs b/src/stream/stream/next.rs index de75f5e9..23abb0b4 100644 --- a/src/stream/stream/next.rs +++ b/src/stream/stream/next.rs @@ -1,6 +1,6 @@ use std::pin::Pin; +use std::future::Future; -use crate::future::Future; use crate::stream::Stream; use crate::task::{Context, Poll}; diff --git a/src/stream/stream/nth.rs b/src/stream/stream/nth.rs index e7e042a9..267bd40a 100644 --- a/src/stream/stream/nth.rs +++ b/src/stream/stream/nth.rs @@ -1,7 +1,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; +use std::future::Future; -use crate::future::Future; use crate::stream::Stream; #[doc(hidden)] @@ -15,7 +15,7 @@ impl Unpin for NthFuture<'_, S> {} impl<'a, S> NthFuture<'a, S> { pub(crate) fn new(stream: &'a mut S, n: usize) -> Self { - NthFuture { stream, n } + Self { stream, n } } } diff --git a/src/stream/stream/partial_cmp.rs b/src/stream/stream/partial_cmp.rs new file mode 100644 index 00000000..85587c99 --- /dev/null +++ b/src/stream/stream/partial_cmp.rs @@ -0,0 +1,94 @@ +use std::cmp::Ordering; +use std::future::Future; +use std::pin::Pin; + +use pin_project_lite::pin_project; + +use super::fuse::Fuse; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + // Lexicographically compares the elements of this `Stream` with those + // of another. + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct PartialCmpFuture { + #[pin] + l: Fuse, + #[pin] + r: Fuse, + l_cache: Option, + r_cache: Option, + } +} + +impl PartialCmpFuture { + pub(super) fn new(l: L, r: R) -> Self { + Self { + l: l.fuse(), + r: r.fuse(), + l_cache: None, + r_cache: None, + } + } +} + +impl Future for PartialCmpFuture +where + L: Stream + Sized, + R: Stream + Sized, + L::Item: PartialOrd, +{ + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + loop { + // Short circuit logic + // Stream that completes earliest can be considered Less, etc + let l_complete = this.l.done && this.l_cache.is_none(); + let r_complete = this.r.done && this.r_cache.is_none(); + + if l_complete && r_complete { + return Poll::Ready(Some(Ordering::Equal)); + } else if l_complete { + return Poll::Ready(Some(Ordering::Less)); + } else if r_complete { + return Poll::Ready(Some(Ordering::Greater)); + } + + // Get next value if possible and necesary + if !this.l.done && this.l_cache.is_none() { + let l_next = futures_core::ready!(this.l.as_mut().poll_next(cx)); + if let Some(item) = l_next { + *this.l_cache = Some(item); + } + } + + if !this.r.done && this.r_cache.is_none() { + let r_next = futures_core::ready!(this.r.as_mut().poll_next(cx)); + if let Some(item) = r_next { + *this.r_cache = Some(item); + } + } + + // Compare if both values are available. + if this.l_cache.is_some() && this.r_cache.is_some() { + let l_value = this.l_cache.as_mut().take().unwrap(); + let r_value = this.r_cache.as_mut().take().unwrap(); + let result = l_value.partial_cmp(&r_value); + + if let Some(Ordering::Equal) = result { + // Reset cache to prepare for next comparison + *this.l_cache = None; + *this.r_cache = None; + } else { + // Return non equal value + return Poll::Ready(result); + } + } + } + } +} diff --git a/src/stream/stream/partition.rs b/src/stream/stream/partition.rs new file mode 100644 index 00000000..7e2caad4 --- /dev/null +++ b/src/stream/stream/partition.rs @@ -0,0 +1,59 @@ +use pin_project_lite::pin_project; +use std::default::Default; +use std::future::Future; +use std::pin::Pin; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + #[derive(Debug)] + #[cfg(all(feature = "default", feature = "unstable"))] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + pub struct PartitionFuture { + #[pin] + stream: S, + f: F, + res: Option<(B, B)>, + } +} + +impl PartitionFuture { + pub(super) fn new(stream: S, f: F) -> Self { + Self { + stream, + f, + res: Some((B::default(), B::default())), + } + } +} + +impl Future for PartitionFuture +where + S: Stream + Sized, + F: FnMut(&S::Item) -> bool, + B: Default + Extend, +{ + type Output = (B, B); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + + loop { + let next = futures_core::ready!(this.stream.as_mut().poll_next(cx)); + + match next { + Some(v) => { + let res = this.res.as_mut().unwrap(); + + if (this.f)(&v) { + res.0.extend(Some(v)) + } else { + res.1.extend(Some(v)) + } + } + None => return Poll::Ready(this.res.take().unwrap()), + } + } + } +} diff --git a/src/stream/stream/position.rs b/src/stream/stream/position.rs new file mode 100644 index 00000000..df60eaae --- /dev/null +++ b/src/stream/stream/position.rs @@ -0,0 +1,50 @@ +use std::future::Future; +use std::pin::Pin; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub struct PositionFuture<'a, S, P> { + stream: &'a mut S, + predicate: P, + index: usize, +} + +impl<'a, S, P> Unpin for PositionFuture<'a, S, P> {} + +impl<'a, S, P> PositionFuture<'a, S, P> { + pub(super) fn new(stream: &'a mut S, predicate: P) -> Self { + Self { + stream, + predicate, + index: 0, + } + } +} + +impl<'a, S, P> Future for PositionFuture<'a, S, P> +where + S: Stream + Unpin, + P: FnMut(S::Item) -> bool, +{ + type Output = Option; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let next = futures_core::ready!(Pin::new(&mut self.stream).poll_next(cx)); + + match next { + Some(v) => { + if (&mut self.predicate)(v) { + Poll::Ready(Some(self.index)) + } else { + cx.waker().wake_by_ref(); + self.index += 1; + Poll::Pending + } + } + None => Poll::Ready(None), + } + } +} diff --git a/src/stream/stream/scan.rs b/src/stream/stream/scan.rs index 78975161..385edf8e 100644 --- a/src/stream/stream/scan.rs +++ b/src/stream/stream/scan.rs @@ -1,13 +1,24 @@ use std::pin::Pin; +use pin_project_lite::pin_project; + use crate::stream::Stream; use crate::task::{Context, Poll}; -/// A stream to maintain state while polling another stream. -#[derive(Debug)] -pub struct Scan { - stream: S, - state_f: (St, F), +pin_project! { + /// A stream to maintain state while polling another stream. + /// + /// This `struct` is created by the [`scan`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`scan`]: trait.Stream.html#method.scan + /// [`Stream`]: trait.Stream.html + #[derive(Debug)] + pub struct Scan { + #[pin] + stream: S, + state_f: (St, F), + } } impl Scan { @@ -17,13 +28,8 @@ impl Scan { state_f: (initial_state, f), } } - - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(state_f: (St, F)); } -impl Unpin for Scan {} - impl Stream for Scan where S: Stream, @@ -31,11 +37,12 @@ where { type Item = B; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let poll_result = self.as_mut().stream().poll_next(cx); + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + let poll_result = this.stream.as_mut().poll_next(cx); poll_result.map(|item| { item.and_then(|item| { - let (state, f) = self.as_mut().state_f(); + let (state, f) = this.state_f; f(state, item) }) }) diff --git a/src/stream/stream/skip.rs b/src/stream/stream/skip.rs index 8a2d966d..bcff50d6 100644 --- a/src/stream/stream/skip.rs +++ b/src/stream/stream/skip.rs @@ -1,21 +1,29 @@ use std::pin::Pin; use std::task::{Context, Poll}; +use pin_project_lite::pin_project; + use crate::stream::Stream; -/// A stream to skip first n elements of another stream. -#[derive(Debug)] -pub struct Skip { - stream: S, - n: usize, +pin_project! { + /// A stream to skip first n elements of another stream. + /// + /// This `struct` is created by the [`skip`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`skip`]: trait.Stream.html#method.skip + /// [`Stream`]: trait.Stream.html + #[derive(Debug)] + pub struct Skip { + #[pin] + stream: S, + n: usize, + } } impl Skip { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(n: usize); - pub(crate) fn new(stream: S, n: usize) -> Self { - Skip { stream, n } + Self { stream, n } } } @@ -25,14 +33,15 @@ where { type Item = S::Item; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); loop { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + let next = futures_core::ready!(this.stream.as_mut().poll_next(cx)); match next { - Some(v) => match self.n { + Some(v) => match *this.n { 0 => return Poll::Ready(Some(v)), - _ => *self.as_mut().n() -= 1, + _ => *this.n -= 1, }, None => return Poll::Ready(None), } diff --git a/src/stream/stream/skip_while.rs b/src/stream/stream/skip_while.rs index b1a8d9ea..23347132 100644 --- a/src/stream/stream/skip_while.rs +++ b/src/stream/stream/skip_while.rs @@ -1,46 +1,52 @@ -use std::marker::PhantomData; use std::pin::Pin; +use pin_project_lite::pin_project; + use crate::stream::Stream; use crate::task::{Context, Poll}; -/// A stream to skip elements of another stream based on a predicate. -#[derive(Debug)] -pub struct SkipWhile { - stream: S, - predicate: Option

, - __t: PhantomData, +pin_project! { + /// A stream to skip elements of another stream based on a predicate. + /// + /// This `struct` is created by the [`skip_while`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`skip_while`]: trait.Stream.html#method.skip_while + /// [`Stream`]: trait.Stream.html + #[derive(Debug)] + pub struct SkipWhile { + #[pin] + stream: S, + predicate: Option

, + } } -impl SkipWhile { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(predicate: Option

); - +impl SkipWhile { pub(crate) fn new(stream: S, predicate: P) -> Self { - SkipWhile { + Self { stream, predicate: Some(predicate), - __t: PhantomData, } } } -impl Stream for SkipWhile +impl Stream for SkipWhile where S: Stream, P: FnMut(&S::Item) -> bool, { type Item = S::Item; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); loop { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + let next = futures_core::ready!(this.stream.as_mut().poll_next(cx)); match next { - Some(v) => match self.as_mut().predicate() { + Some(v) => match this.predicate { Some(p) => { if !p(&v) { - *self.as_mut().predicate() = None; + *this.predicate = None; return Poll::Ready(Some(v)); } } diff --git a/src/stream/stream/step_by.rs b/src/stream/stream/step_by.rs index f84feecd..2149cdad 100644 --- a/src/stream/stream/step_by.rs +++ b/src/stream/stream/step_by.rs @@ -1,23 +1,30 @@ use std::pin::Pin; +use pin_project_lite::pin_project; + use crate::stream::Stream; use crate::task::{Context, Poll}; -/// A stream that steps a given amount of elements of another stream. -#[derive(Debug)] -pub struct StepBy { - stream: S, - step: usize, - i: usize, +pin_project! { + /// A stream that steps a given amount of elements of another stream. + /// + /// This `struct` is created by the [`step_by`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`step_by`]: trait.Stream.html#method.step_by + /// [`Stream`]: trait.Stream.html + #[derive(Debug)] + pub struct StepBy { + #[pin] + stream: S, + step: usize, + i: usize, + } } impl StepBy { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(step: usize); - pin_utils::unsafe_unpinned!(i: usize); - pub(crate) fn new(stream: S, step: usize) -> Self { - StepBy { + Self { stream, step: step.checked_sub(1).unwrap(), i: 0, @@ -31,17 +38,18 @@ where { type Item = S::Item; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); loop { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + let next = futures_core::ready!(this.stream.as_mut().poll_next(cx)); match next { - Some(v) => match self.i { + Some(v) => match this.i { 0 => { - *self.as_mut().i() = self.step; + *this.i = *this.step; return Poll::Ready(Some(v)); } - _ => *self.as_mut().i() -= 1, + _ => *this.i -= 1, }, None => return Poll::Ready(None), } diff --git a/src/stream/stream/take.rs b/src/stream/stream/take.rs index 81d48d23..8c852276 100644 --- a/src/stream/stream/take.rs +++ b/src/stream/stream/take.rs @@ -1,33 +1,47 @@ use std::pin::Pin; +use pin_project_lite::pin_project; + use crate::stream::Stream; use crate::task::{Context, Poll}; -/// A stream that yields the first `n` items of another stream. -#[derive(Clone, Debug)] -pub struct Take { - pub(crate) stream: S, - pub(crate) remaining: usize, +pin_project! { + /// A stream that yields the first `n` items of another stream. + /// + /// This `struct` is created by the [`take`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`take`]: trait.Stream.html#method.take + /// [`Stream`]: trait.Stream.html + #[derive(Clone, Debug)] + pub struct Take { + #[pin] + pub(crate) stream: S, + pub(crate) remaining: usize, + } } -impl Unpin for Take {} - -impl Take { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(remaining: usize); +impl Take { + pub(super) fn new(stream: S, remaining: usize) -> Self { + Self { + stream, + remaining, + } + } } impl Stream for Take { type Item = S::Item; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if self.remaining == 0 { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + if *this.remaining == 0 { Poll::Ready(None) } else { - let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + let next = futures_core::ready!(this.stream.poll_next(cx)); match next { - Some(_) => *self.as_mut().remaining() -= 1, - None => *self.as_mut().remaining() = 0, + Some(_) => *this.remaining -= 1, + None => *this.remaining = 0, } Poll::Ready(next) } diff --git a/src/stream/stream/take_while.rs b/src/stream/stream/take_while.rs new file mode 100644 index 00000000..2ba8490e --- /dev/null +++ b/src/stream/stream/take_while.rs @@ -0,0 +1,55 @@ +use std::pin::Pin; + +use pin_project_lite::pin_project; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + /// A stream that yields elements based on a predicate. + /// + /// This `struct` is created by the [`take_while`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`take_while`]: trait.Stream.html#method.take_while + /// [`Stream`]: trait.Stream.html + #[derive(Debug)] + pub struct TakeWhile { + #[pin] + stream: S, + predicate: P, + } +} + +impl TakeWhile { + pub(super) fn new(stream: S, predicate: P) -> Self { + Self { + stream, + predicate, + } + } +} + +impl Stream for TakeWhile +where + S: Stream, + P: FnMut(&S::Item) -> bool, +{ + type Item = S::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + let next = futures_core::ready!(this.stream.poll_next(cx)); + + match next { + Some(v) => { + if (this.predicate)(&v) { + Poll::Ready(Some(v)) + } else { + Poll::Ready(None) + } + } + None => Poll::Ready(None), + } + } +} diff --git a/src/stream/stream/throttle.rs b/src/stream/stream/throttle.rs new file mode 100644 index 00000000..b2480bbd --- /dev/null +++ b/src/stream/stream/throttle.rs @@ -0,0 +1,70 @@ +use std::future::Future; +use std::pin::Pin; +use std::time::{Duration, Instant}; + +use futures_timer::Delay; +use pin_project_lite::pin_project; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + /// A stream that only yields one element once every `duration`. + /// + /// This `struct` is created by the [`throttle`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`throttle`]: trait.Stream.html#method.throttle + /// [`Stream`]: trait.Stream.html + #[doc(hidden)] + #[allow(missing_debug_implementations)] + pub struct Throttle { + #[pin] + stream: S, + duration: Duration, + #[pin] + blocked: bool, + #[pin] + delay: Delay, + } +} + +impl Throttle { + pub(super) fn new(stream: S, duration: Duration) -> Self { + Self { + stream, + duration, + blocked: false, + delay: Delay::new(Duration::default()), + } + } +} + +impl Stream for Throttle { + type Item = S::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + if *this.blocked { + let d = this.delay.as_mut(); + if d.poll(cx).is_ready() { + *this.blocked = false; + } else { + return Poll::Pending; + } + } + + match this.stream.poll_next(cx) { + Poll::Pending => { + cx.waker().wake_by_ref(); // Continue driving even though emitting Pending + Poll::Pending + } + Poll::Ready(None) => Poll::Ready(None), + Poll::Ready(Some(v)) => { + *this.blocked = true; + this.delay.reset(Instant::now() + *this.duration); + Poll::Ready(Some(v)) + } + } + } +} diff --git a/src/stream/stream/timeout.rs b/src/stream/stream/timeout.rs new file mode 100644 index 00000000..f580360d --- /dev/null +++ b/src/stream/stream/timeout.rs @@ -0,0 +1,63 @@ +use std::error::Error; +use std::fmt; +use std::pin::Pin; +use std::time::Duration; +use std::future::Future; + +use futures_timer::Delay; +use pin_project_lite::pin_project; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + /// A stream with timeout time set + #[derive(Debug)] + pub struct Timeout { + #[pin] + stream: S, + #[pin] + delay: Delay, + } +} + +impl Timeout { + pub(crate) fn new(stream: S, dur: Duration) -> Self { + let delay = Delay::new(dur); + + Self { stream, delay } + } +} + +impl Stream for Timeout { + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + + match this.stream.poll_next(cx) { + Poll::Ready(Some(v)) => Poll::Ready(Some(Ok(v))), + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => match this.delay.poll(cx) { + Poll::Ready(_) => Poll::Ready(Some(Err(TimeoutError { _private: () }))), + Poll::Pending => Poll::Pending, + }, + } + } +} + +/// An error returned when a stream times out. +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[cfg(any(feature = "unstable", feature = "docs"))] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct TimeoutError { + _private: (), +} + +impl Error for TimeoutError {} + +impl fmt::Display for TimeoutError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + "stream has timed out".fmt(f) + } +} diff --git a/src/stream/stream/try_fold.rs b/src/stream/stream/try_fold.rs new file mode 100644 index 00000000..3b92d95a --- /dev/null +++ b/src/stream/stream/try_fold.rs @@ -0,0 +1,52 @@ +use std::pin::Pin; + +use crate::future::Future; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub struct TryFoldFuture<'a, S, F, T> { + stream: &'a mut S, + f: F, + acc: Option, +} + +impl<'a, S, F, T> Unpin for TryFoldFuture<'a, S, F, T> {} + +impl<'a, S, F, T> TryFoldFuture<'a, S, F, T> { + pub(super) fn new(stream: &'a mut S, init: T, f: F) -> Self { + Self { + stream, + f, + acc: Some(init), + } + } +} + +impl<'a, S, F, T, E> Future for TryFoldFuture<'a, S, F, T> +where + S: Stream + Unpin, + F: FnMut(T, S::Item) -> Result, +{ + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + let next = futures_core::ready!(Pin::new(&mut self.stream).poll_next(cx)); + + match next { + Some(v) => { + let old = self.acc.take().unwrap(); + let new = (&mut self.f)(old, v); + + match new { + Ok(o) => self.acc = Some(o), + Err(e) => return Poll::Ready(Err(e)), + } + } + None => return Poll::Ready(Ok(self.acc.take().unwrap())), + } + } + } +} diff --git a/src/stream/stream/try_for_each.rs b/src/stream/stream/try_for_each.rs index ae3d5ea5..86f1674a 100644 --- a/src/stream/stream/try_for_each.rs +++ b/src/stream/stream/try_for_each.rs @@ -1,49 +1,39 @@ -use std::marker::PhantomData; +use std::future::Future; use std::pin::Pin; -use crate::future::Future; use crate::stream::Stream; use crate::task::{Context, Poll}; #[doc(hidden)] #[allow(missing_debug_implementations)] -pub struct TryForEeachFuture { - stream: S, +pub struct TryForEachFuture<'a, S, F> { + stream: &'a mut S, f: F, - __from: PhantomData, - __to: PhantomData, } -impl TryForEeachFuture { - pin_utils::unsafe_pinned!(stream: S); - pin_utils::unsafe_unpinned!(f: F); +impl<'a, S, F> Unpin for TryForEachFuture<'a, S, F> {} - pub(crate) fn new(stream: S, f: F) -> Self { - TryForEeachFuture { - stream, - f, - __from: PhantomData, - __to: PhantomData, - } +impl<'a, S, F> TryForEachFuture<'a, S, F> { + pub(crate) fn new(stream: &'a mut S, f: F) -> Self { + Self { stream, f } } } -impl Future for TryForEeachFuture +impl<'a, S, F, E> Future for TryForEachFuture<'a, S, F> where - S: Stream, - S::Item: std::fmt::Debug, + S: Stream + Unpin, F: FnMut(S::Item) -> Result<(), E>, { type Output = Result<(), E>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { - let item = futures_core::ready!(self.as_mut().stream().poll_next(cx)); + let item = futures_core::ready!(Pin::new(&mut self.stream).poll_next(cx)); match item { None => return Poll::Ready(Ok(())), Some(v) => { - let res = (self.as_mut().f())(v); + let res = (&mut self.f)(v); if let Err(e) = res { return Poll::Ready(Err(e)); } diff --git a/src/stream/stream/unzip.rs b/src/stream/stream/unzip.rs new file mode 100644 index 00000000..e0832ff7 --- /dev/null +++ b/src/stream/stream/unzip.rs @@ -0,0 +1,57 @@ +use std::future::Future; +use std::pin::Pin; + +use pin_project_lite::pin_project; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +pin_project! { + #[derive(Clone, Debug)] + #[cfg(all(feature = "default", feature = "unstable"))] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + pub struct UnzipFuture { + #[pin] + stream: S, + res: Option<(FromA, FromB)>, + } +} + +impl UnzipFuture +where + FromA: Default, + FromB: Default, +{ + pub(super) fn new(stream: S) -> Self { + UnzipFuture { + stream, + res: Some((FromA::default(), FromB::default())), + } + } +} + +impl Future for UnzipFuture +where + S: Stream, + FromA: Default + Extend, + FromB: Default + Extend, +{ + type Output = (FromA, FromB); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + + loop { + let next = futures_core::ready!(this.stream.as_mut().poll_next(cx)); + + match next { + Some((a, b)) => { + let res = this.res.as_mut().unwrap(); + res.0.extend(Some(a)); + res.1.extend(Some(b)); + } + None => return Poll::Ready(this.res.take().unwrap()), + } + } + } +} diff --git a/src/stream/stream/zip.rs b/src/stream/stream/zip.rs index 4c66aefd..597691b4 100644 --- a/src/stream/stream/zip.rs +++ b/src/stream/stream/zip.rs @@ -1,14 +1,26 @@ use std::fmt; use std::pin::Pin; +use pin_project_lite::pin_project; + use crate::stream::Stream; use crate::task::{Context, Poll}; -/// An iterator that iterates two other iterators simultaneously. -pub struct Zip { - item_slot: Option, - first: A, - second: B, +pin_project! { + /// A stream that takes items from two other streams simultaneously. + /// + /// This `struct` is created by the [`zip`] method on [`Stream`]. See its + /// documentation for more. + /// + /// [`zip`]: trait.Stream.html#method.zip + /// [`Stream`]: trait.Stream.html + pub struct Zip { + item_slot: Option, + #[pin] + first: A, + #[pin] + second: B, + } } impl fmt::Debug for Zip { @@ -20,35 +32,30 @@ impl fmt::Debug for Zip { } } -impl Unpin for Zip {} - impl Zip { pub(crate) fn new(first: A, second: B) -> Self { - Zip { + Self { item_slot: None, first, second, } } - - pin_utils::unsafe_unpinned!(item_slot: Option); - pin_utils::unsafe_pinned!(first: A); - pin_utils::unsafe_pinned!(second: B); } impl Stream for Zip { type Item = (A::Item, B::Item); - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if self.as_mut().item_slot().is_none() { - match self.as_mut().first().poll_next(cx) { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + if this.item_slot.is_none() { + match this.first.poll_next(cx) { Poll::Pending => return Poll::Pending, Poll::Ready(None) => return Poll::Ready(None), - Poll::Ready(Some(item)) => *self.as_mut().item_slot() = Some(item), + Poll::Ready(Some(item)) => *this.item_slot = Some(item), } } - let second_item = futures_core::ready!(self.as_mut().second().poll_next(cx)); - let first_item = self.as_mut().item_slot().take().unwrap(); + let second_item = futures_core::ready!(this.second.poll_next(cx)); + let first_item = this.item_slot.take().unwrap(); Poll::Ready(second_item.map(|second_item| (first_item, second_item))) } } diff --git a/src/stream/successors.rs b/src/stream/successors.rs new file mode 100644 index 00000000..4421564e --- /dev/null +++ b/src/stream/successors.rs @@ -0,0 +1,77 @@ +use std::mem; +use std::pin::Pin; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +use pin_project_lite::pin_project; + +/// Creates a new stream where to produce each new element a closure is called with the previous +/// value. +/// +/// # Examples +/// +/// ``` +/// # fn main() { async_std::task::block_on(async { +/// # +/// use async_std::prelude::*; +/// use async_std::stream; +/// +/// let mut s = stream::successors(Some(22), |&val| Some(val + 1)); +/// +/// assert_eq!(s.next().await, Some(22)); +/// assert_eq!(s.next().await, Some(23)); +/// assert_eq!(s.next().await, Some(24)); +/// assert_eq!(s.next().await, Some(25)); +/// +/// # +/// # }) } +/// ``` +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +pub fn successors(first: Option, succ: F) -> Successors +where + F: FnMut(&T) -> Option, +{ + Successors { succ, slot: first } +} + +pin_project! { + /// A stream that yields elements by calling an async closure with the previous value as an + /// argument + /// + /// This stream is constructed by [`successors`] function + /// + /// [`successors`]: fn.succssors.html + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + #[derive(Debug)] + pub struct Successors + where + F: FnMut(&T) -> Option + { + succ: F, + slot: Option, + } +} + +impl Stream for Successors +where + F: FnMut(&T) -> Option, +{ + type Item = T; + + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + + if this.slot.is_none() { + return Poll::Ready(None); + } + + let mut next = (this.succ)(&this.slot.as_ref().unwrap()); + + // 'swapping' here means 'slot' will hold the next value and next will be th one from the previous iteration + mem::swap(this.slot, &mut next); + Poll::Ready(next) + } +} diff --git a/src/stream/sum.rs b/src/stream/sum.rs new file mode 100644 index 00000000..9607bafa --- /dev/null +++ b/src/stream/sum.rs @@ -0,0 +1,79 @@ +use std::future::Future; +use std::pin::Pin; + +use crate::stream::Stream; + +/// Trait to represent types that can be created by summing up a stream. +/// +/// This trait is used to implement the [`sum`] method on streams. Types which +/// implement the trait can be generated by the [`sum`] method. Like +/// [`FromStream`] this trait should rarely be called directly and instead +/// interacted with through [`Stream::sum`]. +/// +/// [`sum`]: trait.Sum.html#tymethod.sum +/// [`FromStream`]: trait.FromStream.html +/// [`Stream::sum`]: trait.Stream.html#method.sum +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +pub trait Sum: Sized { + /// Method which takes a stream and generates `Self` from the elements by + /// "summing up" the items. + fn sum<'a, S>(stream: S) -> Pin + 'a>> + where + S: Stream + 'a; +} + +use crate::stream::stream::StreamExt; +use core::num::Wrapping; +use core::ops::Add; + +macro_rules! integer_sum { + (@impls $zero: expr, $($a:ty)*) => ($( + impl Sum for $a { + fn sum<'a, S>(stream: S) -> Pin+ 'a>> + where + S: Stream + 'a, + { + Box::pin(async move { stream.fold($zero, Add::add).await } ) + } + } + impl<'a> Sum<&'a $a> for $a { + fn sum<'b, S>(stream: S) -> Pin + 'b>> + where + S: Stream + 'b, + { + Box::pin(async move { stream.fold($zero, Add::add).await } ) + } + } + )*); + ($($a:ty)*) => ( + integer_sum!(@impls 0, $($a)*); + integer_sum!(@impls Wrapping(0), $(Wrapping<$a>)*); + ); +} + +macro_rules! float_sum { + ($($a:ty)*) => ($( + impl Sum for $a { + fn sum<'a, S>(stream: S) -> Pin + 'a>> + where S: Stream + 'a, + { + Box::pin(async move { stream.fold(0.0, |a, b| a + b).await } ) + } + } + impl<'a> Sum<&'a $a> for $a { + fn sum<'b, S>(stream: S) -> Pin + 'b>> + where S: Stream + 'b, + { + Box::pin(async move { stream.fold(0.0, |a, b| a + b).await } ) + } + } + )*); + ($($a:ty)*) => ( + float_sum!(@impls 0.0, $($a)*); + float_sum!(@impls Wrapping(0.0), $(Wrapping<$a>)*); + ); +} + +integer_sum! { i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize } +float_sum! { f32 f64 } diff --git a/src/string/extend.rs b/src/string/extend.rs index 8572cc3c..55bec0c5 100644 --- a/src/string/extend.rs +++ b/src/string/extend.rs @@ -2,65 +2,90 @@ use std::borrow::Cow; use std::pin::Pin; use crate::prelude::*; -use crate::stream::{Extend, IntoStream}; +use crate::stream::{self, IntoStream}; -impl Extend for String { - fn stream_extend<'a, S: IntoStream + 'a>( +impl stream::Extend for String { + fn extend<'a, S: IntoStream + 'a>( &'a mut self, stream: S, ) -> Pin + 'a>> { let stream = stream.into_stream(); - self.reserve(stream.size_hint().0); - Box::pin(stream.for_each(move |c| self.push(c))) + Box::pin(async move { + pin_utils::pin_mut!(stream); + + while let Some(item) = stream.next().await { + self.push(item); + } + }) } } -impl<'b> Extend<&'b char> for String { - fn stream_extend<'a, S: IntoStream + 'a>( +impl<'b> stream::Extend<&'b char> for String { + fn extend<'a, S: IntoStream + 'a>( &'a mut self, - //TODO: Remove the underscore when uncommenting the body of this impl - _stream: S, - ) -> Pin + 'a>> - where - 'b: 'a, - { - //TODO: This can be uncommented when `copied` is added to Stream/StreamExt - //Box::pin(stream.into_stream().copied()) - unimplemented!() + stream: S, + ) -> Pin + 'a>> { + let stream = stream.into_stream(); + + Box::pin(async move { + pin_utils::pin_mut!(stream); + + while let Some(item) = stream.next().await { + self.push(*item); + } + }) } } -impl<'b> Extend<&'b str> for String { - fn stream_extend<'a, S: IntoStream + 'a>( +impl<'b> stream::Extend<&'b str> for String { + fn extend<'a, S: IntoStream + 'a>( &'a mut self, stream: S, - ) -> Pin + 'a>> - where - 'b: 'a, - { - Box::pin(stream.into_stream().for_each(move |s| self.push_str(s))) + ) -> Pin + 'a>> { + let stream = stream.into_stream(); + + Box::pin(async move { + pin_utils::pin_mut!(stream); + + while let Some(item) = stream.next().await { + self.push_str(item); + } + }) } } -impl Extend for String { - fn stream_extend<'a, S: IntoStream + 'a>( +impl stream::Extend for String { + fn extend<'a, S: IntoStream + 'a>( &'a mut self, stream: S, ) -> Pin + 'a>> { - Box::pin(stream.into_stream().for_each(move |s| self.push_str(&s))) + let stream = stream.into_stream(); + + Box::pin(async move { + pin_utils::pin_mut!(stream); + + while let Some(item) = stream.next().await { + self.push_str(&item); + } + }) } } -impl<'b> Extend> for String { - fn stream_extend<'a, S: IntoStream> + 'a>( +impl<'b> stream::Extend> for String { + fn extend<'a, S: IntoStream> + 'a>( &'a mut self, stream: S, - ) -> Pin + 'a>> - where - 'b: 'a, - { - Box::pin(stream.into_stream().for_each(move |s| self.push_str(&s))) + ) -> Pin + 'a>> { + let stream = stream.into_stream(); + + Box::pin(async move { + pin_utils::pin_mut!(stream); + + while let Some(item) = stream.next().await { + self.push_str(&item); + } + }) } } diff --git a/src/string/from_stream.rs b/src/string/from_stream.rs index 276d13a7..375ac371 100644 --- a/src/string/from_stream.rs +++ b/src/string/from_stream.rs @@ -1,23 +1,19 @@ use std::borrow::Cow; use std::pin::Pin; -use crate::stream::{Extend, FromStream, IntoStream}; +use crate::prelude::*; +use crate::stream::{self, FromStream, IntoStream}; impl FromStream for String { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = String::new(); - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } @@ -25,19 +21,14 @@ impl FromStream for String { impl<'b> FromStream<&'b char> for String { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = String::new(); - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } @@ -45,19 +36,14 @@ impl<'b> FromStream<&'b char> for String { impl<'b> FromStream<&'b str> for String { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = String::new(); - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } @@ -65,19 +51,14 @@ impl<'b> FromStream<&'b str> for String { impl FromStream for String { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = String::new(); - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } @@ -85,19 +66,14 @@ impl FromStream for String { impl<'b> FromStream> for String { #[inline] - fn from_stream<'a, S: IntoStream>>( + fn from_stream<'a, S: IntoStream> + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = String::new(); - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } diff --git a/src/sync/barrier.rs b/src/sync/barrier.rs index 43488ee4..2822d546 100644 --- a/src/sync/barrier.rs +++ b/src/sync/barrier.rs @@ -8,7 +8,7 @@ use crate::sync::Mutex; /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # /// use async_std::sync::{Arc, Barrier}; /// use async_std::task; @@ -30,8 +30,8 @@ use crate::sync::Mutex; /// handle.await; /// } /// # }); -/// # } /// ``` +#[cfg(feature = "unstable")] #[cfg_attr(feature = "docs", doc(cfg(unstable)))] #[derive(Debug)] pub struct Barrier { @@ -61,6 +61,7 @@ struct BarrierState { /// let barrier = Barrier::new(1); /// let barrier_wait_result = barrier.wait(); /// ``` +#[cfg(feature = "unstable")] #[cfg_attr(feature = "docs", doc(cfg(unstable)))] #[derive(Debug, Clone)] pub struct BarrierWaitResult(bool); @@ -118,7 +119,7 @@ impl Barrier { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::sync::{Arc, Barrier}; /// use async_std::task; @@ -140,7 +141,6 @@ impl Barrier { /// handle.await; /// } /// # }); - /// # } /// ``` pub async fn wait(&self) -> BarrierWaitResult { let mut lock = self.state.lock().await; @@ -188,7 +188,7 @@ impl BarrierWaitResult { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::sync::Barrier; /// @@ -196,7 +196,6 @@ impl BarrierWaitResult { /// let barrier_wait_result = barrier.wait().await; /// println!("{:?}", barrier_wait_result.is_leader()); /// # }); - /// # } /// ``` pub fn is_leader(&self) -> bool { self.0 @@ -205,9 +204,9 @@ impl BarrierWaitResult { #[cfg(test)] mod test { - use futures_channel::mpsc::unbounded; - use futures_util::sink::SinkExt; - use futures_util::stream::StreamExt; + use futures::channel::mpsc::unbounded; + use futures::sink::SinkExt; + use futures::stream::StreamExt; use crate::sync::{Arc, Barrier}; use crate::task; diff --git a/src/sync/channel.rs b/src/sync/channel.rs new file mode 100644 index 00000000..2647f650 --- /dev/null +++ b/src/sync/channel.rs @@ -0,0 +1,951 @@ +use std::cell::UnsafeCell; +use std::fmt; +use std::future::Future; +use std::isize; +use std::marker::PhantomData; +use std::mem; +use std::pin::Pin; +use std::process; +use std::ptr; +use std::sync::atomic::{self, AtomicUsize, Ordering}; +use std::sync::Arc; +use std::task::{Context, Poll}; + +use crossbeam_utils::Backoff; + +use crate::stream::Stream; +use crate::sync::WakerSet; + +/// Creates a bounded multi-producer multi-consumer channel. +/// +/// This channel has a buffer that can hold at most `cap` messages at a time. +/// +/// Senders and receivers can be cloned. When all senders associated with a channel get dropped, it +/// becomes closed. Receive operations on a closed and empty channel return `None` instead of +/// trying to await a message. +/// +/// # Panics +/// +/// If `cap` is zero, this function will panic. +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use std::time::Duration; +/// +/// use async_std::sync::channel; +/// use async_std::task; +/// +/// let (s, r) = channel(1); +/// +/// // This call returns immediately because there is enough space in the channel. +/// s.send(1).await; +/// +/// task::spawn(async move { +/// // This call will have to wait because the channel is full. +/// // It will be able to complete only after the first message is received. +/// s.send(2).await; +/// }); +/// +/// task::sleep(Duration::from_secs(1)).await; +/// assert_eq!(r.recv().await, Some(1)); +/// assert_eq!(r.recv().await, Some(2)); +/// # +/// # }) +/// ``` +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +pub fn channel(cap: usize) -> (Sender, Receiver) { + let channel = Arc::new(Channel::with_capacity(cap)); + let s = Sender { + channel: channel.clone(), + }; + let r = Receiver { + channel, + opt_key: None, + }; + (s, r) +} + +/// The sending side of a channel. +/// +/// This struct is created by the [`channel`] function. See its +/// documentation for more. +/// +/// [`channel`]: fn.channel.html +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::sync::channel; +/// use async_std::task; +/// +/// let (s1, r) = channel(100); +/// let s2 = s1.clone(); +/// +/// task::spawn(async move { s1.send(1).await }); +/// task::spawn(async move { s2.send(2).await }); +/// +/// let msg1 = r.recv().await.unwrap(); +/// let msg2 = r.recv().await.unwrap(); +/// +/// assert_eq!(msg1 + msg2, 3); +/// # +/// # }) +/// ``` +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +pub struct Sender { + /// The inner channel. + channel: Arc>, +} + +impl Sender { + /// Sends a message into the channel. + /// + /// If the channel is full, this method will wait until there is space in the channel. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use async_std::sync::channel; + /// use async_std::task; + /// + /// let (s, r) = channel(1); + /// + /// task::spawn(async move { + /// s.send(1).await; + /// s.send(2).await; + /// }); + /// + /// assert_eq!(r.recv().await, Some(1)); + /// assert_eq!(r.recv().await, Some(2)); + /// assert_eq!(r.recv().await, None); + /// # + /// # }) + /// ``` + pub async fn send(&self, msg: T) { + struct SendFuture<'a, T> { + channel: &'a Channel, + msg: Option, + opt_key: Option, + } + + impl Unpin for SendFuture<'_, T> {} + + impl Future for SendFuture<'_, T> { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + let msg = self.msg.take().unwrap(); + + // If the current task is in the set, remove it. + if let Some(key) = self.opt_key.take() { + self.channel.send_wakers.remove(key); + } + + // Try sending the message. + match self.channel.try_send(msg) { + Ok(()) => return Poll::Ready(()), + Err(TrySendError::Disconnected(msg)) => { + self.msg = Some(msg); + return Poll::Pending; + } + Err(TrySendError::Full(msg)) => { + self.msg = Some(msg); + + // Insert this send operation. + self.opt_key = Some(self.channel.send_wakers.insert(cx)); + + // If the channel is still full and not disconnected, return. + if self.channel.is_full() && !self.channel.is_disconnected() { + return Poll::Pending; + } + } + } + } + } + } + + impl Drop for SendFuture<'_, T> { + fn drop(&mut self) { + // If the current task is still in the set, that means it is being cancelled now. + // Wake up another task instead. + if let Some(key) = self.opt_key { + self.channel.send_wakers.cancel(key); + } + } + } + + SendFuture { + channel: &self.channel, + msg: Some(msg), + opt_key: None, + } + .await + } + + /// Returns the channel capacity. + /// + /// # Examples + /// + /// ``` + /// use async_std::sync::channel; + /// + /// let (s, _) = channel::(5); + /// assert_eq!(s.capacity(), 5); + /// ``` + pub fn capacity(&self) -> usize { + self.channel.cap + } + + /// Returns `true` if the channel is empty. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use async_std::sync::channel; + /// + /// let (s, r) = channel(1); + /// + /// assert!(s.is_empty()); + /// s.send(0).await; + /// assert!(!s.is_empty()); + /// # + /// # }) + /// ``` + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns `true` if the channel is full. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use async_std::sync::channel; + /// + /// let (s, r) = channel(1); + /// + /// assert!(!s.is_full()); + /// s.send(0).await; + /// assert!(s.is_full()); + /// # + /// # }) + /// ``` + pub fn is_full(&self) -> bool { + self.channel.is_full() + } + + /// Returns the number of messages in the channel. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use async_std::sync::channel; + /// + /// let (s, r) = channel(2); + /// assert_eq!(s.len(), 0); + /// + /// s.send(1).await; + /// s.send(2).await; + /// assert_eq!(s.len(), 2); + /// # + /// # }) + /// ``` + pub fn len(&self) -> usize { + self.channel.len() + } +} + +impl Drop for Sender { + fn drop(&mut self) { + // Decrement the sender count and disconnect the channel if it drops down to zero. + if self.channel.sender_count.fetch_sub(1, Ordering::AcqRel) == 1 { + self.channel.disconnect(); + } + } +} + +impl Clone for Sender { + fn clone(&self) -> Sender { + let count = self.channel.sender_count.fetch_add(1, Ordering::Relaxed); + + // Make sure the count never overflows, even if lots of sender clones are leaked. + if count > isize::MAX as usize { + process::abort(); + } + + Sender { + channel: self.channel.clone(), + } + } +} + +impl fmt::Debug for Sender { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("Sender { .. }") + } +} + +/// The receiving side of a channel. +/// +/// This type receives messages by calling `recv`. But it also implements the [`Stream`] trait, +/// which means it can act as an asynchronous iterator. This struct is created by the [`channel`] +/// function. See its documentation for more. +/// +/// [`channel`]: fn.channel.html +/// [`Stream`]: ../stream/trait.Stream.html +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use std::time::Duration; +/// +/// use async_std::sync::channel; +/// use async_std::task; +/// +/// let (s, r) = channel(100); +/// +/// task::spawn(async move { +/// s.send(1).await; +/// task::sleep(Duration::from_secs(1)).await; +/// s.send(2).await; +/// }); +/// +/// assert_eq!(r.recv().await, Some(1)); // Received immediately. +/// assert_eq!(r.recv().await, Some(2)); // Received after 1 second. +/// # +/// # }) +/// ``` +#[cfg(feature = "unstable")] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +pub struct Receiver { + /// The inner channel. + channel: Arc>, + + /// The key for this receiver in the `channel.stream_wakers` set. + opt_key: Option, +} + +impl Receiver { + /// Receives a message from the channel. + /// + /// If the channel is empty and still has senders, this method will wait until a message is + /// sent into the channel or until all senders get dropped. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use async_std::sync::channel; + /// use async_std::task; + /// + /// let (s, r) = channel(1); + /// + /// task::spawn(async move { + /// s.send(1).await; + /// s.send(2).await; + /// }); + /// + /// assert_eq!(r.recv().await, Some(1)); + /// assert_eq!(r.recv().await, Some(2)); + /// assert_eq!(r.recv().await, None); + /// # + /// # }) + /// ``` + pub async fn recv(&self) -> Option { + struct RecvFuture<'a, T> { + channel: &'a Channel, + opt_key: Option, + } + + impl Future for RecvFuture<'_, T> { + type Output = Option; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + poll_recv( + &self.channel, + &self.channel.recv_wakers, + &mut self.opt_key, + cx, + ) + } + } + + impl Drop for RecvFuture<'_, T> { + fn drop(&mut self) { + // If the current task is still in the set, that means it is being cancelled now. + if let Some(key) = self.opt_key { + self.channel.recv_wakers.cancel(key); + } + } + } + + RecvFuture { + channel: &self.channel, + opt_key: None, + } + .await + } + + /// Returns the channel capacity. + /// + /// # Examples + /// + /// ``` + /// use async_std::sync::channel; + /// + /// let (_, r) = channel::(5); + /// assert_eq!(r.capacity(), 5); + /// ``` + pub fn capacity(&self) -> usize { + self.channel.cap + } + + /// Returns `true` if the channel is empty. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use async_std::sync::channel; + /// + /// let (s, r) = channel(1); + /// + /// assert!(r.is_empty()); + /// s.send(0).await; + /// assert!(!r.is_empty()); + /// # + /// # }) + /// ``` + pub fn is_empty(&self) -> bool { + self.channel.is_empty() + } + + /// Returns `true` if the channel is full. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use async_std::sync::channel; + /// + /// let (s, r) = channel(1); + /// + /// assert!(!r.is_full()); + /// s.send(0).await; + /// assert!(r.is_full()); + /// # + /// # }) + /// ``` + pub fn is_full(&self) -> bool { + self.channel.is_full() + } + + /// Returns the number of messages in the channel. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use async_std::sync::channel; + /// + /// let (s, r) = channel(2); + /// assert_eq!(r.len(), 0); + /// + /// s.send(1).await; + /// s.send(2).await; + /// assert_eq!(r.len(), 2); + /// # + /// # }) + /// ``` + pub fn len(&self) -> usize { + self.channel.len() + } +} + +impl Drop for Receiver { + fn drop(&mut self) { + // If the current task is still in the stream set, that means it is being cancelled now. + if let Some(key) = self.opt_key { + self.channel.stream_wakers.cancel(key); + } + + // Decrement the receiver count and disconnect the channel if it drops down to zero. + if self.channel.receiver_count.fetch_sub(1, Ordering::AcqRel) == 1 { + self.channel.disconnect(); + } + } +} + +impl Clone for Receiver { + fn clone(&self) -> Receiver { + let count = self.channel.receiver_count.fetch_add(1, Ordering::Relaxed); + + // Make sure the count never overflows, even if lots of receiver clones are leaked. + if count > isize::MAX as usize { + process::abort(); + } + + Receiver { + channel: self.channel.clone(), + opt_key: None, + } + } +} + +impl Stream for Receiver { + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = &mut *self; + poll_recv( + &this.channel, + &this.channel.stream_wakers, + &mut this.opt_key, + cx, + ) + } +} + +impl fmt::Debug for Receiver { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.pad("Receiver { .. }") + } +} + +/// Polls a receive operation on a channel. +/// +/// If the receive operation is blocked, the current task will be inserted into `wakers` and its +/// associated key will then be stored in `opt_key`. +fn poll_recv( + channel: &Channel, + wakers: &WakerSet, + opt_key: &mut Option, + cx: &mut Context<'_>, +) -> Poll> { + loop { + // If the current task is in the set, remove it. + if let Some(key) = opt_key.take() { + wakers.remove(key); + } + + // Try receiving a message. + match channel.try_recv() { + Ok(msg) => return Poll::Ready(Some(msg)), + Err(TryRecvError::Disconnected) => return Poll::Ready(None), + Err(TryRecvError::Empty) => { + // Insert this receive operation. + *opt_key = Some(wakers.insert(cx)); + + // If the channel is still empty and not disconnected, return. + if channel.is_empty() && !channel.is_disconnected() { + return Poll::Pending; + } + } + } + } +} + +/// A slot in a channel. +struct Slot { + /// The current stamp. + stamp: AtomicUsize, + + /// The message in this slot. + msg: UnsafeCell, +} + +/// Bounded channel based on a preallocated array. +struct Channel { + /// The head of the channel. + /// + /// This value is a "stamp" consisting of an index into the buffer, a mark bit, and a lap, but + /// packed into a single `usize`. The lower bits represent the index, while the upper bits + /// represent the lap. The mark bit in the head is always zero. + /// + /// Messages are popped from the head of the channel. + head: AtomicUsize, + + /// The tail of the channel. + /// + /// This value is a "stamp" consisting of an index into the buffer, a mark bit, and a lap, but + /// packed into a single `usize`. The lower bits represent the index, while the upper bits + /// represent the lap. The mark bit indicates that the channel is disconnected. + /// + /// Messages are pushed into the tail of the channel. + tail: AtomicUsize, + + /// The buffer holding slots. + buffer: *mut Slot, + + /// The channel capacity. + cap: usize, + + /// A stamp with the value of `{ lap: 1, mark: 0, index: 0 }`. + one_lap: usize, + + /// If this bit is set in the tail, that means either all senders were dropped or all receivers + /// were dropped. + mark_bit: usize, + + /// Send operations waiting while the channel is full. + send_wakers: WakerSet, + + /// Receive operations waiting while the channel is empty and not disconnected. + recv_wakers: WakerSet, + + /// Streams waiting while the channel is empty and not disconnected. + stream_wakers: WakerSet, + + /// The number of currently active `Sender`s. + sender_count: AtomicUsize, + + /// The number of currently active `Receivers`s. + receiver_count: AtomicUsize, + + /// Indicates that dropping a `Channel` may drop values of type `T`. + _marker: PhantomData, +} + +unsafe impl Send for Channel {} +unsafe impl Sync for Channel {} +impl Unpin for Channel {} + +impl Channel { + /// Creates a bounded channel of capacity `cap`. + fn with_capacity(cap: usize) -> Self { + assert!(cap > 0, "capacity must be positive"); + + // Compute constants `mark_bit` and `one_lap`. + let mark_bit = (cap + 1).next_power_of_two(); + let one_lap = mark_bit * 2; + + // Head is initialized to `{ lap: 0, mark: 0, index: 0 }`. + let head = 0; + // Tail is initialized to `{ lap: 0, mark: 0, index: 0 }`. + let tail = 0; + + // Allocate a buffer of `cap` slots. + let buffer = { + let mut v = Vec::>::with_capacity(cap); + let ptr = v.as_mut_ptr(); + mem::forget(v); + ptr + }; + + // Initialize stamps in the slots. + for i in 0..cap { + unsafe { + // Set the stamp to `{ lap: 0, mark: 0, index: i }`. + let slot = buffer.add(i); + ptr::write(&mut (*slot).stamp, AtomicUsize::new(i)); + } + } + + Channel { + buffer, + cap, + one_lap, + mark_bit, + head: AtomicUsize::new(head), + tail: AtomicUsize::new(tail), + send_wakers: WakerSet::new(), + recv_wakers: WakerSet::new(), + stream_wakers: WakerSet::new(), + sender_count: AtomicUsize::new(1), + receiver_count: AtomicUsize::new(1), + _marker: PhantomData, + } + } + + /// Attempts to send a message. + fn try_send(&self, msg: T) -> Result<(), TrySendError> { + let backoff = Backoff::new(); + let mut tail = self.tail.load(Ordering::Relaxed); + + loop { + // Extract mark bit from the tail and unset it. + // + // If the mark bit was set (which means all receivers have been dropped), we will still + // send the message into the channel if there is enough capacity. The message will get + // dropped when the channel is dropped (which means when all senders are also dropped). + let mark_bit = tail & self.mark_bit; + tail ^= mark_bit; + + // Deconstruct the tail. + let index = tail & (self.mark_bit - 1); + let lap = tail & !(self.one_lap - 1); + + // Inspect the corresponding slot. + let slot = unsafe { &*self.buffer.add(index) }; + let stamp = slot.stamp.load(Ordering::Acquire); + + // If the tail and the stamp match, we may attempt to push. + if tail == stamp { + let new_tail = if index + 1 < self.cap { + // Same lap, incremented index. + // Set to `{ lap: lap, mark: 0, index: index + 1 }`. + tail + 1 + } else { + // One lap forward, index wraps around to zero. + // Set to `{ lap: lap.wrapping_add(1), mark: 0, index: 0 }`. + lap.wrapping_add(self.one_lap) + }; + + // Try moving the tail. + match self.tail.compare_exchange_weak( + tail | mark_bit, + new_tail | mark_bit, + Ordering::SeqCst, + Ordering::Relaxed, + ) { + Ok(_) => { + // Write the message into the slot and update the stamp. + unsafe { slot.msg.get().write(msg) }; + let stamp = tail + 1; + slot.stamp.store(stamp, Ordering::Release); + + // Wake a blocked receive operation. + self.recv_wakers.notify_one(); + + // Wake all blocked streams. + self.stream_wakers.notify_all(); + + return Ok(()); + } + Err(t) => { + tail = t; + backoff.spin(); + } + } + } else if stamp.wrapping_add(self.one_lap) == tail + 1 { + atomic::fence(Ordering::SeqCst); + let head = self.head.load(Ordering::Relaxed); + + // If the head lags one lap behind the tail as well... + if head.wrapping_add(self.one_lap) == tail { + // ...then the channel is full. + + // Check if the channel is disconnected. + if mark_bit != 0 { + return Err(TrySendError::Disconnected(msg)); + } else { + return Err(TrySendError::Full(msg)); + } + } + + backoff.spin(); + tail = self.tail.load(Ordering::Relaxed); + } else { + // Snooze because we need to wait for the stamp to get updated. + backoff.snooze(); + tail = self.tail.load(Ordering::Relaxed); + } + } + } + + /// Attempts to receive a message. + fn try_recv(&self) -> Result { + let backoff = Backoff::new(); + let mut head = self.head.load(Ordering::Relaxed); + + loop { + // Deconstruct the head. + let index = head & (self.mark_bit - 1); + let lap = head & !(self.one_lap - 1); + + // Inspect the corresponding slot. + let slot = unsafe { &*self.buffer.add(index) }; + let stamp = slot.stamp.load(Ordering::Acquire); + + // If the the stamp is ahead of the head by 1, we may attempt to pop. + if head + 1 == stamp { + let new = if index + 1 < self.cap { + // Same lap, incremented index. + // Set to `{ lap: lap, mark: 0, index: index + 1 }`. + head + 1 + } else { + // One lap forward, index wraps around to zero. + // Set to `{ lap: lap.wrapping_add(1), mark: 0, index: 0 }`. + lap.wrapping_add(self.one_lap) + }; + + // Try moving the head. + match self.head.compare_exchange_weak( + head, + new, + Ordering::SeqCst, + Ordering::Relaxed, + ) { + Ok(_) => { + // Read the message from the slot and update the stamp. + let msg = unsafe { slot.msg.get().read() }; + let stamp = head.wrapping_add(self.one_lap); + slot.stamp.store(stamp, Ordering::Release); + + // Wake a blocked send operation. + self.send_wakers.notify_one(); + + return Ok(msg); + } + Err(h) => { + head = h; + backoff.spin(); + } + } + } else if stamp == head { + atomic::fence(Ordering::SeqCst); + let tail = self.tail.load(Ordering::Relaxed); + + // If the tail equals the head, that means the channel is empty. + if (tail & !self.mark_bit) == head { + // If the channel is disconnected... + if tail & self.mark_bit != 0 { + return Err(TryRecvError::Disconnected); + } else { + // Otherwise, the receive operation is not ready. + return Err(TryRecvError::Empty); + } + } + + backoff.spin(); + head = self.head.load(Ordering::Relaxed); + } else { + // Snooze because we need to wait for the stamp to get updated. + backoff.snooze(); + head = self.head.load(Ordering::Relaxed); + } + } + } + + /// Returns the current number of messages inside the channel. + fn len(&self) -> usize { + loop { + // Load the tail, then load the head. + let tail = self.tail.load(Ordering::SeqCst); + let head = self.head.load(Ordering::SeqCst); + + // If the tail didn't change, we've got consistent values to work with. + if self.tail.load(Ordering::SeqCst) == tail { + let hix = head & (self.mark_bit - 1); + let tix = tail & (self.mark_bit - 1); + + return if hix < tix { + tix - hix + } else if hix > tix { + self.cap - hix + tix + } else if (tail & !self.mark_bit) == head { + 0 + } else { + self.cap + }; + } + } + } + + /// Returns `true` if the channel is disconnected. + pub fn is_disconnected(&self) -> bool { + self.tail.load(Ordering::SeqCst) & self.mark_bit != 0 + } + + /// Returns `true` if the channel is empty. + fn is_empty(&self) -> bool { + let head = self.head.load(Ordering::SeqCst); + let tail = self.tail.load(Ordering::SeqCst); + + // Is the tail equal to the head? + // + // Note: If the head changes just before we load the tail, that means there was a moment + // when the channel was not empty, so it is safe to just return `false`. + (tail & !self.mark_bit) == head + } + + /// Returns `true` if the channel is full. + fn is_full(&self) -> bool { + let tail = self.tail.load(Ordering::SeqCst); + let head = self.head.load(Ordering::SeqCst); + + // Is the head lagging one lap behind tail? + // + // Note: If the tail changes just before we load the head, that means there was a moment + // when the channel was not full, so it is safe to just return `false`. + head.wrapping_add(self.one_lap) == tail & !self.mark_bit + } + + /// Disconnects the channel and wakes up all blocked operations. + fn disconnect(&self) { + let tail = self.tail.fetch_or(self.mark_bit, Ordering::SeqCst); + + if tail & self.mark_bit == 0 { + // Notify everyone blocked on this channel. + self.send_wakers.notify_all(); + self.recv_wakers.notify_all(); + self.stream_wakers.notify_all(); + } + } +} + +impl Drop for Channel { + fn drop(&mut self) { + // Get the index of the head. + let hix = self.head.load(Ordering::Relaxed) & (self.mark_bit - 1); + + // Loop over all slots that hold a message and drop them. + for i in 0..self.len() { + // Compute the index of the next slot holding a message. + let index = if hix + i < self.cap { + hix + i + } else { + hix + i - self.cap + }; + + unsafe { + self.buffer.add(index).drop_in_place(); + } + } + + // Finally, deallocate the buffer, but don't run any destructors. + unsafe { + Vec::from_raw_parts(self.buffer, 0, self.cap); + } + } +} + +/// An error returned from the `try_send()` method. +enum TrySendError { + /// The channel is full but not disconnected. + Full(T), + + /// The channel is full and disconnected. + Disconnected(T), +} + +/// An error returned from the `try_recv()` method. +enum TryRecvError { + /// The channel is empty but not disconnected. + Empty, + + /// The channel is empty and disconnected. + Disconnected, +} diff --git a/src/sync/mod.rs b/src/sync/mod.rs index df1d71ab..088c520b 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs @@ -4,16 +4,158 @@ //! //! [`std::sync`]: https://doc.rust-lang.org/std/sync/index.html //! +//! ## The need for synchronization +//! +//! async-std's sync primitives are scheduler-aware, making it possible to +//! `.await` their operations - for example the locking of a [`Mutex`]. +//! +//! Conceptually, a Rust program is a series of operations which will +//! be executed on a computer. The timeline of events happening in the +//! program is consistent with the order of the operations in the code. +//! +//! Consider the following code, operating on some global static variables: +//! +//! ``` +//! static mut A: u32 = 0; +//! static mut B: u32 = 0; +//! static mut C: u32 = 0; +//! +//! fn main() { +//! unsafe { +//! A = 3; +//! B = 4; +//! A = A + B; +//! C = B; +//! println!("{} {} {}", A, B, C); +//! C = A; +//! } +//! } +//! ``` +//! +//! It appears as if some variables stored in memory are changed, an addition +//! is performed, result is stored in `A` and the variable `C` is +//! modified twice. +//! +//! When only a single thread is involved, the results are as expected: +//! the line `7 4 4` gets printed. +//! +//! As for what happens behind the scenes, when optimizations are enabled the +//! final generated machine code might look very different from the code: +//! +//! - The first store to `C` might be moved before the store to `A` or `B`, +//! _as if_ we had written `C = 4; A = 3; B = 4`. +//! +//! - Assignment of `A + B` to `A` might be removed, since the sum can be stored +//! in a temporary location until it gets printed, with the global variable +//! never getting updated. +//! +//! - The final result could be determined just by looking at the code +//! at compile time, so [constant folding] might turn the whole +//! block into a simple `println!("7 4 4")`. +//! +//! The compiler is allowed to perform any combination of these +//! optimizations, as long as the final optimized code, when executed, +//! produces the same results as the one without optimizations. +//! +//! Due to the [concurrency] involved in modern computers, assumptions +//! about the program's execution order are often wrong. Access to +//! global variables can lead to nondeterministic results, **even if** +//! compiler optimizations are disabled, and it is **still possible** +//! to introduce synchronization bugs. +//! +//! Note that thanks to Rust's safety guarantees, accessing global (static) +//! variables requires `unsafe` code, assuming we don't use any of the +//! synchronization primitives in this module. +//! +//! [constant folding]: https://en.wikipedia.org/wiki/Constant_folding +//! [concurrency]: https://en.wikipedia.org/wiki/Concurrency_(computer_science) +//! +//! ## Out-of-order execution +//! +//! Instructions can execute in a different order from the one we define, due to +//! various reasons: +//! +//! - The **compiler** reordering instructions: If the compiler can issue an +//! instruction at an earlier point, it will try to do so. For example, it +//! might hoist memory loads at the top of a code block, so that the CPU can +//! start [prefetching] the values from memory. +//! +//! In single-threaded scenarios, this can cause issues when writing +//! signal handlers or certain kinds of low-level code. +//! Use [compiler fences] to prevent this reordering. +//! +//! - A **single processor** executing instructions [out-of-order]: +//! Modern CPUs are capable of [superscalar] execution, +//! i.e., multiple instructions might be executing at the same time, +//! even though the machine code describes a sequential process. +//! +//! This kind of reordering is handled transparently by the CPU. +//! +//! - A **multiprocessor** system executing multiple hardware threads +//! at the same time: In multi-threaded scenarios, you can use two +//! kinds of primitives to deal with synchronization: +//! - [memory fences] to ensure memory accesses are made visible to +//! other CPUs in the right order. +//! - [atomic operations] to ensure simultaneous access to the same +//! memory location doesn't lead to undefined behavior. +//! +//! [prefetching]: https://en.wikipedia.org/wiki/Cache_prefetching +//! [compiler fences]: https://doc.rust-lang.org/std/sync/atomic/fn.compiler_fence.html +//! [out-of-order]: https://en.wikipedia.org/wiki/Out-of-order_execution +//! [superscalar]: https://en.wikipedia.org/wiki/Superscalar_processor +//! [memory fences]: https://doc.rust-lang.org/std/sync/atomic/fn.fence.html +//! [atomic operations]: https://doc.rust-lang.org/std/sync/atomic/index.html +//! +//! ## Higher-level synchronization objects +//! +//! Most of the low-level synchronization primitives are quite error-prone and +//! inconvenient to use, which is why async-std also exposes some +//! higher-level synchronization objects. +//! +//! These abstractions can be built out of lower-level primitives. +//! For efficiency, the sync objects in async-std are usually +//! implemented with help from the scheduler, which is +//! able to reschedule the tasks while they are blocked on acquiring +//! a lock. +//! +//! The following is an overview of the available synchronization +//! objects: +//! +//! - [`Arc`]: Atomically Reference-Counted pointer, which can be used +//! in multithreaded environments to prolong the lifetime of some +//! data until all the threads have finished using it. +//! +//! - [`Barrier`]: Ensures multiple threads will wait for each other +//! to reach a point in the program, before continuing execution all +//! together. +//! +//! - [`channel`]: Multi-producer, multi-consumer queues, used for +//! message-based communication. Can provide a lightweight +//! inter-task synchronisation mechanism, at the cost of some +//! extra memory. +//! +//! - [`Mutex`]: Mutual exclusion mechanism, which ensures that at +//! most one task at a time is able to access some data. +//! +//! - [`RwLock`]: Provides a mutual exclusion mechanism which allows +//! multiple readers at the same time, while allowing only one +//! writer at a time. In some cases, this can be more efficient than +//! a mutex. +//! +//! [`Arc`]: struct.Arc.html +//! [`Barrier`]: struct.Barrier.html +//! [`channel`]: fn.channel.html +//! [`Mutex`]: struct.Mutex.html +//! [`RwLock`]: struct.RwLock.html +//! //! # Examples //! //! Spawn a task that updates an integer protected by a mutex: //! //! ``` -//! # fn main() { async_std::task::block_on(async { +//! # async_std::task::block_on(async { //! # -//! use std::sync::Arc; -//! -//! use async_std::sync::Mutex; +//! use async_std::sync::{Arc, Mutex}; //! use async_std::task; //! //! let m1 = Arc::new(Mutex::new(0)); @@ -26,20 +168,27 @@ //! //! assert_eq!(*m1.lock().await, 1); //! # -//! # }) } +//! # }) //! ``` +#![allow(clippy::needless_doctest_main)] + #[doc(inline)] pub use std::sync::{Arc, Weak}; -#[cfg(any(feature = "unstable", feature = "docs"))] -pub use barrier::{Barrier, BarrierWaitResult}; - pub use mutex::{Mutex, MutexGuard}; pub use rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -#[cfg(any(feature = "unstable", feature = "docs"))] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -mod barrier; mod mutex; mod rwlock; + +cfg_unstable! { + pub use barrier::{Barrier, BarrierWaitResult}; + pub use channel::{channel, Sender, Receiver}; + + mod barrier; + mod channel; +} + +pub(crate) mod waker_set; +pub(crate) use waker_set::WakerSet; diff --git a/src/sync/mutex.rs b/src/sync/mutex.rs index 673eb830..4d2cf251 100644 --- a/src/sync/mutex.rs +++ b/src/sync/mutex.rs @@ -2,18 +2,11 @@ use std::cell::UnsafeCell; use std::fmt; use std::ops::{Deref, DerefMut}; use std::pin::Pin; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::future::Future; -use slab::Slab; - -use crate::future::Future; -use crate::task::{Context, Poll, Waker}; - -/// Set if the mutex is locked. -const LOCK: usize = 1; - -/// Set if there are tasks blocked on the mutex. -const BLOCKED: usize = 1 << 1; +use crate::sync::WakerSet; +use crate::task::{Context, Poll}; /// A mutual exclusion primitive for protecting shared data. /// @@ -24,11 +17,9 @@ const BLOCKED: usize = 1 << 1; /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # -/// use std::sync::Arc; -/// -/// use async_std::sync::Mutex; +/// use async_std::sync::{Arc, Mutex}; /// use async_std::task; /// /// let m = Arc::new(Mutex::new(0)); @@ -46,11 +37,11 @@ const BLOCKED: usize = 1 << 1; /// } /// assert_eq!(*m.lock().await, 10); /// # -/// # }) } +/// # }) /// ``` pub struct Mutex { - state: AtomicUsize, - blocked: std::sync::Mutex>>, + locked: AtomicBool, + wakers: WakerSet, value: UnsafeCell, } @@ -69,8 +60,8 @@ impl Mutex { /// ``` pub fn new(t: T) -> Mutex { Mutex { - state: AtomicUsize::new(0), - blocked: std::sync::Mutex::new(Slab::new()), + locked: AtomicBool::new(false), + wakers: WakerSet::new(), value: UnsafeCell::new(t), } } @@ -82,11 +73,9 @@ impl Mutex { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # - /// use std::sync::Arc; - /// - /// use async_std::sync::Mutex; + /// use async_std::sync::{Arc, Mutex}; /// use async_std::task; /// /// let m1 = Arc::new(Mutex::new(10)); @@ -99,57 +88,35 @@ impl Mutex { /// /// assert_eq!(*m2.lock().await, 20); /// # - /// # }) } + /// # }) /// ``` pub async fn lock(&self) -> MutexGuard<'_, T> { pub struct LockFuture<'a, T> { mutex: &'a Mutex, opt_key: Option, - acquired: bool, } impl<'a, T> Future for LockFuture<'a, T> { type Output = MutexGuard<'a, T>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.mutex.try_lock() { - Some(guard) => { - self.acquired = true; - Poll::Ready(guard) + loop { + // If the current task is in the set, remove it. + if let Some(key) = self.opt_key.take() { + self.mutex.wakers.remove(key); } - None => { - let mut blocked = self.mutex.blocked.lock().unwrap(); - - // Register the current task. - match self.opt_key { - None => { - // Insert a new entry into the list of blocked tasks. - let w = cx.waker().clone(); - let key = blocked.insert(Some(w)); - self.opt_key = Some(key); - if blocked.len() == 1 { - self.mutex.state.fetch_or(BLOCKED, Ordering::Relaxed); - } - } - Some(key) => { - // There is already an entry in the list of blocked tasks. Just - // reset the waker if it was removed. - if blocked[key].is_none() { - let w = cx.waker().clone(); - blocked[key] = Some(w); - } - } - } + // Try acquiring the lock. + match self.mutex.try_lock() { + Some(guard) => return Poll::Ready(guard), + None => { + // Insert this lock operation. + self.opt_key = Some(self.mutex.wakers.insert(cx)); - // Try locking again because it's possible the mutex got unlocked just - // before the current task was registered as a blocked task. - match self.mutex.try_lock() { - Some(guard) => { - self.acquired = true; - Poll::Ready(guard) + // If the mutex is still locked, return. + if self.mutex.locked.load(Ordering::SeqCst) { + return Poll::Pending; } - None => Poll::Pending, } } } @@ -158,22 +125,9 @@ impl Mutex { impl Drop for LockFuture<'_, T> { fn drop(&mut self) { + // If the current task is still in the set, that means it is being cancelled now. if let Some(key) = self.opt_key { - let mut blocked = self.mutex.blocked.lock().unwrap(); - let opt_waker = blocked.remove(key); - - if opt_waker.is_none() && !self.acquired { - // We were awoken but didn't acquire the lock. Wake up another task. - if let Some((_, opt_waker)) = blocked.iter_mut().next() { - if let Some(w) = opt_waker.take() { - w.wake(); - } - } - } - - if blocked.is_empty() { - self.mutex.state.fetch_and(!BLOCKED, Ordering::Relaxed); - } + self.mutex.wakers.cancel(key); } } } @@ -181,7 +135,6 @@ impl Mutex { LockFuture { mutex: self, opt_key: None, - acquired: false, } .await } @@ -196,11 +149,9 @@ impl Mutex { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # - /// use std::sync::Arc; - /// - /// use async_std::sync::Mutex; + /// use async_std::sync::{Arc, Mutex}; /// use async_std::task; /// /// let m1 = Arc::new(Mutex::new(10)); @@ -217,10 +168,11 @@ impl Mutex { /// /// assert_eq!(*m2.lock().await, 20); /// # - /// # }) } + /// # }) /// ``` + #[inline] pub fn try_lock(&self) -> Option> { - if self.state.fetch_or(LOCK, Ordering::Acquire) & LOCK == 0 { + if !self.locked.swap(true, Ordering::SeqCst) { Some(MutexGuard(self)) } else { None @@ -249,7 +201,7 @@ impl Mutex { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::sync::Mutex; /// @@ -257,7 +209,7 @@ impl Mutex { /// *mutex.get_mut() = 10; /// assert_eq!(*mutex.lock().await, 10); /// # - /// # }) } + /// # }) /// ``` pub fn get_mut(&mut self) -> &mut T { unsafe { &mut *self.value.get() } @@ -266,18 +218,15 @@ impl Mutex { impl fmt::Debug for Mutex { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.try_lock() { - None => { - struct LockedPlaceholder; - impl fmt::Debug for LockedPlaceholder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("") - } - } - f.debug_struct("Mutex") - .field("data", &LockedPlaceholder) - .finish() + struct Locked; + impl fmt::Debug for Locked { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("") } + } + + match self.try_lock() { + None => f.debug_struct("Mutex").field("data", &Locked).finish(), Some(guard) => f.debug_struct("Mutex").field("data", &&*guard).finish(), } } @@ -303,19 +252,11 @@ unsafe impl Sync for MutexGuard<'_, T> {} impl Drop for MutexGuard<'_, T> { fn drop(&mut self) { - let state = self.0.state.fetch_and(!LOCK, Ordering::AcqRel); - - // If there are any blocked tasks, wake one of them up. - if state & BLOCKED != 0 { - let mut blocked = self.0.blocked.lock().unwrap(); + // Use `SeqCst` ordering to synchronize with `WakerSet::insert()` and `WakerSet::update()`. + self.0.locked.store(false, Ordering::SeqCst); - if let Some((_, opt_waker)) = blocked.iter_mut().next() { - // If there is no waker in this entry, that means it was already woken. - if let Some(w) = opt_waker.take() { - w.wake(); - } - } - } + // Notify a blocked `lock()` operation if none were notified already. + self.0.wakers.notify_any(); } } diff --git a/src/sync/rwlock.rs b/src/sync/rwlock.rs index 55a29fc4..bc3f6405 100644 --- a/src/sync/rwlock.rs +++ b/src/sync/rwlock.rs @@ -1,25 +1,21 @@ use std::cell::UnsafeCell; use std::fmt; +use std::isize; use std::ops::{Deref, DerefMut}; use std::pin::Pin; +use std::process; +use std::future::Future; use std::sync::atomic::{AtomicUsize, Ordering}; -use slab::Slab; - -use crate::future::Future; -use crate::task::{Context, Poll, Waker}; +use crate::sync::WakerSet; +use crate::task::{Context, Poll}; /// Set if a write lock is held. -const WRITE_LOCK: usize = 1; - -/// Set if there are read operations blocked on the lock. -const BLOCKED_READS: usize = 1 << 1; - -/// Set if there are write operations blocked on the lock. -const BLOCKED_WRITES: usize = 1 << 2; +#[allow(clippy::identity_op)] +const WRITE_LOCK: usize = 1 << 0; /// The value of a single blocked read contributing to the read count. -const ONE_READ: usize = 1 << 3; +const ONE_READ: usize = 1 << 1; /// The bits in which the read count is stored. const READ_COUNT_MASK: usize = !(ONE_READ - 1); @@ -33,7 +29,7 @@ const READ_COUNT_MASK: usize = !(ONE_READ - 1); /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # /// use async_std::sync::RwLock; /// @@ -51,17 +47,17 @@ const READ_COUNT_MASK: usize = !(ONE_READ - 1); /// *w += 1; /// assert_eq!(*w, 6); /// # -/// # }) } +/// # }) /// ``` pub struct RwLock { state: AtomicUsize, - reads: std::sync::Mutex>>, - writes: std::sync::Mutex>>, + read_wakers: WakerSet, + write_wakers: WakerSet, value: UnsafeCell, } unsafe impl Send for RwLock {} -unsafe impl Sync for RwLock {} +unsafe impl Sync for RwLock {} impl RwLock { /// Creates a new reader-writer lock. @@ -76,8 +72,8 @@ impl RwLock { pub fn new(t: T) -> RwLock { RwLock { state: AtomicUsize::new(0), - reads: std::sync::Mutex::new(Slab::new()), - writes: std::sync::Mutex::new(Slab::new()), + read_wakers: WakerSet::new(), + write_wakers: WakerSet::new(), value: UnsafeCell::new(t), } } @@ -89,7 +85,7 @@ impl RwLock { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::sync::RwLock; /// @@ -100,103 +96,59 @@ impl RwLock { /// /// assert!(lock.try_read().is_some()); /// # - /// # }) } + /// # }) /// ``` pub async fn read(&self) -> RwLockReadGuard<'_, T> { - pub struct LockFuture<'a, T> { + pub struct ReadFuture<'a, T> { lock: &'a RwLock, opt_key: Option, - acquired: bool, } - impl<'a, T> Future for LockFuture<'a, T> { + impl<'a, T> Future for ReadFuture<'a, T> { type Output = RwLockReadGuard<'a, T>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.lock.try_read() { - Some(guard) => { - self.acquired = true; - Poll::Ready(guard) + loop { + // If the current task is in the set, remove it. + if let Some(key) = self.opt_key.take() { + self.lock.read_wakers.remove(key); } - None => { - let mut reads = self.lock.reads.lock().unwrap(); - - // Register the current task. - match self.opt_key { - None => { - // Insert a new entry into the list of blocked reads. - let w = cx.waker().clone(); - let key = reads.insert(Some(w)); - self.opt_key = Some(key); - - if reads.len() == 1 { - self.lock.state.fetch_or(BLOCKED_READS, Ordering::Relaxed); - } - } - Some(key) => { - // There is already an entry in the list of blocked reads. Just - // reset the waker if it was removed. - if reads[key].is_none() { - let w = cx.waker().clone(); - reads[key] = Some(w); - } - } - } - // Try locking again because it's possible the lock got unlocked just - // before the current task was registered as a blocked task. - match self.lock.try_read() { - Some(guard) => { - self.acquired = true; - Poll::Ready(guard) + // Try acquiring a read lock. + match self.lock.try_read() { + Some(guard) => return Poll::Ready(guard), + None => { + // Insert this lock operation. + self.opt_key = Some(self.lock.read_wakers.insert(cx)); + + // If the lock is still acquired for writing, return. + if self.lock.state.load(Ordering::SeqCst) & WRITE_LOCK != 0 { + return Poll::Pending; } - None => Poll::Pending, } } } } } - impl Drop for LockFuture<'_, T> { + impl Drop for ReadFuture<'_, T> { fn drop(&mut self) { + // If the current task is still in the set, that means it is being cancelled now. if let Some(key) = self.opt_key { - let mut reads = self.lock.reads.lock().unwrap(); - let opt_waker = reads.remove(key); + self.lock.read_wakers.cancel(key); - if reads.is_empty() { - self.lock.state.fetch_and(!BLOCKED_READS, Ordering::Relaxed); - } - - if opt_waker.is_none() { - // We were awoken. Wake up another blocked read. - if let Some((_, opt_waker)) = reads.iter_mut().next() { - if let Some(w) = opt_waker.take() { - w.wake(); - return; - } - } - drop(reads); - - if !self.acquired { - // We didn't acquire the lock and didn't wake another blocked read. - // Wake a blocked write instead. - let mut writes = self.lock.writes.lock().unwrap(); - if let Some((_, opt_waker)) = writes.iter_mut().next() { - if let Some(w) = opt_waker.take() { - w.wake(); - return; - } - } - } + // If there are no active readers, notify a blocked writer if none were + // notified already. + if self.lock.state.load(Ordering::SeqCst) & READ_COUNT_MASK == 0 { + self.lock.write_wakers.notify_any(); } } } } - LockFuture { + ReadFuture { lock: self, opt_key: None, - acquired: false, } .await } @@ -211,7 +163,7 @@ impl RwLock { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::sync::RwLock; /// @@ -222,10 +174,10 @@ impl RwLock { /// /// assert!(lock.try_read().is_some()); /// # - /// # }) } + /// # }) /// ``` pub fn try_read(&self) -> Option> { - let mut state = self.state.load(Ordering::Acquire); + let mut state = self.state.load(Ordering::SeqCst); loop { // If a write lock is currently held, then a read lock cannot be acquired. @@ -233,12 +185,17 @@ impl RwLock { return None; } + // Make sure the number of readers doesn't overflow. + if state > isize::MAX as usize { + process::abort(); + } + // Increment the number of active reads. match self.state.compare_exchange_weak( state, state + ONE_READ, - Ordering::AcqRel, - Ordering::Acquire, + Ordering::SeqCst, + Ordering::SeqCst, ) { Ok(_) => return Some(RwLockReadGuard(self)), Err(s) => state = s, @@ -253,7 +210,7 @@ impl RwLock { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::sync::RwLock; /// @@ -264,102 +221,56 @@ impl RwLock { /// /// assert!(lock.try_read().is_none()); /// # - /// # }) } + /// # }) /// ``` pub async fn write(&self) -> RwLockWriteGuard<'_, T> { - pub struct LockFuture<'a, T> { + pub struct WriteFuture<'a, T> { lock: &'a RwLock, opt_key: Option, - acquired: bool, } - impl<'a, T> Future for LockFuture<'a, T> { + impl<'a, T> Future for WriteFuture<'a, T> { type Output = RwLockWriteGuard<'a, T>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.lock.try_write() { - Some(guard) => { - self.acquired = true; - Poll::Ready(guard) + loop { + // If the current task is in the set, remove it. + if let Some(key) = self.opt_key.take() { + self.lock.write_wakers.remove(key); } - None => { - let mut writes = self.lock.writes.lock().unwrap(); - - // Register the current task. - match self.opt_key { - None => { - // Insert a new entry into the list of blocked writes. - let w = cx.waker().clone(); - let key = writes.insert(Some(w)); - self.opt_key = Some(key); - - if writes.len() == 1 { - self.lock.state.fetch_or(BLOCKED_WRITES, Ordering::Relaxed); - } - } - Some(key) => { - // There is already an entry in the list of blocked writes. Just - // reset the waker if it was removed. - if writes[key].is_none() { - let w = cx.waker().clone(); - writes[key] = Some(w); - } - } - } - // Try locking again because it's possible the lock got unlocked just - // before the current task was registered as a blocked task. - match self.lock.try_write() { - Some(guard) => { - self.acquired = true; - Poll::Ready(guard) + // Try acquiring a write lock. + match self.lock.try_write() { + Some(guard) => return Poll::Ready(guard), + None => { + // Insert this lock operation. + self.opt_key = Some(self.lock.write_wakers.insert(cx)); + + // If the lock is still acquired for reading or writing, return. + if self.lock.state.load(Ordering::SeqCst) != 0 { + return Poll::Pending; } - None => Poll::Pending, } } } } } - impl Drop for LockFuture<'_, T> { + impl Drop for WriteFuture<'_, T> { fn drop(&mut self) { + // If the current task is still in the set, that means it is being cancelled now. if let Some(key) = self.opt_key { - let mut writes = self.lock.writes.lock().unwrap(); - let opt_waker = writes.remove(key); - - if writes.is_empty() { - self.lock - .state - .fetch_and(!BLOCKED_WRITES, Ordering::Relaxed); - } - - if opt_waker.is_none() && !self.acquired { - // We were awoken but didn't acquire the lock. Wake up another write. - if let Some((_, opt_waker)) = writes.iter_mut().next() { - if let Some(w) = opt_waker.take() { - w.wake(); - return; - } - } - drop(writes); - - // There are no blocked writes. Wake a blocked read instead. - let mut reads = self.lock.reads.lock().unwrap(); - if let Some((_, opt_waker)) = reads.iter_mut().next() { - if let Some(w) = opt_waker.take() { - w.wake(); - return; - } - } + if !self.lock.write_wakers.cancel(key) { + // If no other blocked reader was notified, notify all readers. + self.lock.read_wakers.notify_all(); } } } } - LockFuture { + WriteFuture { lock: self, opt_key: None, - acquired: false, } .await } @@ -374,7 +285,7 @@ impl RwLock { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::sync::RwLock; /// @@ -385,27 +296,13 @@ impl RwLock { /// /// assert!(lock.try_write().is_none()); /// # - /// # }) } + /// # }) /// ``` pub fn try_write(&self) -> Option> { - let mut state = self.state.load(Ordering::Acquire); - - loop { - // If any kind of lock is currently held, then a write lock cannot be acquired. - if state & (WRITE_LOCK | READ_COUNT_MASK) != 0 { - return None; - } - - // Set the write lock. - match self.state.compare_exchange_weak( - state, - state | WRITE_LOCK, - Ordering::AcqRel, - Ordering::Acquire, - ) { - Ok(_) => return Some(RwLockWriteGuard(self)), - Err(s) => state = s, - } + if self.state.compare_and_swap(0, WRITE_LOCK, Ordering::SeqCst) == 0 { + Some(RwLockWriteGuard(self)) + } else { + None } } @@ -431,7 +328,7 @@ impl RwLock { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::sync::RwLock; /// @@ -439,7 +336,7 @@ impl RwLock { /// *lock.get_mut() = 10; /// assert_eq!(*lock.write().await, 10); /// # - /// # }) } + /// # }) /// ``` pub fn get_mut(&mut self) -> &mut T { unsafe { &mut *self.value.get() } @@ -448,18 +345,15 @@ impl RwLock { impl fmt::Debug for RwLock { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.try_read() { - None => { - struct LockedPlaceholder; - impl fmt::Debug for LockedPlaceholder { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("") - } - } - f.debug_struct("RwLock") - .field("data", &LockedPlaceholder) - .finish() + struct Locked; + impl fmt::Debug for Locked { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("") } + } + + match self.try_read() { + None => f.debug_struct("RwLock").field("data", &Locked).finish(), Some(guard) => f.debug_struct("RwLock").field("data", &&*guard).finish(), } } @@ -485,18 +379,11 @@ unsafe impl Sync for RwLockReadGuard<'_, T> {} impl Drop for RwLockReadGuard<'_, T> { fn drop(&mut self) { - let state = self.0.state.fetch_sub(ONE_READ, Ordering::AcqRel); - - // If this was the last read and there are blocked writes, wake one of them up. - if (state & READ_COUNT_MASK) == ONE_READ && state & BLOCKED_WRITES != 0 { - let mut writes = self.0.writes.lock().unwrap(); + let state = self.0.state.fetch_sub(ONE_READ, Ordering::SeqCst); - if let Some((_, opt_waker)) = writes.iter_mut().next() { - // If there is no waker in this entry, that means it was already woken. - if let Some(w) = opt_waker.take() { - w.wake(); - } - } + // If this was the last reader, notify a blocked writer if none were notified already. + if state & READ_COUNT_MASK == ONE_READ { + self.0.write_wakers.notify_any(); } } } @@ -529,25 +416,13 @@ unsafe impl Sync for RwLockWriteGuard<'_, T> {} impl Drop for RwLockWriteGuard<'_, T> { fn drop(&mut self) { - let state = self.0.state.fetch_and(!WRITE_LOCK, Ordering::AcqRel); - - let mut guard = None; - - // Check if there are any blocked reads or writes. - if state & BLOCKED_READS != 0 { - guard = Some(self.0.reads.lock().unwrap()); - } else if state & BLOCKED_WRITES != 0 { - guard = Some(self.0.writes.lock().unwrap()); - } + self.0.state.store(0, Ordering::SeqCst); - // Wake up a single blocked task. - if let Some(mut guard) = guard { - if let Some((_, opt_waker)) = guard.iter_mut().next() { - // If there is no waker in this entry, that means it was already woken. - if let Some(w) = opt_waker.take() { - w.wake(); - } - } + // Notify all blocked readers. + if !self.0.read_wakers.notify_all() { + // If there were no blocked readers, notify a blocked writer if none were notified + // already. + self.0.write_wakers.notify_any(); } } } diff --git a/src/sync/waker_set.rs b/src/sync/waker_set.rs new file mode 100644 index 00000000..7e897af1 --- /dev/null +++ b/src/sync/waker_set.rs @@ -0,0 +1,238 @@ +//! A common utility for building synchronization primitives. +//! +//! When an async operation is blocked, it needs to register itself somewhere so that it can be +//! notified later on. The `WakerSet` type helps with keeping track of such async operations and +//! notifying them when they may make progress. + +use std::cell::UnsafeCell; +use std::ops::{Deref, DerefMut}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::task::{Context, Waker}; + +use crossbeam_utils::Backoff; +use slab::Slab; + +/// Set when the entry list is locked. +#[allow(clippy::identity_op)] +const LOCKED: usize = 1 << 0; + +/// Set when there is at least one entry that has already been notified. +const NOTIFIED: usize = 1 << 1; + +/// Set when there is at least one notifiable entry. +const NOTIFIABLE: usize = 1 << 2; + +/// Inner representation of `WakerSet`. +struct Inner { + /// A list of entries in the set. + /// + /// Each entry has an optional waker associated with the task that is executing the operation. + /// If the waker is set to `None`, that means the task has been woken up but hasn't removed + /// itself from the `WakerSet` yet. + /// + /// The key of each entry is its index in the `Slab`. + entries: Slab>, + + /// The number of notifiable entries. + notifiable: usize, +} + +/// A set holding wakers. +pub struct WakerSet { + /// Holds three bits: `LOCKED`, `NOTIFY_ONE`, and `NOTIFY_ALL`. + flag: AtomicUsize, + + /// A set holding wakers. + inner: UnsafeCell, +} + +impl WakerSet { + /// Creates a new `WakerSet`. + #[inline] + pub fn new() -> WakerSet { + WakerSet { + flag: AtomicUsize::new(0), + inner: UnsafeCell::new(Inner { + entries: Slab::new(), + notifiable: 0, + }), + } + } + + /// Inserts a waker for a blocked operation and returns a key associated with it. + #[cold] + pub fn insert(&self, cx: &Context<'_>) -> usize { + let w = cx.waker().clone(); + let mut inner = self.lock(); + + let key = inner.entries.insert(Some(w)); + inner.notifiable += 1; + key + } + + /// Removes the waker of an operation. + #[cold] + pub fn remove(&self, key: usize) { + let mut inner = self.lock(); + + if inner.entries.remove(key).is_some() { + inner.notifiable -= 1; + } + } + + /// Removes the waker of a cancelled operation. + /// + /// Returns `true` if another blocked operation from the set was notified. + #[cold] + pub fn cancel(&self, key: usize) -> bool { + let mut inner = self.lock(); + + match inner.entries.remove(key) { + Some(_) => inner.notifiable -= 1, + None => { + // The operation was cancelled and notified so notify another operation instead. + for (_, opt_waker) in inner.entries.iter_mut() { + // If there is no waker in this entry, that means it was already woken. + if let Some(w) = opt_waker.take() { + w.wake(); + inner.notifiable -= 1; + return true; + } + } + } + } + + false + } + + /// Notifies a blocked operation if none have been notified already. + /// + /// Returns `true` if an operation was notified. + #[inline] + pub fn notify_any(&self) -> bool { + // Use `SeqCst` ordering to synchronize with `Lock::drop()`. + let flag = self.flag.load(Ordering::SeqCst); + + if flag & NOTIFIED == 0 && flag & NOTIFIABLE != 0 { + self.notify(Notify::Any) + } else { + false + } + } + + /// Notifies one additional blocked operation. + /// + /// Returns `true` if an operation was notified. + #[inline] + #[cfg(feature = "unstable")] + pub fn notify_one(&self) -> bool { + // Use `SeqCst` ordering to synchronize with `Lock::drop()`. + if self.flag.load(Ordering::SeqCst) & NOTIFIABLE != 0 { + self.notify(Notify::One) + } else { + false + } + } + + /// Notifies all blocked operations. + /// + /// Returns `true` if at least one operation was notified. + #[inline] + pub fn notify_all(&self) -> bool { + // Use `SeqCst` ordering to synchronize with `Lock::drop()`. + if self.flag.load(Ordering::SeqCst) & NOTIFIABLE != 0 { + self.notify(Notify::All) + } else { + false + } + } + + /// Notifies blocked operations, either one or all of them. + /// + /// Returns `true` if at least one operation was notified. + #[cold] + fn notify(&self, n: Notify) -> bool { + let mut inner = &mut *self.lock(); + let mut notified = false; + + for (_, opt_waker) in inner.entries.iter_mut() { + // If there is no waker in this entry, that means it was already woken. + if let Some(w) = opt_waker.take() { + w.wake(); + inner.notifiable -= 1; + notified = true; + + if n == Notify::One { + break; + } + } + + if n == Notify::Any { + break; + } + } + + notified + } + + /// Locks the list of entries. + fn lock(&self) -> Lock<'_> { + let backoff = Backoff::new(); + while self.flag.fetch_or(LOCKED, Ordering::Acquire) & LOCKED != 0 { + backoff.snooze(); + } + Lock { waker_set: self } + } +} + +/// A guard holding a `WakerSet` locked. +struct Lock<'a> { + waker_set: &'a WakerSet, +} + +impl Drop for Lock<'_> { + #[inline] + fn drop(&mut self) { + let mut flag = 0; + + // Set the `NOTIFIED` flag if there is at least one notified entry. + if self.entries.len() - self.notifiable > 0 { + flag |= NOTIFIED; + } + + // Set the `NOTIFIABLE` flag if there is at least one notifiable entry. + if self.notifiable > 0 { + flag |= NOTIFIABLE; + } + + // Use `SeqCst` ordering to synchronize with `WakerSet::lock_to_notify()`. + self.waker_set.flag.store(flag, Ordering::SeqCst); + } +} + +impl Deref for Lock<'_> { + type Target = Inner; + + #[inline] + fn deref(&self) -> &Inner { + unsafe { &*self.waker_set.inner.get() } + } +} + +impl DerefMut for Lock<'_> { + #[inline] + fn deref_mut(&mut self) -> &mut Inner { + unsafe { &mut *self.waker_set.inner.get() } + } +} + +/// Notification strategy. +#[derive(Clone, Copy, Eq, PartialEq)] +enum Notify { + /// Make sure at least one entry is notified. + Any, + /// Notify one additional entry. + One, + /// Notify all entries. + All, +} diff --git a/src/task/block_on.rs b/src/task/block_on.rs index 11504002..80259c57 100644 --- a/src/task/block_on.rs +++ b/src/task/block_on.rs @@ -1,27 +1,24 @@ -use std::cell::UnsafeCell; +use std::cell::Cell; +use std::future::Future; use std::mem::{self, ManuallyDrop}; -use std::panic::{self, AssertUnwindSafe, UnwindSafe}; -use std::pin::Pin; use std::sync::Arc; use std::task::{RawWaker, RawWakerVTable}; -use std::thread::{self, Thread}; - -use super::task; -use super::task_local; -use super::worker; -use crate::future::Future; -use crate::task::{Context, Poll, Waker}; +use std::thread; +use crossbeam_utils::sync::Parker; use kv_log_macro::trace; +use log::log_enabled; + +use crate::task::{Context, Poll, Task, Waker}; /// Spawns a task and blocks the current thread on its result. /// /// Calling this function is similar to [spawning] a thread and immediately [joining] it, except an /// asynchronous task will be spawned. /// -/// See also: [`task::blocking`]. +/// See also: [`task::spawn_blocking`]. /// -/// [`task::blocking`]: fn.blocking.html +/// [`task::spawn_blocking`]: fn.spawn_blocking.html /// /// [spawning]: https://doc.rust-lang.org/std/thread/fn.spawn.html /// [joining]: https://doc.rust-lang.org/std/thread/struct.JoinHandle.html#method.join @@ -41,127 +38,109 @@ pub fn block_on(future: F) -> T where F: Future, { - unsafe { - // A place on the stack where the result will be stored. - let out = &mut UnsafeCell::new(None); - - // Wrap the future into one that stores the result into `out`. - let future = { - let out = out.get(); - - async move { - let future = CatchUnwindFuture { - future: AssertUnwindSafe(future), - }; - *out = Some(future.await); - } - }; - - // Create a tag for the task. - let tag = task::Tag::new(None); - - // Log this `block_on` operation. - let child_id = tag.task_id().as_u64(); - let parent_id = worker::get_task(|t| t.id().as_u64()).unwrap_or(0); + // Create a new task handle. + let task = Task::new(None); + // Log this `block_on` operation. + if log_enabled!(log::Level::Trace) { trace!("block_on", { - parent_id: parent_id, - child_id: child_id, + task_id: task.id().0, + parent_task_id: Task::get_current(|t| t.id().0).unwrap_or(0), }); - - // Wrap the future into one that drops task-local variables on exit. - let future = task_local::add_finalizer(future); - - let future = async move { - future.await; - trace!("block_on completed", { - parent_id: parent_id, - child_id: child_id, - }); - }; - - // Pin the future onto the stack. - pin_utils::pin_mut!(future); - - // Transmute the future into one that is futurestatic. - let future = mem::transmute::< - Pin<&'_ mut dyn Future>, - Pin<&'static mut dyn Future>, - >(future); - - // Block on the future and and wait for it to complete. - worker::set_tag(&tag, || block(future)); - - // Take out the result. - match (*out.get()).take().unwrap() { - Ok(v) => v, - Err(err) => panic::resume_unwind(err), - } } -} -struct CatchUnwindFuture { - future: F, -} + let future = async move { + // Drop task-locals on exit. + defer! { + Task::get_current(|t| unsafe { t.drop_locals() }); + } -impl CatchUnwindFuture { - pin_utils::unsafe_pinned!(future: F); -} + // Log completion on exit. + defer! { + if log_enabled!(log::Level::Trace) { + Task::get_current(|t| { + trace!("completed", { + task_id: t.id().0, + }); + }); + } + } -impl Future for CatchUnwindFuture { - type Output = thread::Result; + future.await + }; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - panic::catch_unwind(AssertUnwindSafe(|| self.future().poll(cx)))?.map(Ok) - } + // Run the future as a task. + unsafe { Task::set_current(&task, || run(future)) } } -fn block(f: F) -> T +/// Blocks the current thread on a future's result. +fn run(future: F) -> T where F: Future, { thread_local! { - static ARC_THREAD: Arc = Arc::new(thread::current()); + // May hold a pre-allocated parker that can be reused for efficiency. + // + // Note that each invocation of `block` needs its own parker. In particular, if `block` + // recursively calls itself, we must make sure that each recursive call uses a distinct + // parker instance. + static CACHE: Cell>> = Cell::new(None); } - pin_utils::pin_mut!(f); + // Virtual table for wakers based on `Arc`. + static VTABLE: RawWakerVTable = { + unsafe fn clone_raw(ptr: *const ()) -> RawWaker { + let arc = ManuallyDrop::new(Arc::from_raw(ptr as *const Parker)); + #[allow(clippy::redundant_clone)] + mem::forget(arc.clone()); + RawWaker::new(ptr, &VTABLE) + } - ARC_THREAD.with(|arc_thread: &Arc| { - let ptr = (&**arc_thread as *const Thread) as *const (); - let vt = vtable(); + unsafe fn wake_raw(ptr: *const ()) { + let arc = Arc::from_raw(ptr as *const Parker); + arc.unparker().unpark(); + } - let waker = unsafe { ManuallyDrop::new(Waker::from_raw(RawWaker::new(ptr, vt))) }; - let cx = &mut Context::from_waker(&waker); + unsafe fn wake_by_ref_raw(ptr: *const ()) { + let arc = ManuallyDrop::new(Arc::from_raw(ptr as *const Parker)); + arc.unparker().unpark(); + } - loop { - if let Poll::Ready(t) = f.as_mut().poll(cx) { - return t; - } - thread::park(); + unsafe fn drop_raw(ptr: *const ()) { + drop(Arc::from_raw(ptr as *const Parker)) } - }) -} -fn vtable() -> &'static RawWakerVTable { - unsafe fn clone_raw(ptr: *const ()) -> RawWaker { - let arc = ManuallyDrop::new(Arc::from_raw(ptr as *const Thread)); - mem::forget(arc.clone()); - RawWaker::new(ptr, vtable()) - } + RawWakerVTable::new(clone_raw, wake_raw, wake_by_ref_raw, drop_raw) + }; - unsafe fn wake_raw(ptr: *const ()) { - let arc = Arc::from_raw(ptr as *const Thread); - arc.unpark(); - } + // Pin the future on the stack. + pin_utils::pin_mut!(future); - unsafe fn wake_by_ref_raw(ptr: *const ()) { - let arc = ManuallyDrop::new(Arc::from_raw(ptr as *const Thread)); - arc.unpark(); - } + CACHE.with(|cache| { + // Reuse a cached parker or create a new one for this invocation of `block`. + let arc_parker: Arc = cache.take().unwrap_or_else(|| Arc::new(Parker::new())); + let ptr = (&*arc_parker as *const Parker) as *const (); - unsafe fn drop_raw(ptr: *const ()) { - drop(Arc::from_raw(ptr as *const Thread)) - } + // Create a waker and task context. + let waker = unsafe { ManuallyDrop::new(Waker::from_raw(RawWaker::new(ptr, &VTABLE))) }; + let cx = &mut Context::from_waker(&waker); + + let mut step = 0; + loop { + if let Poll::Ready(t) = future.as_mut().poll(cx) { + // Save the parker for the next invocation of `block`. + cache.set(Some(arc_parker)); + return t; + } - &RawWakerVTable::new(clone_raw, wake_raw, wake_by_ref_raw, drop_raw) + // Yield a few times or park the current thread. + if step < 3 { + thread::yield_now(); + step += 1; + } else { + arc_parker.park(); + step = 0; + } + } + }) } diff --git a/src/task/blocking.rs b/src/task/blocking.rs deleted file mode 100644 index 53b52c82..00000000 --- a/src/task/blocking.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! A thread pool for running blocking functions asynchronously. - -use std::sync::atomic::{AtomicU64, Ordering}; -use std::thread; -use std::time::Duration; - -use crossbeam_channel::{bounded, Receiver, Sender}; -use lazy_static::lazy_static; - -use crate::future::Future; -use crate::task::task::{JoinHandle, Tag}; -use crate::utils::abort_on_panic; - -const MAX_THREADS: u64 = 10_000; - -static DYNAMIC_THREAD_COUNT: AtomicU64 = AtomicU64::new(0); - -struct Pool { - sender: Sender>, - receiver: Receiver>, -} - -lazy_static! { - static ref POOL: Pool = { - for _ in 0..2 { - thread::Builder::new() - .name("async-blocking-driver".to_string()) - .spawn(|| abort_on_panic(|| { - for task in &POOL.receiver { - task.run(); - } - })) - .expect("cannot start a thread driving blocking tasks"); - } - - // We want to use an unbuffered channel here to help - // us drive our dynamic control. In effect, the - // kernel's scheduler becomes the queue, reducing - // the number of buffers that work must flow through - // before being acted on by a core. This helps keep - // latency snappy in the overall async system by - // reducing bufferbloat. - let (sender, receiver) = bounded(0); - Pool { sender, receiver } - }; -} - -// Create up to MAX_THREADS dynamic blocking task worker threads. -// Dynamic threads will terminate themselves if they don't -// receive any work after between one and ten seconds. -fn maybe_create_another_blocking_thread() { - // We use a `Relaxed` atomic operation because - // it's just a heuristic, and would not lose correctness - // even if it's random. - let workers = DYNAMIC_THREAD_COUNT.load(Ordering::Relaxed); - if workers >= MAX_THREADS { - return; - } - - // We want to avoid having all threads terminate at - // exactly the same time, causing thundering herd - // effects. We want to stagger their destruction over - // 10 seconds or so to make the costs fade into - // background noise. - // - // Generate a simple random number of milliseconds - let rand_sleep_ms = u64::from(random(10_000)); - - thread::Builder::new() - .name("async-blocking-driver-dynamic".to_string()) - .spawn(move || { - let wait_limit = Duration::from_millis(1000 + rand_sleep_ms); - - DYNAMIC_THREAD_COUNT.fetch_add(1, Ordering::Relaxed); - while let Ok(task) = POOL.receiver.recv_timeout(wait_limit) { - abort_on_panic(|| task.run()); - } - DYNAMIC_THREAD_COUNT.fetch_sub(1, Ordering::Relaxed); - }) - .expect("cannot start a dynamic thread driving blocking tasks"); -} - -// Enqueues work, attempting to send to the threadpool in a -// nonblocking way and spinning up another worker thread if -// there is not a thread ready to accept the work. -fn schedule(t: async_task::Task) { - if let Err(err) = POOL.sender.try_send(t) { - // We were not able to send to the channel without - // blocking. Try to spin up another thread and then - // retry sending while blocking. - maybe_create_another_blocking_thread(); - POOL.sender.send(err.into_inner()).unwrap(); - } -} - -/// Spawns a blocking task. -/// -/// The task will be spawned onto a thread pool specifically dedicated to blocking tasks. -pub(crate) fn spawn(future: F) -> JoinHandle -where - F: Future + Send + 'static, - R: Send + 'static, -{ - let tag = Tag::new(None); - let (task, handle) = async_task::spawn(future, schedule, tag); - task.schedule(); - JoinHandle::new(handle) -} - -/// Generates a random number in `0..n`. -fn random(n: u32) -> u32 { - use std::cell::Cell; - use std::num::Wrapping; - - thread_local! { - static RNG: Cell> = Cell::new(Wrapping(1_406_868_647)); - } - - RNG.with(|rng| { - // This is the 32-bit variant of Xorshift. - // - // Source: https://en.wikipedia.org/wiki/Xorshift - let mut x = rng.get(); - x ^= x << 13; - x ^= x >> 17; - x ^= x << 5; - rng.set(x); - - // This is a fast alternative to `x % n`. - // - // Author: Daniel Lemire - // Source: https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ - ((u64::from(x.0)).wrapping_mul(u64::from(n)) >> 32) as u32 - }) -} diff --git a/src/task/builder.rs b/src/task/builder.rs index a43b42bc..afd4c2c1 100644 --- a/src/task/builder.rs +++ b/src/task/builder.rs @@ -1,7 +1,11 @@ -use super::pool; -use super::JoinHandle; -use crate::future::Future; +use kv_log_macro::trace; +use log::log_enabled; +use std::future::Future; + use crate::io; +use crate::task::executor; +use crate::task::{JoinHandle, Task}; +use crate::utils::abort_on_panic; /// Task builder that configures the settings of a new task. #[derive(Debug, Default)] @@ -11,11 +15,13 @@ pub struct Builder { impl Builder { /// Creates a new builder. + #[inline] pub fn new() -> Builder { Builder { name: None } } /// Configures the name of the task. + #[inline] pub fn name(mut self, name: String) -> Builder { self.name = Some(name); self @@ -27,6 +33,52 @@ impl Builder { F: Future + Send + 'static, T: Send + 'static, { - Ok(pool::get().spawn(future, self)) + // Create a new task handle. + let task = Task::new(self.name); + + // Log this `spawn` operation. + if log_enabled!(log::Level::Trace) { + trace!("spawn", { + task_id: task.id().0, + parent_task_id: Task::get_current(|t| t.id().0).unwrap_or(0), + }); + } + + let future = async move { + // Drop task-locals on exit. + defer! { + Task::get_current(|t| unsafe { t.drop_locals() }); + } + + // Log completion on exit. + defer! { + if log_enabled!(log::Level::Trace) { + Task::get_current(|t| { + trace!("completed", { + task_id: t.id().0, + }); + }); + } + } + + future.await + }; + + let schedule = move |t| executor::schedule(Runnable(t)); + let (task, handle) = async_task::spawn(future, schedule, task); + task.schedule(); + Ok(JoinHandle::new(handle)) + } +} + +/// A runnable task. +pub(crate) struct Runnable(async_task::Task); + +impl Runnable { + /// Runs the task by polling its future once. + pub fn run(self) { + unsafe { + Task::set_current(self.0.tag(), || abort_on_panic(|| self.0.run())); + } } } diff --git a/src/task/current.rs b/src/task/current.rs new file mode 100644 index 00000000..0dc36991 --- /dev/null +++ b/src/task/current.rs @@ -0,0 +1,28 @@ +use crate::task::Task; + +/// Returns a handle to the current task. +/// +/// # Panics +/// +/// This function will panic if not called within the context of a task created by [`block_on`], +/// [`spawn`], or [`Builder::spawn`]. +/// +/// [`block_on`]: fn.block_on.html +/// [`spawn`]: fn.spawn.html +/// [`Builder::spawn`]: struct.Builder.html#method.spawn +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::task; +/// +/// println!("The name of this task is {:?}", task::current().name()); +/// # +/// # }) +/// ``` +pub fn current() -> Task { + Task::get_current(|t| t.clone()) + .expect("`task::current()` called outside the context of a task") +} diff --git a/src/task/executor/mod.rs b/src/task/executor/mod.rs new file mode 100644 index 00000000..2a6a696e --- /dev/null +++ b/src/task/executor/mod.rs @@ -0,0 +1,13 @@ +//! Task executor. +//! +//! API bindings between `crate::task` and this module are very simple: +//! +//! * The only export is the `schedule` function. +//! * The only import is the `crate::task::Runnable` type. + +pub(crate) use pool::schedule; + +use sleepers::Sleepers; + +mod pool; +mod sleepers; diff --git a/src/task/executor/pool.rs b/src/task/executor/pool.rs new file mode 100644 index 00000000..5249b3d9 --- /dev/null +++ b/src/task/executor/pool.rs @@ -0,0 +1,179 @@ +use std::cell::Cell; +use std::iter; +use std::thread; +use std::time::Duration; + +use crossbeam_deque::{Injector, Stealer, Worker}; +use once_cell::sync::Lazy; +use once_cell::unsync::OnceCell; + +use crate::task::executor::Sleepers; +use crate::task::Runnable; +use crate::utils::{abort_on_panic, random}; + +/// The state of an executor. +struct Pool { + /// The global queue of tasks. + injector: Injector, + + /// Handles to local queues for stealing work from worker threads. + stealers: Vec>, + + /// Used for putting idle workers to sleep and notifying them when new tasks come in. + sleepers: Sleepers, +} + +/// Global executor that runs spawned tasks. +static POOL: Lazy = Lazy::new(|| { + let num_threads = num_cpus::get().max(1); + let mut stealers = Vec::new(); + + // Spawn worker threads. + for _ in 0..num_threads { + let worker = Worker::new_fifo(); + stealers.push(worker.stealer()); + + let proc = Processor { + worker, + slot: Cell::new(None), + slot_runs: Cell::new(0), + }; + + thread::Builder::new() + .name("async-std/executor".to_string()) + .spawn(|| { + let _ = PROCESSOR.with(|p| p.set(proc)); + abort_on_panic(main_loop); + }) + .expect("cannot start a thread driving tasks"); + } + + Pool { + injector: Injector::new(), + stealers, + sleepers: Sleepers::new(), + } +}); + +/// The state of a worker thread. +struct Processor { + /// The local task queue. + worker: Worker, + + /// Contains the next task to run as an optimization that skips queues. + slot: Cell>, + + /// How many times in a row tasks have been taked from the slot rather than the queue. + slot_runs: Cell, +} + +thread_local! { + /// Worker thread state. + static PROCESSOR: OnceCell = OnceCell::new(); +} + +/// Schedules a new runnable task for execution. +pub(crate) fn schedule(task: Runnable) { + PROCESSOR.with(|proc| { + // If the current thread is a worker thread, store it into its task slot or push it into + // its local task queue. Otherwise, push it into the global task queue. + match proc.get() { + Some(proc) => { + // Replace the task in the slot. + if let Some(task) = proc.slot.replace(Some(task)) { + // If the slot already contained a task, push it into the local task queue. + proc.worker.push(task); + POOL.sleepers.notify_one(); + } + } + None => { + POOL.injector.push(task); + POOL.sleepers.notify_one(); + } + } + }) +} + +/// Main loop running a worker thread. +fn main_loop() { + /// Number of yields when no runnable task is found. + const YIELDS: u32 = 3; + /// Number of short sleeps when no runnable task in found. + const SLEEPS: u32 = 1; + + // The number of times the thread didn't find work in a row. + let mut fails = 0; + + loop { + // Try to find a runnable task. + match find_runnable() { + Some(task) => { + fails = 0; + + // Run the found task. + task.run(); + } + None => { + fails += 1; + + // Yield the current thread or put it to sleep. + if fails <= YIELDS { + thread::yield_now(); + } else if fails <= YIELDS + SLEEPS { + thread::sleep(Duration::from_micros(10)); + } else { + POOL.sleepers.wait(); + fails = 0; + } + } + } + } +} + +/// Find the next runnable task. +fn find_runnable() -> Option { + /// Maximum number of times the slot can be used in a row. + const SLOT_LIMIT: u32 = 16; + + PROCESSOR.with(|proc| { + let proc = proc.get().unwrap(); + + // Try taking a task from the slot. + let runs = proc.slot_runs.get(); + if runs < SLOT_LIMIT { + if let Some(task) = proc.slot.take() { + proc.slot_runs.set(runs + 1); + return Some(task); + } + } + proc.slot_runs.set(0); + + // Pop a task from the local queue, if not empty. + proc.worker.pop().or_else(|| { + // Otherwise, we need to look for a task elsewhere. + iter::repeat_with(|| { + // Try stealing a batch of tasks from the global queue. + POOL.injector + .steal_batch_and_pop(&proc.worker) + // Or try stealing a batch of tasks from one of the other threads. + .or_else(|| { + // First, pick a random starting point in the list of local queues. + let len = POOL.stealers.len(); + let start = random(len as u32) as usize; + + // Try stealing a batch of tasks from each local queue starting from the + // chosen point. + let (l, r) = POOL.stealers.split_at(start); + let stealers = r.iter().chain(l.iter()); + stealers + .map(|s| s.steal_batch_and_pop(&proc.worker)) + .collect() + }) + }) + // Loop while no task was stolen and any steal operation needs to be retried. + .find(|s| !s.is_retry()) + // Extract the stolen task, if there is one. + .and_then(|s| s.success()) + }) + }) +} diff --git a/src/task/sleepers.rs b/src/task/executor/sleepers.rs similarity index 100% rename from src/task/sleepers.rs rename to src/task/executor/sleepers.rs diff --git a/src/task/join_handle.rs b/src/task/join_handle.rs new file mode 100644 index 00000000..9fefff2e --- /dev/null +++ b/src/task/join_handle.rs @@ -0,0 +1,56 @@ +use std::future::Future; +use std::pin::Pin; + +use crate::task::{Context, Poll, Task}; + +/// A handle that awaits the result of a task. +/// +/// Dropping a [`JoinHandle`] will detach the task, meaning that there is no longer +/// a handle to the task and no way to `join` on it. +/// +/// Created when a task is [spawned]. +/// +/// [spawned]: fn.spawn.html +#[derive(Debug)] +pub struct JoinHandle(async_task::JoinHandle); + +unsafe impl Send for JoinHandle {} +unsafe impl Sync for JoinHandle {} + +impl JoinHandle { + /// Creates a new `JoinHandle`. + pub(crate) fn new(inner: async_task::JoinHandle) -> JoinHandle { + JoinHandle(inner) + } + + /// Returns a handle to the underlying task. + /// + /// # Examples + /// + /// ``` + /// # async_std::task::block_on(async { + /// # + /// use async_std::task; + /// + /// let handle = task::spawn(async { + /// 1 + 2 + /// }); + /// println!("id = {}", handle.task().id()); + /// # + /// # }) + pub fn task(&self) -> &Task { + self.0.tag() + } +} + +impl Future for JoinHandle { + type Output = T; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match Pin::new(&mut self.0).poll(cx) { + Poll::Pending => Poll::Pending, + Poll::Ready(None) => panic!("cannot await the result of a panicked task"), + Poll::Ready(Some(val)) => Poll::Ready(val), + } + } +} diff --git a/src/task/mod.rs b/src/task/mod.rs index a14e5d52..198e5787 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -1,88 +1,162 @@ -//! Asynchronous tasks. +//! Types and traits for working with asynchronous tasks. //! //! This module is similar to [`std::thread`], except it uses asynchronous tasks in place of //! threads. //! -//! [`std::thread`]: https://doc.rust-lang.org/std/thread/index.html +//! [`std::thread`]: https://doc.rust-lang.org/std/thread //! -//! # Examples +//! ## The task model //! -//! Spawn a task and await its result: +//! An executing asynchronous Rust program consists of a collection of native OS threads, on top of +//! which multiple stackless coroutines are multiplexed. We refer to these as "tasks". Tasks can +//! be named, and provide some built-in support for synchronization. //! +//! Communication between tasks can be done through channels, Rust's message-passing types, along +//! with [other forms of tasks synchronization](../sync/index.html) and shared-memory data +//! structures. In particular, types that are guaranteed to be threadsafe are easily shared between +//! tasks using the atomically-reference-counted container, [`Arc`]. +//! +//! Fatal logic errors in Rust cause *thread panic*, during which a thread will unwind the stack, +//! running destructors and freeing owned resources. If a panic occurs inside a task, there is no +//! meaningful way of recovering, so the panic will propagate through any thread boundaries all the +//! way to the root task. This is also known as a "panic = abort" model. +//! +//! ## Spawning a task +//! +//! A new task can be spawned using the [`task::spawn`][`spawn`] function: +//! +//! ```no_run +//! use async_std::task; +//! +//! task::spawn(async { +//! // some work here +//! }); //! ``` -//! # fn main() { async_std::task::block_on(async { +//! +//! In this example, the spawned task is "detached" from the current task. This means that it can +//! outlive its parent (the task that spawned it), unless this parent is the root task. +//! +//! The root task can also wait on the completion of the child task; a call to [`spawn`] produces a +//! [`JoinHandle`], which provides implements `Future` and can be `await`ed: +//! +//! ``` +//! use async_std::task; +//! +//! # async_std::task::block_on(async { //! # +//! let child = task::spawn(async { +//! // some work here +//! }); +//! // some work here +//! let res = child.await; +//! # +//! # }) +//! ``` +//! +//! The `await` operator returns the final value produced by the child task. +//! +//! ## Configuring tasks +//! +//! A new task can be configured before it is spawned via the [`Builder`] type, +//! which currently allows you to set the name and stack size for the child task: +//! +//! ``` +//! # #![allow(unused_must_use)] //! use async_std::task; //! -//! let handle = task::spawn(async { -//! 1 + 2 +//! # async_std::task::block_on(async { +//! # +//! task::Builder::new().name("child1".to_string()).spawn(async { +//! println!("Hello, world!"); //! }); -//! assert_eq!(handle.await, 3); //! # -//! # }) } +//! # }) //! ``` +//! +//! ## The `Task` type +//! +//! Tasks are represented via the [`Task`] type, which you can get in one of +//! two ways: +//! +//! * By spawning a new task, e.g., using the [`task::spawn`][`spawn`] +//! function, and calling [`task`][`JoinHandle::task`] on the [`JoinHandle`]. +//! * By requesting the current task, using the [`task::current`] function. +//! +//! ## Task-local storage +//! +//! This module also provides an implementation of task-local storage for Rust +//! programs. Task-local storage is a method of storing data into a global +//! variable that each task in the program will have its own copy of. +//! Tasks do not share this data, so accesses do not need to be synchronized. +//! +//! A task-local key owns the value it contains and will destroy the value when the +//! task exits. It is created with the [`task_local!`] macro and can contain any +//! value that is `'static` (no borrowed pointers). It provides an accessor function, +//! [`with`], that yields a shared reference to the value to the specified +//! closure. Task-local keys allow only shared access to values, as there would be no +//! way to guarantee uniqueness if mutable borrows were allowed. +//! +//! ## Naming tasks +//! +//! Tasks are able to have associated names for identification purposes. By default, spawned +//! tasks are unnamed. To specify a name for a task, build the task with [`Builder`] and pass +//! the desired task name to [`Builder::name`]. To retrieve the task name from within the +//! task, use [`Task::name`]. +//! +//! [`Arc`]: ../gsync/struct.Arc.html +//! [`spawn`]: fn.spawn.html +//! [`JoinHandle`]: struct.JoinHandle.html +//! [`JoinHandle::task`]: struct.JoinHandle.html#method.task +//! [`join`]: struct.JoinHandle.html#method.join +//! [`panic!`]: https://doc.rust-lang.org/std/macro.panic.html +//! [`Builder`]: struct.Builder.html +//! [`Builder::stack_size`]: struct.Builder.html#method.stack_size +//! [`Builder::name`]: struct.Builder.html#method.name +//! [`task::current`]: fn.current.html +//! [`Task`]: struct.Thread.html +//! [`Task::name`]: struct.Task.html#method.name +//! [`task_local!`]: ../macro.task_local.html +//! [`with`]: struct.LocalKey.html#method.with -#[doc(inline)] -pub use std::task::{Context, Poll, Waker}; +cfg_std! { + #[doc(inline)] + pub use std::task::{Context, Poll, Waker}; -#[cfg(any(feature = "unstable", feature = "docs"))] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -#[doc(inline)] -pub use async_macros::ready; + #[doc(inline)] + pub use async_macros::ready; + + pub use yield_now::yield_now; + mod yield_now; +} -pub use block_on::block_on; -pub use builder::Builder; -pub use pool::spawn; -pub use sleep::sleep; -pub use task::{JoinHandle, Task, TaskId}; -pub use task_local::{AccessError, LocalKey}; -pub use worker::current; +cfg_default! { + pub use block_on::block_on; + pub use builder::Builder; + pub use current::current; + pub use task::Task; + pub use task_id::TaskId; + pub use join_handle::JoinHandle; + pub use sleep::sleep; + pub use spawn::spawn; + pub use task_local::{AccessError, LocalKey}; -mod block_on; -mod builder; -mod pool; -mod sleep; -mod sleepers; -mod task; -mod task_local; -mod worker; + use builder::Runnable; + use task_local::LocalsMap; -pub(crate) mod blocking; + mod block_on; + mod builder; + mod current; + mod executor; + mod join_handle; + mod sleep; + mod spawn; + mod spawn_blocking; + mod task; + mod task_id; + mod task_local; -/// Spawns a blocking task. -/// -/// The task will be spawned onto a thread pool specifically dedicated to blocking tasks. This -/// is useful to prevent long-running synchronous operations from blocking the main futures -/// executor. -/// -/// See also: [`task::block_on`]. -/// -/// [`task::block_on`]: fn.block_on.html -/// -/// # Examples -/// -/// Basic usage: -/// -/// ``` -/// # fn main() { async_std::task::block_on(async { -/// # -/// use async_std::task; -/// -/// task::blocking(async { -/// println!("long-running task here"); -/// }).await; -/// # -/// # }) } -/// ``` -// Once this function stabilizes we should merge `blocking::spawn` into this so -// all code in our crate uses `task::blocking` too. -#[cfg(any(feature = "unstable", feature = "docs"))] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] -#[inline] -pub fn blocking(future: F) -> task::JoinHandle -where - F: crate::future::Future + Send + 'static, - R: Send + 'static, -{ - blocking::spawn(future) + #[cfg(any(feature = "unstable", test))] + pub use spawn_blocking::spawn_blocking; + #[cfg(not(any(feature = "unstable", test)))] + pub(crate) use spawn_blocking::spawn_blocking; } diff --git a/src/task/pool.rs b/src/task/pool.rs deleted file mode 100644 index 49fbfe49..00000000 --- a/src/task/pool.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::iter; -use std::thread; - -use crossbeam_deque::{Injector, Stealer, Worker}; -use kv_log_macro::trace; -use lazy_static::lazy_static; - -use super::sleepers::Sleepers; -use super::task; -use super::task_local; -use super::worker; -use super::{Builder, JoinHandle}; -use crate::future::Future; -use crate::utils::abort_on_panic; - -/// Spawns a task. -/// -/// This function is similar to [`std::thread::spawn`], except it spawns an asynchronous task. -/// -/// [`std::thread`]: https://doc.rust-lang.org/std/thread/fn.spawn.html -/// -/// # Examples -/// -/// ``` -/// # fn main() { async_std::task::block_on(async { -/// # -/// use async_std::task; -/// -/// let handle = task::spawn(async { -/// 1 + 2 -/// }); -/// -/// assert_eq!(handle.await, 3); -/// # -/// # }) } -/// ``` -pub fn spawn(future: F) -> JoinHandle -where - F: Future + Send + 'static, - T: Send + 'static, -{ - Builder::new().spawn(future).expect("cannot spawn future") -} - -pub(crate) struct Pool { - pub injector: Injector, - pub stealers: Vec>, - pub sleepers: Sleepers, -} - -impl Pool { - /// Spawn a future onto the pool. - pub fn spawn(&self, future: F, builder: Builder) -> JoinHandle - where - F: Future + Send + 'static, - T: Send + 'static, - { - let tag = task::Tag::new(builder.name); - - // Log this `spawn` operation. - let child_id = tag.task_id().as_u64(); - let parent_id = worker::get_task(|t| t.id().as_u64()).unwrap_or(0); - - trace!("spawn", { - parent_id: parent_id, - child_id: child_id, - }); - - // Wrap the future into one that drops task-local variables on exit. - let future = unsafe { task_local::add_finalizer(future) }; - - // Wrap the future into one that logs completion on exit. - let future = async move { - let res = future.await; - trace!("spawn completed", { - parent_id: parent_id, - child_id: child_id, - }); - res - }; - - let (task, handle) = async_task::spawn(future, worker::schedule, tag); - task.schedule(); - JoinHandle::new(handle) - } - - /// Find the next runnable task to run. - pub fn find_task(&self, local: &Worker) -> Option { - // Pop a task from the local queue, if not empty. - local.pop().or_else(|| { - // Otherwise, we need to look for a task elsewhere. - iter::repeat_with(|| { - // Try stealing a batch of tasks from the injector queue. - self.injector - .steal_batch_and_pop(local) - // Or try stealing a bach of tasks from one of the other threads. - .or_else(|| { - self.stealers - .iter() - .map(|s| s.steal_batch_and_pop(local)) - .collect() - }) - }) - // Loop while no task was stolen and any steal operation needs to be retried. - .find(|s| !s.is_retry()) - // Extract the stolen task, if there is one. - .and_then(|s| s.success()) - }) - } -} - -#[inline] -pub(crate) fn get() -> &'static Pool { - lazy_static! { - static ref POOL: Pool = { - let num_threads = num_cpus::get().max(1); - let mut stealers = Vec::new(); - - // Spawn worker threads. - for _ in 0..num_threads { - let worker = Worker::new_fifo(); - stealers.push(worker.stealer()); - - thread::Builder::new() - .name("async-task-driver".to_string()) - .spawn(|| abort_on_panic(|| worker::main_loop(worker))) - .expect("cannot start a thread driving tasks"); - } - - Pool { - injector: Injector::new(), - stealers, - sleepers: Sleepers::new(), - } - }; - } - &*POOL -} diff --git a/src/task/sleep.rs b/src/task/sleep.rs index 9db09ffe..380ee3e6 100644 --- a/src/task/sleep.rs +++ b/src/task/sleep.rs @@ -11,10 +11,14 @@ use crate::io; /// /// [`std::thread::sleep`]: https://doc.rust-lang.org/std/thread/fn.sleep.html /// +/// See also: [`stream::interval`]. +/// +/// [`stream::interval`]: ../stream/fn.interval.html +/// /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # /// use std::time::Duration; /// @@ -22,7 +26,7 @@ use crate::io; /// /// task::sleep(Duration::from_secs(1)).await; /// # -/// # }) } +/// # }) /// ``` pub async fn sleep(dur: Duration) { let _: io::Result<()> = io::timeout(dur, future::pending()).await; diff --git a/src/task/spawn.rs b/src/task/spawn.rs new file mode 100644 index 00000000..f81a483d --- /dev/null +++ b/src/task/spawn.rs @@ -0,0 +1,32 @@ +use std::future::Future; + +use crate::task::{Builder, JoinHandle}; + +/// Spawns a task. +/// +/// This function is similar to [`std::thread::spawn`], except it spawns an asynchronous task. +/// +/// [`std::thread`]: https://doc.rust-lang.org/std/thread/fn.spawn.html +/// +/// # Examples +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::task; +/// +/// let handle = task::spawn(async { +/// 1 + 2 +/// }); +/// +/// assert_eq!(handle.await, 3); +/// # +/// # }) +/// ``` +pub fn spawn(future: F) -> JoinHandle +where + F: Future + Send + 'static, + T: Send + 'static, +{ + Builder::new().spawn(future).expect("cannot spawn task") +} diff --git a/src/task/spawn_blocking.rs b/src/task/spawn_blocking.rs new file mode 100644 index 00000000..578afa4e --- /dev/null +++ b/src/task/spawn_blocking.rs @@ -0,0 +1,121 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::thread; +use std::time::Duration; + +use crossbeam_channel::{unbounded, Receiver, Sender}; +use once_cell::sync::Lazy; + +use crate::task::{JoinHandle, Task}; +use crate::utils::abort_on_panic; + +/// Spawns a blocking task. +/// +/// The task will be spawned onto a thread pool specifically dedicated to blocking tasks. This +/// is useful to prevent long-running synchronous operations from blocking the main futures +/// executor. +/// +/// See also: [`task::block_on`], [`task::spawn`]. +/// +/// [`task::block_on`]: fn.block_on.html +/// [`task::spawn`]: fn.spawn.html +/// +/// # Examples +/// +/// Basic usage: +/// +/// ``` +/// # #[cfg(feature = "unstable")] +/// # async_std::task::block_on(async { +/// # +/// use async_std::task; +/// +/// task::spawn_blocking(|| { +/// println!("long-running task here"); +/// }).await; +/// # +/// # }) +/// ``` +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[inline] +pub fn spawn_blocking(f: F) -> JoinHandle +where + F: FnOnce() -> T + Send + 'static, + T: Send + 'static, +{ + let schedule = |task| POOL.sender.send(task).unwrap(); + let (task, handle) = async_task::spawn(async { f() }, schedule, Task::new(None)); + task.schedule(); + JoinHandle::new(handle) +} + +type Runnable = async_task::Task; + +/// The number of sleeping worker threads. +static SLEEPING: AtomicUsize = AtomicUsize::new(0); + +struct Pool { + sender: Sender, + receiver: Receiver, +} + +static POOL: Lazy = Lazy::new(|| { + // Start a single worker thread waiting for the first task. + start_thread(); + + let (sender, receiver) = unbounded(); + Pool { sender, receiver } +}); + +fn start_thread() { + SLEEPING.fetch_add(1, Ordering::SeqCst); + let timeout = Duration::from_secs(1); + + thread::Builder::new() + .name("async-std/blocking".to_string()) + .spawn(move || { + loop { + let mut task = match POOL.receiver.recv_timeout(timeout) { + Ok(task) => task, + Err(_) => { + // Check whether this is the last sleeping thread. + if SLEEPING.fetch_sub(1, Ordering::SeqCst) == 1 { + // If so, then restart the thread to make sure there is always at least + // one sleeping thread. + if SLEEPING.compare_and_swap(0, 1, Ordering::SeqCst) == 0 { + continue; + } + } + + // Stop the thread. + return; + } + }; + + // If there are no sleeping threads, then start one to make sure there is always at + // least one sleeping thread. + if SLEEPING.fetch_sub(1, Ordering::SeqCst) == 1 { + start_thread(); + } + + loop { + // Run the task. + abort_on_panic(|| task.run()); + + // Try taking another task if there are any available. + task = match POOL.receiver.try_recv() { + Ok(task) => task, + Err(_) => break, + }; + } + + // If there is at least one sleeping thread, stop this thread instead of putting it + // to sleep. + if SLEEPING.load(Ordering::SeqCst) > 0 { + return; + } + + SLEEPING.fetch_add(1, Ordering::SeqCst); + } + }) + .expect("cannot start a blocking thread"); +} diff --git a/src/task/task.rs b/src/task/task.rs index ba808aa2..bcec2e0e 100644 --- a/src/task/task.rs +++ b/src/task/task.rs @@ -1,31 +1,74 @@ +use std::cell::Cell; use std::fmt; -use std::i64; -use std::mem; -use std::num::NonZeroU64; -use std::pin::Pin; -use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; +use std::mem::ManuallyDrop; +use std::ptr; +use std::sync::atomic::{AtomicPtr, Ordering}; use std::sync::Arc; -use super::task_local; -use crate::future::Future; -use crate::task::{Context, Poll}; +use crate::task::{LocalsMap, TaskId}; +use crate::utils::abort_on_panic; + +thread_local! { + /// A pointer to the currently running task. + static CURRENT: Cell<*const Task> = Cell::new(ptr::null_mut()); +} + +/// The inner representation of a task handle. +struct Inner { + /// The task ID. + id: TaskId, + + /// The optional task name. + name: Option>, + + /// The map holding task-local values. + locals: LocalsMap, +} + +impl Inner { + #[inline] + fn new(name: Option) -> Inner { + Inner { + id: TaskId::generate(), + name: name.map(String::into_boxed_str), + locals: LocalsMap::new(), + } + } +} /// A handle to a task. -#[derive(Clone)] -pub struct Task(Arc); +pub struct Task { + /// The inner representation. + /// + /// This pointer is lazily initialized on first use. In most cases, the inner representation is + /// never touched and therefore we don't allocate it unless it's really needed. + inner: AtomicPtr, +} unsafe impl Send for Task {} unsafe impl Sync for Task {} impl Task { - /// Returns a reference to task metadata. - pub(crate) fn metadata(&self) -> &Metadata { - &self.0 + /// Creates a new task handle. + /// + /// If the task is unnamed, the inner representation of the task will be lazily allocated on + /// demand. + #[inline] + pub(crate) fn new(name: Option) -> Task { + let inner = match name { + None => AtomicPtr::default(), + Some(name) => { + let raw = Arc::into_raw(Arc::new(Inner::new(Some(name)))); + AtomicPtr::new(raw as *mut Inner) + } + }; + Task { inner } } /// Gets the task's unique identifier. + #[inline] pub fn id(&self) -> TaskId { - self.metadata().task_id + self.inner().id } /// Returns the name of this task. @@ -34,176 +77,101 @@ impl Task { /// /// [`Builder::name`]: struct.Builder.html#method.name pub fn name(&self) -> Option<&str> { - self.metadata().name.as_ref().map(|s| s.as_str()) + self.inner().name.as_ref().map(|s| &**s) } -} - -impl fmt::Debug for Task { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Task").field("name", &self.name()).finish() - } -} -/// A handle that awaits the result of a task. -/// -/// Dropping a [`JoinHandle`] will detach the task, meaning that there is no longer -/// a handle to the task and no way to `join` on it. -/// -/// Created when a task is [spawned]. -/// -/// [spawned]: fn.spawn.html -#[derive(Debug)] -pub struct JoinHandle(async_task::JoinHandle); - -unsafe impl Send for JoinHandle {} -unsafe impl Sync for JoinHandle {} - -impl JoinHandle { - pub(crate) fn new(inner: async_task::JoinHandle) -> JoinHandle { - JoinHandle(inner) + /// Returns the map holding task-local values. + pub(crate) fn locals(&self) -> &LocalsMap { + &self.inner().locals } - /// Returns a handle to the underlying task. + /// Drops all task-local values. /// - /// # Examples - /// - /// ``` - /// # fn main() { async_std::task::block_on(async { - /// # - /// use async_std::task; - /// - /// let handle = task::spawn(async { - /// 1 + 2 - /// }); - /// println!("id = {}", handle.task().id()); - /// # - /// # }) } - pub fn task(&self) -> &Task { - self.0.tag().task() - } -} - -impl Future for JoinHandle { - type Output = T; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match Pin::new(&mut self.0).poll(cx) { - Poll::Pending => Poll::Pending, - Poll::Ready(None) => panic!("task has panicked"), - Poll::Ready(Some(val)) => Poll::Ready(val), + /// This method is only safe to call at the end of the task. + #[inline] + pub(crate) unsafe fn drop_locals(&self) { + let raw = self.inner.load(Ordering::Acquire); + if let Some(inner) = raw.as_mut() { + // Abort the process if dropping task-locals panics. + abort_on_panic(|| { + inner.locals.clear(); + }); } } -} -/// A unique identifier for a task. -/// -/// # Examples -/// -/// ``` -/// # -/// use async_std::task; -/// -/// task::block_on(async { -/// println!("id = {:?}", task::current().id()); -/// }) -/// ``` -#[derive(Eq, PartialEq, Clone, Copy, Hash, Debug)] -pub struct TaskId(NonZeroU64); - -impl TaskId { - pub(crate) fn new() -> TaskId { - static COUNTER: AtomicU64 = AtomicU64::new(1); - - let id = COUNTER.fetch_add(1, Ordering::Relaxed); - - if id > i64::MAX as u64 { - std::process::abort(); + /// Returns the inner representation, initializing it on first use. + fn inner(&self) -> &Inner { + loop { + let raw = self.inner.load(Ordering::Acquire); + if !raw.is_null() { + return unsafe { &*raw }; + } + + let new = Arc::into_raw(Arc::new(Inner::new(None))) as *mut Inner; + if self.inner.compare_and_swap(raw, new, Ordering::AcqRel) != raw { + unsafe { + drop(Arc::from_raw(new)); + } + } } - unsafe { TaskId(NonZeroU64::new_unchecked(id)) } } - pub(crate) fn as_u64(self) -> u64 { - self.0.get() - } -} - -impl fmt::Display for TaskId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) + /// Set a reference to the current task. + pub(crate) unsafe fn set_current(task: *const Task, f: F) -> R + where + F: FnOnce() -> R, + { + CURRENT.with(|current| { + let old_task = current.replace(task); + defer! { + current.set(old_task); + } + f() + }) } -} - -pub(crate) type Runnable = async_task::Task; -pub(crate) struct Metadata { - pub task_id: TaskId, - pub name: Option, - pub local_map: task_local::Map, -} - -pub(crate) struct Tag { - task_id: TaskId, - raw_metadata: AtomicUsize, -} - -impl Tag { - pub fn new(name: Option) -> Tag { - let task_id = TaskId::new(); - - let opt_task = name.map(|name| { - Task(Arc::new(Metadata { - task_id, - name: Some(name), - local_map: task_local::Map::new(), - })) - }); - - Tag { - task_id, - raw_metadata: AtomicUsize::new(unsafe { - mem::transmute::, usize>(opt_task) - }), + /// Gets a reference to the current task. + pub(crate) fn get_current(f: F) -> Option + where + F: FnOnce(&Task) -> R, + { + let res = CURRENT.try_with(|current| unsafe { current.get().as_ref().map(f) }); + match res { + Ok(Some(val)) => Some(val), + Ok(None) | Err(_) => None, } } +} - pub fn task(&self) -> &Task { - unsafe { - let raw = self.raw_metadata.load(Ordering::Acquire); - - if mem::transmute::<&usize, &Option>(&raw).is_none() { - let new = Some(Task(Arc::new(Metadata { - task_id: TaskId::new(), - name: None, - local_map: task_local::Map::new(), - }))); - - let new_raw = mem::transmute::, usize>(new); - - if self - .raw_metadata - .compare_exchange(raw, new_raw, Ordering::AcqRel, Ordering::Acquire) - .is_err() - { - let new = mem::transmute::>(new_raw); - drop(new); - } +impl Drop for Task { + fn drop(&mut self) { + // Deallocate the inner representation if it was initialized. + let raw = *self.inner.get_mut(); + if !raw.is_null() { + unsafe { + drop(Arc::from_raw(raw)); } - - mem::transmute::<&AtomicUsize, &Option>(&self.raw_metadata) - .as_ref() - .unwrap() } } +} - pub fn task_id(&self) -> TaskId { - self.task_id +impl Clone for Task { + fn clone(&self) -> Task { + // We need to make sure the inner representation is initialized now so that this instance + // and the clone have raw pointers that point to the same `Arc`. + let arc = unsafe { ManuallyDrop::new(Arc::from_raw(self.inner())) }; + let raw = Arc::into_raw(Arc::clone(&arc)); + Task { + inner: AtomicPtr::new(raw as *mut Inner), + } } } -impl Drop for Tag { - fn drop(&mut self) { - let raw = *self.raw_metadata.get_mut(); - let opt_task = unsafe { mem::transmute::>(raw) }; - drop(opt_task); +impl fmt::Debug for Task { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Task") + .field("id", &self.id()) + .field("name", &self.name()) + .finish() } } diff --git a/src/task/task_id.rs b/src/task/task_id.rs new file mode 100644 index 00000000..67eee154 --- /dev/null +++ b/src/task/task_id.rs @@ -0,0 +1,35 @@ +use std::fmt; +use std::sync::atomic::{AtomicU64, Ordering}; + +/// A unique identifier for a task. +/// +/// # Examples +/// +/// ``` +/// use async_std::task; +/// +/// task::block_on(async { +/// println!("id = {:?}", task::current().id()); +/// }) +/// ``` +#[derive(Eq, PartialEq, Clone, Copy, Hash, Debug)] +pub struct TaskId(pub(crate) u64); + +impl TaskId { + /// Generates a new `TaskId`. + pub(crate) fn generate() -> TaskId { + static COUNTER: AtomicU64 = AtomicU64::new(1); + + let id = COUNTER.fetch_add(1, Ordering::Relaxed); + if id > u64::max_value() / 2 { + std::process::abort(); + } + TaskId(id) + } +} + +impl fmt::Display for TaskId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/src/task/task_local.rs b/src/task/task_local.rs index 8347e34b..72e53d72 100644 --- a/src/task/task_local.rs +++ b/src/task/task_local.rs @@ -1,65 +1,9 @@ use std::cell::UnsafeCell; use std::error::Error; use std::fmt; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Mutex; +use std::sync::atomic::{AtomicU32, Ordering}; -use lazy_static::lazy_static; - -use super::worker; -use crate::future::Future; -use crate::utils::abort_on_panic; - -/// Declares task-local values. -/// -/// The macro wraps any number of static declarations and makes them task-local. Attributes and -/// visibility modifiers are allowed. -/// -/// Each declared value is of the accessor type [`LocalKey`]. -/// -/// [`LocalKey`]: task/struct.LocalKey.html -/// -/// # Examples -/// -/// ``` -/// # -/// use std::cell::Cell; -/// -/// use async_std::task; -/// use async_std::prelude::*; -/// -/// task_local! { -/// static VAL: Cell = Cell::new(5); -/// } -/// -/// task::block_on(async { -/// let v = VAL.with(|c| c.get()); -/// assert_eq!(v, 5); -/// }); -/// ``` -#[macro_export] -macro_rules! task_local { - () => (); - - ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr) => ( - $(#[$attr])* $vis static $name: $crate::task::LocalKey<$t> = { - #[inline] - fn __init() -> $t { - $init - } - - $crate::task::LocalKey { - __init, - __key: ::std::sync::atomic::AtomicUsize::new(0), - } - }; - ); - - ($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr; $($rest:tt)*) => ( - $crate::task_local!($(#[$attr])* $vis static $name: $t = $init); - $crate::task_local!($($rest)*); - ); -} +use crate::task::Task; /// The key for accessing a task-local value. /// @@ -71,7 +15,7 @@ pub struct LocalKey { pub __init: fn() -> T, #[doc(hidden)] - pub __key: AtomicUsize, + pub __key: AtomicU32, } impl LocalKey { @@ -154,14 +98,13 @@ impl LocalKey { where F: FnOnce(&T) -> R, { - worker::get_task(|task| unsafe { + Task::get_current(|task| unsafe { // Prepare the numeric key, initialization function, and the map of task-locals. let key = self.key(); let init = || Box::new((self.__init)()) as Box; - let map = &task.metadata().local_map; // Get the value in the map of task-locals, or initialize and insert one. - let value: *const dyn Send = map.get_or_insert(key, init); + let value: *const dyn Send = task.locals().get_or_insert(key, init); // Call the closure with the value passed as an argument. f(&*(value as *const T)) @@ -171,26 +114,26 @@ impl LocalKey { /// Returns the numeric key associated with this task-local. #[inline] - fn key(&self) -> usize { + fn key(&self) -> u32 { #[cold] - fn init(key: &AtomicUsize) -> usize { - lazy_static! { - static ref COUNTER: Mutex = Mutex::new(1); - } + fn init(key: &AtomicU32) -> u32 { + static COUNTER: AtomicU32 = AtomicU32::new(1); - let mut counter = COUNTER.lock().unwrap(); - let prev = key.compare_and_swap(0, *counter, Ordering::AcqRel); + let counter = COUNTER.fetch_add(1, Ordering::Relaxed); + if counter > u32::max_value() / 2 { + std::process::abort(); + } - if prev == 0 { - *counter += 1; - *counter - 1 - } else { - prev + match key.compare_and_swap(0, counter, Ordering::AcqRel) { + 0 => counter, + k => k, } } - let key = self.__key.load(Ordering::Acquire); - if key == 0 { init(&self.__key) } else { key } + match self.__key.load(Ordering::Acquire) { + 0 => init(&self.__key), + k => k, + } } } @@ -216,51 +159,55 @@ impl fmt::Display for AccessError { impl Error for AccessError {} +/// A key-value entry in a map of task-locals. +struct Entry { + /// Key identifying the task-local variable. + key: u32, + + /// Value stored in this entry. + value: Box, +} + /// A map that holds task-locals. -pub(crate) struct Map { - /// A list of `(key, value)` entries sorted by the key. - entries: UnsafeCell)>>, +pub(crate) struct LocalsMap { + /// A list of key-value entries sorted by the key. + entries: UnsafeCell>>, } -impl Map { +impl LocalsMap { /// Creates an empty map of task-locals. - pub fn new() -> Map { - Map { - entries: UnsafeCell::new(Vec::new()), + pub fn new() -> LocalsMap { + LocalsMap { + entries: UnsafeCell::new(Some(Vec::new())), } } - /// Returns a thread-local value associated with `key` or inserts one constructed by `init`. + /// Returns a task-local value associated with `key` or inserts one constructed by `init`. #[inline] - pub fn get_or_insert(&self, key: usize, init: impl FnOnce() -> Box) -> &dyn Send { - let entries = unsafe { &mut *self.entries.get() }; - - let index = match entries.binary_search_by_key(&key, |e| e.0) { - Ok(i) => i, - Err(i) => { - entries.insert(i, (key, init())); - i + pub fn get_or_insert(&self, key: u32, init: impl FnOnce() -> Box) -> &dyn Send { + match unsafe { (*self.entries.get()).as_mut() } { + None => panic!("can't access task-locals while the task is being dropped"), + Some(entries) => { + let index = match entries.binary_search_by_key(&key, |e| e.key) { + Ok(i) => i, + Err(i) => { + let value = init(); + entries.insert(i, Entry { key, value }); + i + } + }; + &*entries[index].value } - }; - - &*entries[index].1 + } } /// Clears the map and drops all task-locals. - pub fn clear(&self) { - let entries = unsafe { &mut *self.entries.get() }; - entries.clear(); - } -} - -// Wrap the future into one that drops task-local variables on exit. -pub(crate) unsafe fn add_finalizer(f: impl Future) -> impl Future { - async move { - let res = f.await; - - // Abort on panic because thread-local variables behave the same way. - abort_on_panic(|| worker::get_task(|task| task.metadata().local_map.clear())); - - res + /// + /// This method is only safe to call at the end of the task. + pub unsafe fn clear(&self) { + // Since destructors may attempt to access task-locals, we musnt't hold a mutable reference + // to the `Vec` while dropping them. Instead, we first take the `Vec` out and then drop it. + let entries = (*self.entries.get()).take(); + drop(entries); } } diff --git a/src/task/worker.rs b/src/task/worker.rs deleted file mode 100644 index fc2a6e7e..00000000 --- a/src/task/worker.rs +++ /dev/null @@ -1,110 +0,0 @@ -use std::cell::Cell; -use std::ptr; - -use crossbeam_deque::Worker; - -use super::pool; -use super::task; -use super::Task; -use crate::utils::abort_on_panic; - -/// Returns a handle to the current task. -/// -/// # Panics -/// -/// This function will panic if not called within the context of a task created by [`block_on`], -/// [`spawn`], or [`Builder::spawn`]. -/// -/// [`block_on`]: fn.block_on.html -/// [`spawn`]: fn.spawn.html -/// [`Builder::spawn`]: struct.Builder.html#method.spawn -/// -/// # Examples -/// -/// ``` -/// # fn main() { async_std::task::block_on(async { -/// # -/// use async_std::task; -/// -/// println!("The name of this task is {:?}", task::current().name()); -/// # -/// # }) } -/// ``` -pub fn current() -> Task { - get_task(|task| task.clone()).expect("`task::current()` called outside the context of a task") -} - -thread_local! { - static TAG: Cell<*const task::Tag> = Cell::new(ptr::null_mut()); -} - -pub(crate) fn set_tag(tag: *const task::Tag, f: F) -> R -where - F: FnOnce() -> R, -{ - struct ResetTag<'a>(&'a Cell<*const task::Tag>); - - impl Drop for ResetTag<'_> { - fn drop(&mut self) { - self.0.set(ptr::null()); - } - } - - TAG.with(|t| { - t.set(tag); - let _guard = ResetTag(t); - - f() - }) -} - -pub(crate) fn get_task(f: F) -> Option -where - F: FnOnce(&Task) -> R, -{ - let res = TAG.try_with(|tag| unsafe { tag.get().as_ref().map(task::Tag::task).map(f) }); - - match res { - Ok(Some(val)) => Some(val), - Ok(None) | Err(_) => None, - } -} - -thread_local! { - static IS_WORKER: Cell = Cell::new(false); - static QUEUE: Cell>> = Cell::new(None); -} - -pub(crate) fn is_worker() -> bool { - IS_WORKER.with(|is_worker| is_worker.get()) -} - -fn get_queue) -> T, T>(f: F) -> T { - QUEUE.with(|queue| { - let q = queue.take().unwrap(); - let ret = f(&q); - queue.set(Some(q)); - ret - }) -} - -pub(crate) fn schedule(task: task::Runnable) { - if is_worker() { - get_queue(|q| q.push(task)); - } else { - pool::get().injector.push(task); - } - pool::get().sleepers.notify_one(); -} - -pub(crate) fn main_loop(worker: Worker) { - IS_WORKER.with(|is_worker| is_worker.set(true)); - QUEUE.with(|queue| queue.set(Some(worker))); - - loop { - match get_queue(|q| pool::get().find_task(q)) { - Some(task) => set_tag(task.tag(), || abort_on_panic(|| task.run())), - None => pool::get().sleepers.wait(), - } - } -} diff --git a/src/task/yield_now.rs b/src/task/yield_now.rs new file mode 100644 index 00000000..40306966 --- /dev/null +++ b/src/task/yield_now.rs @@ -0,0 +1,51 @@ +use std::pin::Pin; +use std::future::Future; + +use crate::task::{Context, Poll}; + +/// Cooperatively gives up a timeslice to the task scheduler. +/// +/// Calling this function will move the currently executing future to the back +/// of the execution queue, making room for other futures to execute. This is +/// especially useful after running CPU-intensive operations inside a future. +/// +/// See also [`task::spawn_blocking`]. +/// +/// [`task::spawn_blocking`]: fn.spawn_blocking.html +/// +/// # Examples +/// +/// Basic usage: +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::task; +/// +/// task::yield_now().await; +/// # +/// # }) +/// ``` +#[inline] +pub async fn yield_now() { + YieldNow(false).await +} + +struct YieldNow(bool); + +impl Future for YieldNow { + type Output = (); + + // The futures executor is implemented as a FIFO queue, so all this future + // does is re-schedule the future back to the end of the queue, giving room + // for other futures to progress. + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if !self.0 { + self.0 = true; + cx.waker().wake_by_ref(); + Poll::Pending + } else { + Poll::Ready(()) + } + } +} diff --git a/src/unit/extend.rs b/src/unit/extend.rs new file mode 100644 index 00000000..55c8e0d0 --- /dev/null +++ b/src/unit/extend.rs @@ -0,0 +1,19 @@ +use std::pin::Pin; + +use crate::prelude::*; +use crate::stream::{self, IntoStream}; + +impl stream::Extend<()> for () { + fn extend<'a, T: IntoStream + 'a>( + &'a mut self, + stream: T, + ) -> Pin + 'a>> { + let stream = stream.into_stream(); + + Box::pin(async move { + pin_utils::pin_mut!(stream); + + while let Some(_) = stream.next().await {} + }) + } +} diff --git a/src/unit/from_stream.rs b/src/unit/from_stream.rs index a238982d..da216e22 100644 --- a/src/unit/from_stream.rs +++ b/src/unit/from_stream.rs @@ -5,12 +5,9 @@ use crate::stream::{FromStream, IntoStream}; impl FromStream<()> for () { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { Box::pin(stream.into_stream().for_each(|_| ())) } } diff --git a/src/unit/mod.rs b/src/unit/mod.rs index cb8063d0..bbf28987 100644 --- a/src/unit/mod.rs +++ b/src/unit/mod.rs @@ -4,3 +4,4 @@ //! asynchronously with values of type `()`. mod from_stream; +mod extend; diff --git a/src/utils.rs b/src/utils.rs index bdf0f3b5..7d253b49 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,26 +1,166 @@ -use std::mem; -use std::process; - /// Calls a function and aborts if it panics. /// /// This is useful in unsafe code where we can't recover from panics. +#[cfg(feature = "default")] #[inline] pub fn abort_on_panic(f: impl FnOnce() -> T) -> T { struct Bomb; impl Drop for Bomb { fn drop(&mut self) { - process::abort(); + std::process::abort(); } } let bomb = Bomb; let t = f(); - mem::forget(bomb); + std::mem::forget(bomb); t } -/// Defines an extension trait for a base trait from the `futures` crate. +/// Generates a random number in `0..n`. +#[cfg(feature = "default")] +pub fn random(n: u32) -> u32 { + use std::cell::Cell; + use std::num::Wrapping; + + thread_local! { + static RNG: Cell> = { + // Take the address of a local value as seed. + let mut x = 0i32; + let r = &mut x; + let addr = r as *mut i32 as usize; + Cell::new(Wrapping(addr as u32)) + } + } + + RNG.with(|rng| { + // This is the 32-bit variant of Xorshift. + // + // Source: https://en.wikipedia.org/wiki/Xorshift + let mut x = rng.get(); + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + rng.set(x); + + // This is a fast alternative to `x % n`. + // + // Author: Daniel Lemire + // Source: https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ + ((u64::from(x.0)).wrapping_mul(u64::from(n)) >> 32) as u32 + }) +} + +/// Add additional context to errors +pub(crate) trait Context { + fn context(self, message: impl Fn() -> String) -> Self; +} + +/// Defers evaluation of a block of code until the end of the scope. +#[cfg(feature = "default")] +#[doc(hidden)] +macro_rules! defer { + ($($body:tt)*) => { + let _guard = { + pub struct Guard(Option); + + impl Drop for Guard { + fn drop(&mut self) { + (self.0).take().map(|f| f()); + } + } + + Guard(Some(|| { + let _ = { $($body)* }; + })) + }; + }; +} + +/// Declares unstable items. +#[doc(hidden)] +macro_rules! cfg_unstable { + ($($item:item)*) => { + $( + #[cfg(feature = "unstable")] + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + $item + )* + } +} + +/// Declares Unix-specific items. +#[doc(hidden)] +macro_rules! cfg_unix { + ($($item:item)*) => { + $( + #[cfg(any(unix, feature = "docs"))] + #[cfg_attr(feature = "docs", doc(cfg(unix)))] + $item + )* + } +} + +/// Declares Windows-specific items. +#[doc(hidden)] +macro_rules! cfg_windows { + ($($item:item)*) => { + $( + #[cfg(any(windows, feature = "docs"))] + #[cfg_attr(feature = "docs", doc(cfg(windows)))] + $item + )* + } +} + +/// Declares items when the "docs" feature is enabled. +#[doc(hidden)] +macro_rules! cfg_docs { + ($($item:item)*) => { + $( + #[cfg(feature = "docs")] + $item + )* + } +} + +/// Declares items when the "docs" feature is disabled. +#[doc(hidden)] +macro_rules! cfg_not_docs { + ($($item:item)*) => { + $( + #[cfg(not(feature = "docs"))] + $item + )* + } +} + +/// Declares std items. +#[allow(unused_macros)] +#[doc(hidden)] +macro_rules! cfg_std { + ($($item:item)*) => { + $( + #[cfg(feature = "std")] + $item + )* + } +} + +/// Declares default items. +#[allow(unused_macros)] +#[doc(hidden)] +macro_rules! cfg_default { + ($($item:item)*) => { + $( + #[cfg(feature = "default")] + $item + )* + } +} + +/// Defines an extension trait for a base trait. /// /// In generated docs, the base trait will contain methods from the extension trait. In actual /// code, the base trait will be re-exported and the extension trait will be hidden. We then @@ -29,16 +169,20 @@ pub fn abort_on_panic(f: impl FnOnce() -> T) -> T { /// Inside invocations of this macro, we write a definitions that looks similar to the final /// rendered docs, and the macro then generates all the boilerplate for us. #[doc(hidden)] -#[macro_export] macro_rules! extension_trait { ( // Interesting patterns: // - `$name`: trait name that gets rendered in the docs // - `$ext`: name of the hidden extension trait - // - `$base`: base trait from the `futures` crate - $(#[$attr:meta])* - pub trait $name:ident [$ext:ident: $base:path] { - $($body:tt)* + // - `$base`: base trait + #[doc = $doc:tt] + pub trait $name:ident { + $($body_base:tt)* + } + + #[doc = $doc_ext:tt] + pub trait $ext:ident: $base:path { + $($body_ext:tt)* } // Shim trait impls that only appear in docs. @@ -58,11 +202,11 @@ macro_rules! extension_trait { pub struct ImplFuture<'a, T>(std::marker::PhantomData<&'a T>); } - // Render a fake trait containing all methods from the base trait and the extension trait. + // Render a fake trait combining the bodies of the base trait and the extension trait. #[cfg(feature = "docs")] - $(#[$attr])* + #[doc = $doc] pub trait $name { - extension_trait!(@doc () $($body)*); + extension_trait!(@doc () $($body_base)* $($body_ext)*); } // When not rendering docs, re-export the base trait from the futures crate. @@ -70,51 +214,32 @@ macro_rules! extension_trait { pub use $base as $name; // The extension trait that adds methods to any type implementing the base trait. - $(#[$attr])* - pub trait $ext: $base { - extension_trait!(@ext () $($body)*); + #[doc = $doc_ext] + pub trait $ext: $name { + extension_trait!(@ext () $($body_ext)*); } // Blanket implementation of the extension trait for any type implementing the base trait. - impl $ext for T {} + impl $ext for T {} // Shim trait impls that only appear in docs. $(#[cfg(feature = "docs")] $imp)* }; - // Parse an associated type. - (@doc ($($head:tt)*) type $name:ident; $($tail:tt)*) => { - extension_trait!(@doc ($($head)* type $name;) $($tail)*); - }; - (@ext ($($head:tt)*) type $ident:ty; $($tail:tt)*) => { - extension_trait!(@ext ($($head)*) $($tail)*); - }; - - // Parse a required method. - (@doc ($($head:tt)*) fn $name:ident $args:tt $(-> $ret:ty)?; $($tail:tt)*) => { - extension_trait!(@doc ($($head)* fn $name $args $(-> $ret)?;) $($tail)*); - }; - (@ext ($($head:tt)*) fn $name:ident $args:tt $(-> $ret:ty)?; $($tail:tt)*) => { - extension_trait!(@ext ($($head)*) $($tail)*); - }; - - // Parse a provided method that exists in the base trait. - (@doc ($($head:tt)*) fn $name:ident $args:tt $(-> $ret:ty)? { $($body:tt)* } $($tail:tt)*) => { - extension_trait!(@doc ($($head)* fn $name $args $(-> $ret)? { $($body)* }) $($tail)*); - }; - (@ext ($($head:tt)*) fn $name:ident $args:tt $(-> $ret:ty)? { $($body:tt)* } $($tail:tt)*) => { - extension_trait!(@ext ($($head)*) $($tail)*); + // Optimization: expand `$head` eagerly before starting a new method definition. + (@ext ($($head:tt)*) #[doc = $d:literal] $($tail:tt)*) => { + $($head)* extension_trait!(@ext (#[doc = $d]) $($tail)*); }; - // Parse the return type in an extension method where the future doesn't borrow. - (@doc ($($head:tt)*) -> impl Future [$f:ty] $($tail:tt)*) => { + // Parse the return type in an extension method. + (@doc ($($head:tt)*) -> impl Future $(+ $lt:lifetime)? [$f:ty] $($tail:tt)*) => { extension_trait!(@doc ($($head)* -> owned::ImplFuture<$out>) $($tail)*); }; - (@ext ($($head:tt)*) -> impl Future [$f:ty] $($tail:tt)*) => { + (@ext ($($head:tt)*) -> impl Future $(+ $lt:lifetime)? [$f:ty] $($tail:tt)*) => { extension_trait!(@ext ($($head)* -> $f) $($tail)*); }; - // Parse the return type in an extension method where the future borrows its environment. + // Parse the return type in an extension method. (@doc ($($head:tt)*) -> impl Future + $lt:lifetime [$f:ty] $($tail:tt)*) => { extension_trait!(@doc ($($head)* -> borrowed::ImplFuture<$lt, $out>) $($tail)*); }; @@ -122,7 +247,7 @@ macro_rules! extension_trait { extension_trait!(@ext ($($head)* -> $f) $($tail)*); }; - // Parse a token that doesn't fit into any of the previous patterns. + // Parse a token. (@doc ($($head:tt)*) $token:tt $($tail:tt)*) => { extension_trait!(@doc ($($head)* $token) $($tail)*); }; @@ -133,6 +258,12 @@ macro_rules! extension_trait { // Handle the end of the token list. (@doc ($($head:tt)*)) => { $($head)* }; (@ext ($($head:tt)*)) => { $($head)* }; -} -pub use crate::extension_trait; + // Parse imports at the beginning of the macro. + ($import:item $($tail:tt)*) => { + #[cfg(feature = "docs")] + $import + + extension_trait!($($tail)*); + }; +} diff --git a/src/vec/extend.rs b/src/vec/extend.rs index ecf68c83..302fc7a8 100644 --- a/src/vec/extend.rs +++ b/src/vec/extend.rs @@ -1,10 +1,10 @@ use std::pin::Pin; use crate::prelude::*; -use crate::stream::{Extend, IntoStream}; +use crate::stream::{self, IntoStream}; -impl Extend for Vec { - fn stream_extend<'a, S: IntoStream + 'a>( +impl stream::Extend for Vec { + fn extend<'a, S: IntoStream + 'a>( &'a mut self, stream: S, ) -> Pin + 'a>> { diff --git a/src/vec/from_stream.rs b/src/vec/from_stream.rs index b326b04c..e88e8202 100644 --- a/src/vec/from_stream.rs +++ b/src/vec/from_stream.rs @@ -3,23 +3,22 @@ use std::pin::Pin; use std::rc::Rc; use std::sync::Arc; -use crate::stream::{Extend, FromStream, IntoStream}; +use crate::prelude::*; +use crate::stream::{self, FromStream, IntoStream}; impl FromStream for Vec { #[inline] fn from_stream<'a, S: IntoStream>( stream: S, - ) -> Pin + 'a>> + ) -> Pin + 'a>> where ::IntoStream: 'a, { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - let mut out = vec![]; - out.stream_extend(stream).await; + stream::extend(&mut out, stream).await; out }) } @@ -27,17 +26,12 @@ impl FromStream for Vec { impl<'b, T: Clone> FromStream for Cow<'b, [T]> { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - Cow::Owned(FromStream::from_stream(stream).await) }) } @@ -45,17 +39,12 @@ impl<'b, T: Clone> FromStream for Cow<'b, [T]> { impl FromStream for Box<[T]> { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - Vec::from_stream(stream).await.into_boxed_slice() }) } @@ -63,17 +52,12 @@ impl FromStream for Box<[T]> { impl FromStream for Rc<[T]> { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - Vec::from_stream(stream).await.into() }) } @@ -81,17 +65,12 @@ impl FromStream for Rc<[T]> { impl FromStream for Arc<[T]> { #[inline] - fn from_stream<'a, S: IntoStream>( + fn from_stream<'a, S: IntoStream + 'a>( stream: S, - ) -> Pin + 'a>> - where - ::IntoStream: 'a, - { + ) -> Pin + 'a>> { let stream = stream.into_stream(); Box::pin(async move { - pin_utils::pin_mut!(stream); - Vec::from_stream(stream).await.into() }) } diff --git a/tests/buf_writer.rs b/tests/buf_writer.rs index 188cd8c8..5df90e08 100644 --- a/tests/buf_writer.rs +++ b/tests/buf_writer.rs @@ -4,6 +4,7 @@ use async_std::task; #[test] fn test_buffered_writer() { + #![allow(clippy::cognitive_complexity)] task::block_on(async { let inner = Vec::new(); let mut writer = BufWriter::with_capacity(2, inner); @@ -48,13 +49,13 @@ fn test_buffered_writer() { } #[test] -fn test_buffered_writer_inner_into_inner_does_not_flush() { +fn test_buffered_writer_inner_into_inner_flushes() { task::block_on(async { let mut w = BufWriter::with_capacity(3, Vec::new()); w.write(&[0, 1]).await.unwrap(); assert_eq!(*w.get_ref(), []); - let w = w.into_inner(); - assert_eq!(w, []); + let w = w.into_inner().await.unwrap(); + assert_eq!(w, [0, 1]); }) } diff --git a/tests/channel.rs b/tests/channel.rs new file mode 100644 index 00000000..34bd888f --- /dev/null +++ b/tests/channel.rs @@ -0,0 +1,359 @@ +#![cfg(feature = "unstable")] + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; +use std::time::Duration; + +use async_std::sync::channel; +use async_std::task; +use rand::{thread_rng, Rng}; + +fn ms(ms: u64) -> Duration { + Duration::from_millis(ms) +} + +#[test] +fn smoke() { + task::block_on(async { + let (s, r) = channel(1); + + s.send(7).await; + assert_eq!(r.recv().await, Some(7)); + + s.send(8).await; + assert_eq!(r.recv().await, Some(8)); + + drop(s); + assert_eq!(r.recv().await, None); + }); + + task::block_on(async { + let (s, r) = channel(10); + drop(r); + s.send(1).await; + }); +} + +#[test] +fn capacity() { + for i in 1..10 { + let (s, r) = channel::<()>(i); + assert_eq!(s.capacity(), i); + assert_eq!(r.capacity(), i); + } +} + +#[test] +fn len_empty_full() { + #![allow(clippy::cognitive_complexity)] + task::block_on(async { + let (s, r) = channel(2); + + assert_eq!(s.len(), 0); + assert_eq!(s.is_empty(), true); + assert_eq!(s.is_full(), false); + assert_eq!(r.len(), 0); + assert_eq!(r.is_empty(), true); + assert_eq!(r.is_full(), false); + + s.send(()).await; + + assert_eq!(s.len(), 1); + assert_eq!(s.is_empty(), false); + assert_eq!(s.is_full(), false); + assert_eq!(r.len(), 1); + assert_eq!(r.is_empty(), false); + assert_eq!(r.is_full(), false); + + s.send(()).await; + + assert_eq!(s.len(), 2); + assert_eq!(s.is_empty(), false); + assert_eq!(s.is_full(), true); + assert_eq!(r.len(), 2); + assert_eq!(r.is_empty(), false); + assert_eq!(r.is_full(), true); + + r.recv().await; + + assert_eq!(s.len(), 1); + assert_eq!(s.is_empty(), false); + assert_eq!(s.is_full(), false); + assert_eq!(r.len(), 1); + assert_eq!(r.is_empty(), false); + assert_eq!(r.is_full(), false); + }) +} + +#[test] +fn recv() { + task::block_on(async { + let (s, r) = channel(100); + + task::spawn(async move { + assert_eq!(r.recv().await, Some(7)); + task::sleep(ms(1000)).await; + assert_eq!(r.recv().await, Some(8)); + task::sleep(ms(1000)).await; + assert_eq!(r.recv().await, Some(9)); + assert_eq!(r.recv().await, None); + }); + + task::sleep(ms(1500)).await; + s.send(7).await; + s.send(8).await; + s.send(9).await; + }) +} + +#[test] +fn send() { + task::block_on(async { + let (s, r) = channel(1); + + task::spawn(async move { + s.send(7).await; + task::sleep(ms(1000)).await; + s.send(8).await; + task::sleep(ms(1000)).await; + s.send(9).await; + task::sleep(ms(1000)).await; + s.send(10).await; + }); + + task::sleep(ms(1500)).await; + assert_eq!(r.recv().await, Some(7)); + assert_eq!(r.recv().await, Some(8)); + assert_eq!(r.recv().await, Some(9)); + }) +} + +#[test] +fn recv_after_disconnect() { + task::block_on(async { + let (s, r) = channel(100); + + s.send(1).await; + s.send(2).await; + s.send(3).await; + + drop(s); + + assert_eq!(r.recv().await, Some(1)); + assert_eq!(r.recv().await, Some(2)); + assert_eq!(r.recv().await, Some(3)); + assert_eq!(r.recv().await, None); + }) +} + +#[test] +fn len() { + const COUNT: usize = 25_000; + const CAP: usize = 1000; + + task::block_on(async { + let (s, r) = channel(CAP); + + assert_eq!(s.len(), 0); + assert_eq!(r.len(), 0); + + for _ in 0..CAP / 10 { + for i in 0..50 { + s.send(i).await; + assert_eq!(s.len(), i + 1); + } + + for i in 0..50 { + r.recv().await; + assert_eq!(r.len(), 50 - i - 1); + } + } + + assert_eq!(s.len(), 0); + assert_eq!(r.len(), 0); + + for i in 0..CAP { + s.send(i).await; + assert_eq!(s.len(), i + 1); + } + + for _ in 0..CAP { + r.recv().await.unwrap(); + } + + assert_eq!(s.len(), 0); + assert_eq!(r.len(), 0); + + let child = task::spawn({ + let r = r.clone(); + async move { + for i in 0..COUNT { + assert_eq!(r.recv().await, Some(i)); + let len = r.len(); + assert!(len <= CAP); + } + } + }); + + for i in 0..COUNT { + s.send(i).await; + let len = s.len(); + assert!(len <= CAP); + } + + child.await; + + assert_eq!(s.len(), 0); + assert_eq!(r.len(), 0); + }) +} + +#[test] +fn disconnect_wakes_receiver() { + task::block_on(async { + let (s, r) = channel::<()>(1); + + let child = task::spawn(async move { + assert_eq!(r.recv().await, None); + }); + + task::sleep(ms(1000)).await; + drop(s); + + child.await; + }) +} + +#[test] +fn spsc() { + const COUNT: usize = 100_000; + + task::block_on(async { + let (s, r) = channel(3); + + let child = task::spawn(async move { + for i in 0..COUNT { + assert_eq!(r.recv().await, Some(i)); + } + assert_eq!(r.recv().await, None); + }); + + for i in 0..COUNT { + s.send(i).await; + } + drop(s); + + child.await; + }) +} + +#[test] +fn mpmc() { + const COUNT: usize = 25_000; + const TASKS: usize = 4; + + task::block_on(async { + let (s, r) = channel::(3); + let v = (0..COUNT).map(|_| AtomicUsize::new(0)).collect::>(); + let v = Arc::new(v); + + let mut tasks = Vec::new(); + + for _ in 0..TASKS { + let r = r.clone(); + let v = v.clone(); + tasks.push(task::spawn(async move { + for _ in 0..COUNT { + let n = r.recv().await.unwrap(); + v[n].fetch_add(1, Ordering::SeqCst); + } + })); + } + + for _ in 0..TASKS { + let s = s.clone(); + tasks.push(task::spawn(async move { + for i in 0..COUNT { + s.send(i).await; + } + })); + } + + for t in tasks { + t.await; + } + + for c in v.iter() { + assert_eq!(c.load(Ordering::SeqCst), TASKS); + } + }); +} + +#[test] +fn oneshot() { + const COUNT: usize = 10_000; + + task::block_on(async { + for _ in 0..COUNT { + let (s, r) = channel(1); + + let c1 = task::spawn(async move { r.recv().await.unwrap() }); + let c2 = task::spawn(async move { s.send(0).await }); + + c1.await; + c2.await; + } + }) +} + +#[test] +fn drops() { + const RUNS: usize = 100; + + static DROPS: AtomicUsize = AtomicUsize::new(0); + + #[derive(Debug, PartialEq)] + struct DropCounter; + + impl Drop for DropCounter { + fn drop(&mut self) { + DROPS.fetch_add(1, Ordering::SeqCst); + } + } + + let mut rng = thread_rng(); + + for _ in 0..RUNS { + task::block_on(async { + let steps = rng.gen_range(0, 10_000); + let additional = rng.gen_range(0, 50); + + DROPS.store(0, Ordering::SeqCst); + let (s, r) = channel::(50); + + let child = task::spawn({ + let r = r.clone(); + async move { + for _ in 0..steps { + r.recv().await.unwrap(); + } + } + }); + + for _ in 0..steps { + s.send(DropCounter).await; + } + + child.await; + + for _ in 0..additional { + s.send(DropCounter).await; + } + + assert_eq!(DROPS.load(Ordering::SeqCst), steps); + drop(s); + drop(r); + assert_eq!(DROPS.load(Ordering::SeqCst), steps + additional); + }) + } +} diff --git a/tests/rwlock.rs b/tests/rwlock.rs index ff25e862..370dcb9f 100644 --- a/tests/rwlock.rs +++ b/tests/rwlock.rs @@ -13,7 +13,7 @@ use futures::channel::mpsc; /// Generates a random number in `0..n`. pub fn random(n: u32) -> u32 { thread_local! { - static RNG: Cell> = Cell::new(Wrapping(1406868647)); + static RNG: Cell> = Cell::new(Wrapping(1_406_868_647)); } RNG.with(|rng| { diff --git a/tests/stream.rs b/tests/stream.rs new file mode 100644 index 00000000..42a6191f --- /dev/null +++ b/tests/stream.rs @@ -0,0 +1,100 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; + +use pin_project_lite::pin_project; + +use async_std::prelude::*; +use async_std::stream; +use async_std::sync::channel; +use async_std::task; + +#[test] +/// Checks that streams are merged fully even if one of the components +/// experiences delay. +fn merging_delayed_streams_work() { + let (sender, receiver) = channel::(10); + + let mut s = receiver.merge(stream::empty()); + let t = task::spawn(async move { + let mut xs = Vec::new(); + while let Some(x) = s.next().await { + xs.push(x); + } + xs + }); + + task::block_on(async move { + task::sleep(std::time::Duration::from_millis(500)).await; + sender.send(92).await; + drop(sender); + let xs = t.await; + assert_eq!(xs, vec![92]) + }); + + let (sender, receiver) = channel::(10); + + let mut s = stream::empty().merge(receiver); + let t = task::spawn(async move { + let mut xs = Vec::new(); + while let Some(x) = s.next().await { + xs.push(x); + } + xs + }); + + task::block_on(async move { + task::sleep(std::time::Duration::from_millis(500)).await; + sender.send(92).await; + drop(sender); + let xs = t.await; + assert_eq!(xs, vec![92]) + }); +} + +pin_project! { + /// The opposite of `Fuse`: makes the stream panic if polled after termination. + struct Explode { + #[pin] + done: bool, + #[pin] + inner: S, + } +} + +impl Stream for Explode { + type Item = S::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + if *this.done { + panic!("KABOOM!") + } + let res = this.inner.poll_next(cx); + if let Poll::Ready(None) = &res { + *this.done = true; + } + res + } +} + +fn explode(s: S) -> Explode { + Explode { + done: false, + inner: s, + } +} + +#[test] +fn merge_works_with_unfused_streams() { + let s1 = explode(stream::once(92)); + let s2 = explode(stream::once(92)); + let mut s = s1.merge(s2); + let xs = task::block_on(async move { + let mut xs = Vec::new(); + while let Some(x) = s.next().await { + xs.push(x) + } + xs + }); + assert_eq!(xs, vec![92, 92]); +} diff --git a/tests/tcp.rs b/tests/tcp.rs index c8281d71..00fa3a04 100644 --- a/tests/tcp.rs +++ b/tests/tcp.rs @@ -49,3 +49,48 @@ fn incoming_read() -> io::Result<()> { Ok(()) }) } + +#[test] +fn smoke_std_stream_to_async_listener() -> io::Result<()> { + use std::io::Write; + + task::block_on(async { + let listener = TcpListener::bind("127.0.0.1:0").await?; + let addr = listener.local_addr()?; + + let mut std_stream = std::net::TcpStream::connect(&addr)?; + std_stream.write_all(THE_WINTERS_TALE)?; + + let mut buf = vec![0; 1024]; + let mut incoming = listener.incoming(); + let mut stream = incoming.next().await.unwrap()?; + + let n = stream.read(&mut buf).await?; + assert_eq!(&buf[..n], THE_WINTERS_TALE); + + Ok(()) + }) +} + +#[test] +fn smoke_async_stream_to_std_listener() -> io::Result<()> { + use std::io::Read; + + let std_listener = std::net::TcpListener::bind("127.0.0.1:0")?; + let addr = std_listener.local_addr()?; + + task::block_on(async move { + let mut stream = TcpStream::connect(&addr).await?; + stream.write_all(THE_WINTERS_TALE).await?; + io::Result::Ok(()) + })?; + + let mut buf = vec![0; 1024]; + let mut incoming = std_listener.incoming(); + let mut stream = incoming.next().unwrap()?; + + let n = stream.read(&mut buf).unwrap(); + assert_eq!(&buf[..n], THE_WINTERS_TALE); + + Ok(()) +} diff --git a/tests/verbose_errors.rs b/tests/verbose_errors.rs new file mode 100644 index 00000000..15630928 --- /dev/null +++ b/tests/verbose_errors.rs @@ -0,0 +1,31 @@ +use async_std::{fs, io, net::ToSocketAddrs, task}; + +#[test] +fn open_file() { + task::block_on(async { + let non_existing_file = "/ashjudlkahasdasdsikdhajik/asdasdasdasdasdasd/fjuiklashdbflasas"; + let res = fs::File::open(non_existing_file).await; + match res { + Ok(_) => panic!("Found file with random name: We live in a simulation"), + Err(e) => assert_eq!( + "Could not open `/ashjudlkahasdasdsikdhajik/asdasdasdasdasdasd/fjuiklashdbflasas`", + &format!("{}", e) + ), + } + }) +} + +#[test] +fn resolve_address() { + task::block_on(async { + let non_existing_addr = "ashjudlkahasdasdsikdhajik.asdasdasdasdasdasd.fjuiklashdbflasas:80"; + let res: Result<_, io::Error> = non_existing_addr.to_socket_addrs().await; + match res { + Ok(_) => panic!("Found address with random name: We live in a simulation"), + Err(e) => assert_eq!( + "could not resolve address `\"ashjudlkahasdasdsikdhajik.asdasdasdasdasdasd.fjuiklashdbflasas:80\"`", + &format!("{}", e) + ), + } + }) +}