diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59f149de..b92e50b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,20 +1,28 @@ -on: pull_request +name: CI + +on: + pull_request: + push: + branches: + - staging + - trying jobs: build_and_test: - name: Build and test on ${{ matrix.os }} + name: Build and test runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] + rust: [nightly] steps: - uses: actions/checkout@master - - name: Install nightly + - name: Install ${{ matrix.rust }} uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: ${{ matrix.rust }} override: true - name: check @@ -41,12 +49,22 @@ 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 }} + override: true + - name: setup run: | - rustup default nightly rustup component add rustfmt test -x $HOME/.cargo/bin/mdbook || ./ci/install-mdbook.sh rustc --version + - name: mdbook run: | mdbook build docs @@ -54,4 +72,22 @@ jobs: run: cargo fmt --all -- --check - name: Docs - run: cargo doc --features docs,unstable + 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 }} diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml deleted file mode 100644 index c26affbf..00000000 --- a/.github/workflows/clippy.yml +++ /dev/null @@ -1,20 +0,0 @@ -on: pull_request - -name: Clippy check -jobs: - 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 }} diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0249f8ed..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,unstable - - - 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 cdce1340..77ab025a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,59 @@ and this project adheres to [Semantic Versioning](https://book.async.rs/overview ## [Unreleased] +# [0.99.9] - 2019-10-08 + +This patch upgrades our `futures-rs` version, allowing us to build on the 1.39 +beta. Additionally we've introduced `map` and `for_each` to `Stream`. And we've +added about a dozen new `FromStream` implementations for `std` types, bringing +us up to par with std's `FromIterator` implementations. + +And finally we've added a new "unstable" `task::blocking` function which can be +used to convert blocking code into async code using a threadpool. We've been +using this internally for a while now to async-std to power our `fs` and +`net::SocketAddr` implementations. With this patch userland code now finally has +access to this too. + +## Example + +__Create a stream of tuples, and collect into a hashmap__ +```rust +let a = stream::once(1u8); +let b = stream::once(0u8); + +let s = a.zip(b); + +let map: HashMap = s.collect().await; +assert_eq!(map.get(&1), Some(&0u8)); +``` + +__Spawn a blocking task on a dedicated threadpool__ +```rust +task::blocking(async { + println!("long-running task here"); +}).await; +``` + +## Added + +- Added `stream::Stream::map` +- Added `stream::Stream::for_each` +- Added `stream::Stream::try_for_each` +- Added `task::blocking` as "unstable" +- Added `FromStream` for all `std::{option, collections, result, string, sync}` types. +- Added the `path` submodule as "unstable". + +## Changed + +- Updated `futures-preview` to `0.3.0-alpha.19`, allowing us to build on `rustc 1.39.0-beta`. +- As a consequence of this upgrade, all of our concrete stream implementations + now make use of `Stream::size_hint` to optimize internal allocations. +- We now use GitHub Actions through [actions-rs](https://github.com/actions-rs), + in addition to Travis CI. We intend to fully switch in the near future. +- Fixed a bug introduced in 0.99.6 where Unix Domain Listeners would sometimes become unresponsive. +- Updated our `sync::Barrier` docs to match std. +- Updated our `stream::FromStream` docs to match std's `FromIterator`. + # [0.99.8] - 2019-09-28 ## Added @@ -130,7 +183,8 @@ and this project adheres to [Semantic Versioning](https://book.async.rs/overview - Initial beta release -[Unreleased]: https://github.com/async-rs/async-std/compare/v0.99.8...HEAD +[Unreleased]: https://github.com/async-rs/async-std/compare/v0.99.9...HEAD +[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 [0.99.6]: https://github.com/async-rs/async-std/compare/v0.99.5...v0.99.6 diff --git a/Cargo.toml b/Cargo.toml index 5b988bb4..dc319f71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "async-std" -version = "0.99.8" +version = "0.99.9" authors = [ "Stjepan Glavina ", "Yoshua Wuyts ", @@ -32,7 +32,7 @@ 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" +futures-timer = "1.0.2" lazy_static = "1.4.0" log = { version = "0.4.8", features = ["kv_unstable"] } memchr = "2.2.1" diff --git a/README.md b/README.md index 978e1c0e..7aeaed86 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,78 @@ -# 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 times. +- __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 +## Examples ```rust use async_std::task; @@ -47,96 +84,48 @@ fn main() { } ``` -## Low-Friction Sockets with Built-In Timeouts - -```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 -} - -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); - }); -} -``` - -## 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. -## Take a look around +[`examples`]: https://github.com/async-rs/async-std/tree/master/examples -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 doc --features docs.rs --open -``` +## Installation -Check out the [examples](examples). To run an example: +With [cargo add][cargo-add] installed run: +```sh +$ cargo add async-std ``` -cargo 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/bors.toml b/bors.toml index 359f8947..732f8fcf 100644 --- a/bors.toml +++ b/bors.toml @@ -1 +1,7 @@ -status = ["continuous-integration/travis-ci/push"] +status = [ + "Build and test (ubuntu-latest, nightly)", + "Build and test (windows-latest, nightly)", + "Build and test (macOS-latest, nightly)", + "Checking fmt and docs", + "Clippy check", +] diff --git a/src/collections/binary_heap/extend.rs b/src/collections/binary_heap/extend.rs index 8538f9b0..4503fe39 100644 --- a/src/collections/binary_heap/extend.rs +++ b/src/collections/binary_heap/extend.rs @@ -10,9 +10,9 @@ impl Extend for BinaryHeap { stream: S, ) -> Pin + 'a>> { let stream = stream.into_stream(); - //TODO: Add this back in when size_hint is added to Stream/StreamExt - //let (lower_bound, _) = stream.size_hint(); - //self.reserve(lower_bound); + + self.reserve(stream.size_hint().0); + Box::pin(stream.for_each(move |item| self.push(item))) } } diff --git a/src/collections/hash_map/extend.rs b/src/collections/hash_map/extend.rs index 5f96b8ab..c34bb9ed 100644 --- a/src/collections/hash_map/extend.rs +++ b/src/collections/hash_map/extend.rs @@ -23,13 +23,12 @@ where // hint lower bound if the map is empty. Otherwise reserve half the hint (rounded up), so // the map will only resize twice in the worst case. - //TODO: Add this back in when size_hint is added to Stream/StreamExt - //let reserve = if self.is_empty() { - // stream.size_hint().0 - //} else { - // (stream.size_hint().0 + 1) / 2 - //}; - //self.reserve(reserve); + let additional = if self.is_empty() { + stream.size_hint().0 + } else { + (stream.size_hint().0 + 1) / 2 + }; + self.reserve(additional); Box::pin(stream.for_each(move |(k, v)| { self.insert(k, v); diff --git a/src/collections/hash_set/extend.rs b/src/collections/hash_set/extend.rs index 72400e11..123e844e 100644 --- a/src/collections/hash_set/extend.rs +++ b/src/collections/hash_set/extend.rs @@ -26,13 +26,12 @@ where // hint lower bound if the map is empty. Otherwise reserve half the hint (rounded up), so // the map will only resize twice in the worst case. - //TODO: Add this back in when size_hint is added to Stream/StreamExt - //let reserve = if self.is_empty() { - // stream.size_hint().0 - //} else { - // (stream.size_hint().0 + 1) / 2 - //}; - //self.reserve(reserve); + let additional = if self.is_empty() { + stream.size_hint().0 + } else { + (stream.size_hint().0 + 1) / 2 + }; + self.reserve(additional); Box::pin(stream.for_each(move |item| { self.insert(item); diff --git a/src/collections/linked_list/extend.rs b/src/collections/linked_list/extend.rs index 567a92d3..63a1a97c 100644 --- a/src/collections/linked_list/extend.rs +++ b/src/collections/linked_list/extend.rs @@ -10,9 +10,6 @@ impl Extend for LinkedList { stream: S, ) -> Pin + 'a>> { let stream = stream.into_stream(); - //TODO: Add this back in when size_hint is added to Stream/StreamExt - //let (lower_bound, _) = stream.size_hint(); - //self.reserve(lower_bound); Box::pin(stream.for_each(move |item| self.push_back(item))) } } diff --git a/src/collections/vec_deque/extend.rs b/src/collections/vec_deque/extend.rs index d034bdcd..17e396ab 100644 --- a/src/collections/vec_deque/extend.rs +++ b/src/collections/vec_deque/extend.rs @@ -10,9 +10,9 @@ impl Extend for VecDeque { stream: S, ) -> Pin + 'a>> { let stream = stream.into_stream(); - //TODO: Add this back in when size_hint is added to Stream/StreamExt - //let (lower_bound, _) = stream.size_hint(); - //self.reserve(lower_bound); + + self.reserve(stream.size_hint().0); + Box::pin(stream.for_each(move |item| self.push_back(item))) } } diff --git a/src/fs/canonicalize.rs b/src/fs/canonicalize.rs index c484aeeb..601d477c 100644 --- a/src/fs/canonicalize.rs +++ b/src/fs/canonicalize.rs @@ -1,6 +1,5 @@ -use std::path::{Path, PathBuf}; - use crate::io; +use crate::path::{Path, PathBuf}; use crate::task::blocking; /// Returns the canonical form of a path. @@ -33,5 +32,5 @@ 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 + blocking::spawn(move || std::fs::canonicalize(&path).map(Into::into)).await } diff --git a/src/fs/copy.rs b/src/fs/copy.rs index fc3fd3e6..733fb64b 100644 --- a/src/fs/copy.rs +++ b/src/fs/copy.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Copies the contents and permissions of a file to a new location. @@ -42,5 +41,5 @@ 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 + blocking::spawn(move || std::fs::copy(&from, &to)).await } diff --git a/src/fs/create_dir.rs b/src/fs/create_dir.rs index aa2d5724..740d303c 100644 --- a/src/fs/create_dir.rs +++ b/src/fs/create_dir.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Creates a new directory. @@ -35,5 +34,5 @@ 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 + blocking::spawn(move || std::fs::create_dir(path)).await } diff --git a/src/fs/create_dir_all.rs b/src/fs/create_dir_all.rs index 33176f75..76604de7 100644 --- a/src/fs/create_dir_all.rs +++ b/src/fs/create_dir_all.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Creates a new directory and all of its parents if they are missing. @@ -30,5 +29,5 @@ 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 + blocking::spawn(move || std::fs::create_dir_all(path)).await } diff --git a/src/fs/dir_builder.rs b/src/fs/dir_builder.rs index 0de230db..6dfddcb3 100644 --- a/src/fs/dir_builder.rs +++ b/src/fs/dir_builder.rs @@ -1,9 +1,9 @@ -use std::path::Path; +use std::future::Future; use cfg_if::cfg_if; -use crate::future::Future; use crate::io; +use crate::path::Path; use crate::task::blocking; /// A builder for creating directories with configurable options. @@ -14,7 +14,7 @@ use crate::task::blocking; /// /// [`os::unix::fs::DirBuilderExt`]: ../os/unix/fs/trait.DirBuilderExt.html /// [`std::fs::DirBuilder`]: https://doc.rust-lang.org/std/fs/struct.DirBuilder.html -#[derive(Debug)] +#[derive(Debug, Default)] pub struct DirBuilder { /// Set to `true` if non-existent parent directories should be created. recursive: bool, @@ -109,7 +109,7 @@ impl DirBuilder { } let path = path.as_ref().to_owned(); - async move { blocking::spawn(async move { builder.create(path) }).await } + async move { blocking::spawn(move || builder.create(path)).await } } } diff --git a/src/fs/dir_entry.rs b/src/fs/dir_entry.rs index 66b3cb7c..80d4bbdf 100644 --- a/src/fs/dir_entry.rs +++ b/src/fs/dir_entry.rs @@ -1,12 +1,12 @@ 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::path::PathBuf; use crate::task::blocking; /// An entry in a directory. @@ -50,7 +50,7 @@ impl DirEntry { /// # Ok(()) }) } /// ``` pub fn path(&self) -> PathBuf { - self.0.path() + self.0.path().into() } /// Reads the metadata for this entry. @@ -89,7 +89,7 @@ impl DirEntry { /// ``` pub async fn metadata(&self) -> io::Result { let inner = self.0.clone(); - blocking::spawn(async move { inner.metadata() }).await + blocking::spawn(move || inner.metadata()).await } /// Reads the file type for this entry. @@ -127,7 +127,7 @@ impl DirEntry { /// ``` pub async fn file_type(&self) -> io::Result { let inner = self.0.clone(); - blocking::spawn(async move { inner.file_type() }).await + blocking::spawn(move || inner.file_type()).await } /// Returns the bare name of this entry without the leading path. diff --git a/src/fs/file.rs b/src/fs/file.rs index bdf93474..39162345 100644 --- a/src/fs/file.rs +++ b/src/fs/file.rs @@ -3,7 +3,6 @@ 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}; @@ -13,6 +12,7 @@ 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}; @@ -97,7 +97,7 @@ 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?; + let file = blocking::spawn(move || std::fs::File::open(&path)).await?; Ok(file.into()) } @@ -132,7 +132,7 @@ 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?; + let file = blocking::spawn(move || std::fs::File::create(&path)).await?; Ok(file.into()) } @@ -165,7 +165,7 @@ impl File { }) .await?; - blocking::spawn(async move { state.file.sync_all() }).await + blocking::spawn(move || state.file.sync_all()).await } /// Synchronizes OS-internal buffered contents to disk. @@ -201,7 +201,7 @@ impl File { }) .await?; - blocking::spawn(async move { state.file.sync_data() }).await + blocking::spawn(move || state.file.sync_data()).await } /// Truncates or extends the file. @@ -234,7 +234,7 @@ impl File { }) .await?; - blocking::spawn(async move { state.file.set_len(size) }).await + blocking::spawn(move || state.file.set_len(size)).await } /// Reads the file's metadata. @@ -253,7 +253,7 @@ impl File { /// ``` pub async fn metadata(&self) -> io::Result { let file = self.file.clone(); - blocking::spawn(async move { file.metadata() }).await + blocking::spawn(move || file.metadata()).await } /// Changes the permissions on the file. @@ -282,7 +282,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 + blocking::spawn(move || file.set_permissions(perm)).await } } @@ -702,7 +702,7 @@ impl LockGuard { self.register(cx); // Start a read operation asynchronously. - blocking::spawn(async move { + blocking::spawn(move || { // Read some data from the file into the cache. let res = { let State { file, cache, .. } = &mut *self; @@ -811,7 +811,7 @@ impl LockGuard { self.register(cx); // Start a write operation asynchronously. - blocking::spawn(async move { + blocking::spawn(move || { match (&*self.file).write_all(&self.cache) { Ok(_) => { // Switch to idle mode. @@ -844,7 +844,7 @@ impl LockGuard { self.register(cx); // Start a flush operation asynchronously. - blocking::spawn(async move { + blocking::spawn(move || { match (&*self.file).flush() { Ok(()) => { // Mark the file as flushed. diff --git a/src/fs/hard_link.rs b/src/fs/hard_link.rs index 5f950cde..8b09b5d1 100644 --- a/src/fs/hard_link.rs +++ b/src/fs/hard_link.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Creates a hard link on the filesystem. @@ -33,5 +32,5 @@ 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 + blocking::spawn(move || std::fs::hard_link(&from, &to)).await } diff --git a/src/fs/metadata.rs b/src/fs/metadata.rs index 2c9e41ec..65f494b5 100644 --- a/src/fs/metadata.rs +++ b/src/fs/metadata.rs @@ -1,8 +1,7 @@ -use std::path::Path; - use cfg_if::cfg_if; use crate::io; +use crate::path::Path; use crate::task::blocking; /// Reads metadata for a path. @@ -37,7 +36,7 @@ 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 + blocking::spawn(move || std::fs::metadata(path)).await } cfg_if! { diff --git a/src/fs/open_options.rs b/src/fs/open_options.rs index c6cc74a0..fe65e2f4 100644 --- a/src/fs/open_options.rs +++ b/src/fs/open_options.rs @@ -1,10 +1,10 @@ -use std::path::Path; +use std::future::Future; use cfg_if::cfg_if; use crate::fs::File; -use crate::future::Future; use crate::io; +use crate::path::Path; use crate::task::blocking; /// A builder for opening files with configurable options. @@ -286,7 +286,13 @@ 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 { blocking::spawn(move || options.open(path).map(|f| f.into())).await } + } +} + +impl Default for OpenOptions { + fn default() -> Self { + Self::new() } } diff --git a/src/fs/read.rs b/src/fs/read.rs index 6b3560db..a0eb130b 100644 --- a/src/fs/read.rs +++ b/src/fs/read.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Reads the entire contents of a file as raw bytes. @@ -37,5 +36,5 @@ 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 + blocking::spawn(move || std::fs::read(path)).await } diff --git a/src/fs/read_dir.rs b/src/fs/read_dir.rs index c6f80fee..6e478019 100644 --- a/src/fs/read_dir.rs +++ b/src/fs/read_dir.rs @@ -1,11 +1,11 @@ -use std::path::Path; 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, Poll}; +use crate::task::{blocking, Context, JoinHandle, Poll}; /// Returns a stream of entries in a directory. /// @@ -45,7 +45,7 @@ use crate::task::{blocking, Context, 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) }) + blocking::spawn(move || std::fs::read_dir(path)) .await .map(ReadDir::new) } @@ -71,7 +71,7 @@ pub struct ReadDir(State); #[derive(Debug)] enum State { Idle(Option), - Busy(blocking::JoinHandle<(std::fs::ReadDir, Option>)>), + Busy(JoinHandle<(std::fs::ReadDir, Option>)>), } impl ReadDir { @@ -91,7 +91,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(blocking::spawn(move || { let next = inner.next(); (inner, next) })); diff --git a/src/fs/read_link.rs b/src/fs/read_link.rs index aede99bc..eaa7b624 100644 --- a/src/fs/read_link.rs +++ b/src/fs/read_link.rs @@ -1,6 +1,5 @@ -use std::path::{Path, PathBuf}; - use crate::io; +use crate::path::{Path, PathBuf}; use crate::task::blocking; /// Reads a symbolic link and returns the path it points to. @@ -29,5 +28,5 @@ 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 + blocking::spawn(move || std::fs::read_link(path).map(Into::into)).await } diff --git a/src/fs/read_to_string.rs b/src/fs/read_to_string.rs index 345f76ef..40c4b6b8 100644 --- a/src/fs/read_to_string.rs +++ b/src/fs/read_to_string.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Reads the entire contents of a file as a string. @@ -38,5 +37,5 @@ 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 + blocking::spawn(move || std::fs::read_to_string(path)).await } diff --git a/src/fs/remove_dir.rs b/src/fs/remove_dir.rs index a176edc8..d1fa7bf3 100644 --- a/src/fs/remove_dir.rs +++ b/src/fs/remove_dir.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Removes an empty directory. @@ -30,5 +29,5 @@ 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 + blocking::spawn(move || std::fs::remove_dir(path)).await } diff --git a/src/fs/remove_dir_all.rs b/src/fs/remove_dir_all.rs index 9db0c31f..0a0fceb7 100644 --- a/src/fs/remove_dir_all.rs +++ b/src/fs/remove_dir_all.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Removes a directory and all of its contents. @@ -30,5 +29,5 @@ 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 + blocking::spawn(move || std::fs::remove_dir_all(path)).await } diff --git a/src/fs/remove_file.rs b/src/fs/remove_file.rs index cc0eeb25..5bc0608d 100644 --- a/src/fs/remove_file.rs +++ b/src/fs/remove_file.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Removes a file. @@ -30,5 +29,5 @@ 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 + blocking::spawn(move || std::fs::remove_file(path)).await } diff --git a/src/fs/rename.rs b/src/fs/rename.rs index 72cd227f..c2aa77b7 100644 --- a/src/fs/rename.rs +++ b/src/fs/rename.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Renames a file or directory to a new location. @@ -35,5 +34,5 @@ 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 + blocking::spawn(move || std::fs::rename(&from, &to)).await } diff --git a/src/fs/set_permissions.rs b/src/fs/set_permissions.rs index 6fa6306f..d14ced94 100644 --- a/src/fs/set_permissions.rs +++ b/src/fs/set_permissions.rs @@ -1,7 +1,6 @@ -use std::path::Path; - use crate::fs::Permissions; use crate::io; +use crate::path::Path; use crate::task::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 + blocking::spawn(move || std::fs::set_permissions(path, perm)).await } diff --git a/src/fs/symlink_metadata.rs b/src/fs/symlink_metadata.rs index 6f1b9d50..bc5cce86 100644 --- a/src/fs/symlink_metadata.rs +++ b/src/fs/symlink_metadata.rs @@ -1,7 +1,6 @@ -use std::path::Path; - use crate::fs::Metadata; use crate::io; +use crate::path::Path; use crate::task::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 + blocking::spawn(move || std::fs::symlink_metadata(path)).await } diff --git a/src/fs/write.rs b/src/fs/write.rs index b0d7abcc..3df56042 100644 --- a/src/fs/write.rs +++ b/src/fs/write.rs @@ -1,6 +1,5 @@ -use std::path::Path; - use crate::io; +use crate::path::Path; use crate::task::blocking; /// Writes a slice of bytes as the new contents of a file. @@ -34,5 +33,5 @@ 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 + blocking::spawn(move || std::fs::write(path, contents)).await } diff --git a/src/future/future.rs b/src/future/future.rs new file mode 100644 index 00000000..556dc1ac --- /dev/null +++ b/src/future/future.rs @@ -0,0 +1,145 @@ +use crate::utils::extension_trait; + +cfg_if::cfg_if! { + if #[cfg(feature = "docs")] { + use std::pin::Pin; + use std::ops::{Deref, DerefMut}; + + use crate::task::{Context, Poll}; + } +} + +extension_trait! { + #[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 `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 + "#] + 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; + } + + pub trait FutureExt: std::future::Future { + } + + 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/into_future.rs b/src/future/into_future.rs new file mode 100644 index 00000000..58b67661 --- /dev/null +++ b/src/future/into_future.rs @@ -0,0 +1,54 @@ +use crate::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(any(feature = "unstable", feature = "docs"))] +#[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..cc3b7a5d 100644 --- a/src/future/mod.rs +++ b/src/future/mod.rs @@ -42,25 +42,30 @@ //! | `future::try_select` | `Result` | Return on first `Ok`, reject on last Err #[doc(inline)] -pub use std::future::Future; +pub use async_macros::{join, try_join}; #[doc(inline)] #[cfg_attr(feature = "docs", doc(cfg(unstable)))] -pub use async_macros::{join, select, try_join, try_select}; +pub use async_macros::{select, try_select}; use cfg_if::cfg_if; +pub use future::Future; pub use pending::pending; pub use poll_fn::poll_fn; pub use ready::ready; +pub use timeout::{timeout, TimeoutError}; +pub(crate) mod future; mod pending; mod poll_fn; mod ready; +mod timeout; cfg_if! { if #[cfg(any(feature = "unstable", feature = "docs"))] { - mod timeout; - pub use timeout::{timeout, TimeoutError}; + mod into_future; + + pub use into_future::IntoFuture; } } diff --git a/src/future/pending.rs b/src/future/pending.rs index aaee7065..2138a301 100644 --- a/src/future/pending.rs +++ b/src/future/pending.rs @@ -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..a808f97f 100644 --- a/src/future/poll_fn.rs +++ b/src/future/poll_fn.rs @@ -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..a8338fba 100644 --- a/src/future/timeout.rs +++ b/src/future/timeout.rs @@ -28,8 +28,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, @@ -42,8 +40,6 @@ where } /// A future that times out after a duration of time. -#[doc(hidden)] -#[allow(missing_debug_implementations)] struct TimeoutFuture { future: F, delay: Delay, @@ -69,8 +65,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/mod.rs b/src/io/buf_read/mod.rs index d34b2ae2..dff10fab 100644 --- a/src/io/buf_read/mod.rs +++ b/src/io/buf_read/mod.rs @@ -1,8 +1,11 @@ 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; @@ -41,7 +44,7 @@ extension_trait! { https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncBufRead.html [provided methods]: #provided-methods "#] - 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 +67,9 @@ extension_trait! { should no longer be returned in calls to `read`. "#] fn consume(self: Pin<&mut Self>, amt: usize); + } + 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/split.rs b/src/io/buf_read/split.rs new file mode 100644 index 00000000..aa3b6fb6 --- /dev/null +++ b/src/io/buf_read/split.rs @@ -0,0 +1,46 @@ +use std::mem; +use std::pin::Pin; + +use super::read_until_internal; +use crate::io::{self, BufRead}; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +/// 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 { + 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 Self { + reader, + buf, + read, + delim, + } = unsafe { self.get_unchecked_mut() }; + let reader = unsafe { Pin::new_unchecked(reader) }; + let n = futures_core::ready!(read_until_internal(reader, cx, *delim, buf, read))?; + if n == 0 && buf.is_empty() { + return Poll::Ready(None); + } + if buf[buf.len() - 1] == *delim { + buf.pop(); + } + Poll::Ready(Some(Ok(mem::replace(buf, vec![])))) + } +} diff --git a/src/io/buf_writer.rs b/src/io/buf_writer.rs index 2b7545a1..f12aacbc 100644 --- a/src/io/buf_writer.rs +++ b/src/io/buf_writer.rs @@ -1,10 +1,12 @@ -use crate::task::{Context, Poll}; -use futures_core::ready; -use futures_io::{AsyncSeek, AsyncWrite, SeekFrom}; use std::fmt; -use std::io; use std::pin::Pin; +use futures_core::ready; + +use crate::io::write::WriteExt; +use crate::io::{self, Seek, SeekFrom, Write}; +use crate::task::{Context, Poll}; + const DEFAULT_CAPACITY: usize = 8 * 1024; /// Wraps a writer and buffers its output. @@ -82,7 +84,10 @@ pub struct BufWriter { written: usize, } -impl BufWriter { +#[derive(Debug)] +pub struct IntoInnerError(W, std::io::Error); + +impl BufWriter { pin_utils::unsafe_pinned!(inner: W); pin_utils::unsafe_unpinned!(buf: Vec); @@ -173,24 +178,36 @@ impl BufWriter { &mut self.inner } - // pub fn get_pin_mut(self: Pin<&mut Self>) -> Pin<&mut W> { - // self.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), + } } - // pub fn poll_into_inner(self: Pin<&mut Self>, _cx: Context<'_>) -> Poll> { - // unimplemented!("poll into inner method") - // } - /// Returns a reference to the internally buffered data. /// /// # Examples @@ -251,7 +268,7 @@ impl BufWriter { } } -impl AsyncWrite for BufWriter { +impl Write for BufWriter { fn poll_write( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -278,7 +295,7 @@ impl AsyncWrite for BufWriter { } } -impl fmt::Debug for BufWriter { +impl fmt::Debug for BufWriter { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("BufReader") .field("writer", &self.inner) @@ -287,11 +304,10 @@ impl fmt::Debug for BufWriter { } } -impl AsyncSeek for BufWriter { +impl Seek for BufWriter { /// Seek to the offset, in bytes, in the underlying writer. /// /// Seeking always writes out the internal buffer before seeking. - fn poll_seek( mut self: Pin<&mut Self>, cx: &mut Context<'_>, @@ -301,80 +317,3 @@ impl AsyncSeek for BufWriter { self.inner().poll_seek(cx, pos) } } - -mod tests { - #![allow(unused_imports)] - - use super::BufWriter; - use crate::io::{self, SeekFrom}; - use crate::prelude::*; - use crate::task; - - #[test] - fn test_buffered_writer() { - task::block_on(async { - let inner = Vec::new(); - let mut writer = BufWriter::with_capacity(2, inner); - - writer.write(&[0, 1]).await.unwrap(); - assert_eq!(writer.buffer(), []); - assert_eq!(*writer.get_ref(), [0, 1]); - - writer.write(&[2]).await.unwrap(); - assert_eq!(writer.buffer(), [2]); - assert_eq!(*writer.get_ref(), [0, 1]); - - writer.write(&[3]).await.unwrap(); - assert_eq!(writer.buffer(), [2, 3]); - assert_eq!(*writer.get_ref(), [0, 1]); - - writer.flush().await.unwrap(); - assert_eq!(writer.buffer(), []); - assert_eq!(*writer.get_ref(), [0, 1, 2, 3]); - - writer.write(&[4]).await.unwrap(); - writer.write(&[5]).await.unwrap(); - assert_eq!(writer.buffer(), [4, 5]); - assert_eq!(*writer.get_ref(), [0, 1, 2, 3]); - - writer.write(&[6]).await.unwrap(); - assert_eq!(writer.buffer(), [6]); - assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5]); - - writer.write(&[7, 8]).await.unwrap(); - assert_eq!(writer.buffer(), []); - assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8]); - - writer.write(&[9, 10, 11]).await.unwrap(); - assert_eq!(writer.buffer(), []); - assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); - - writer.flush().await.unwrap(); - assert_eq!(writer.buffer(), []); - assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); - }) - } - - #[test] - fn test_buffered_writer_inner_into_inner_does_not_flush() { - 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, []); - }) - } - - #[test] - fn test_buffered_writer_seek() { - task::block_on(async { - let mut w = BufWriter::with_capacity(3, io::Cursor::new(Vec::new())); - w.write_all(&[0, 1, 2, 3, 4, 5]).await.unwrap(); - w.write_all(&[6, 7]).await.unwrap(); - assert_eq!(w.seek(SeekFrom::Current(0)).await.ok(), Some(8)); - assert_eq!(&w.get_ref().get_ref()[..], &[0, 1, 2, 3, 4, 5, 6, 7][..]); - assert_eq!(w.seek(SeekFrom::Start(2)).await.ok(), Some(2)); - }) - } -} diff --git a/src/io/cursor.rs b/src/io/cursor.rs index 2dfc8a75..603b85a1 100644 --- a/src/io/cursor.rs +++ b/src/io/cursor.rs @@ -21,7 +21,6 @@ use crate::task::{Context, Poll}; /// [`Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html /// [bytes]: https://doc.rust-lang.org/std/primitive.slice.html /// [`File`]: struct.File.html -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] #[derive(Clone, Debug, Default)] pub struct Cursor { inner: std::io::Cursor, diff --git a/src/io/mod.rs b/src/io/mod.rs index 7a942854..9a125b20 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -1,24 +1,273 @@ -//! 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::prelude::*; +//! use async_std::fs::File; +//! +//! # 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::io::prelude::*; +//! use async_std::io::SeekFrom; +//! use async_std::fs::File; +//! +//! # 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::io::prelude::*; +//! use async_std::io::BufReader; +//! use async_std::fs::File; +//! +//! # 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::io::prelude::*; +//! use async_std::io::BufWriter; +//! use async_std::fs::File; +//! +//! # 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(()) }) } +//! ``` //! -//! Read a line from the standard input: +//! 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::*; //! -//! let stdin = io::stdin(); -//! let mut line = String::new(); -//! stdin.read_line(&mut line).await?; +//! # 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 +//! +//! 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::prelude::*; +//! use async_std::io::BufReader; +//! use async_std::fs::File; +//! +//! # 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; +//! +//! 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 #[doc(inline)] pub use std::io::{Error, ErrorKind, IoSlice, IoSliceMut, Result, SeekFrom}; @@ -39,6 +288,10 @@ pub use stdout::{stdout, Stdout}; pub use timeout::timeout; pub use write::Write; +// For use in the print macros. +#[doc(hidden)] +pub use stdio::{_eprint, _print}; + pub mod prelude; pub(crate) mod buf_read; @@ -55,5 +308,6 @@ mod repeat; mod sink; mod stderr; mod stdin; +mod stdio; mod stdout; mod timeout; diff --git a/src/io/read/mod.rs b/src/io/read/mod.rs index 6fd95c12..40cb3ca0 100644 --- a/src/io/read/mod.rs +++ b/src/io/read/mod.rs @@ -50,7 +50,7 @@ extension_trait! { [`poll_read`]: #tymethod.poll_read [`poll_read_vectored`]: #method.poll_read_vectored "#] - pub trait Read [ReadExt: futures_io::AsyncRead] { + pub trait Read { #[doc = r#" Attempt to read from the `AsyncRead` into `buf`. "#] @@ -70,7 +70,9 @@ extension_trait! { ) -> Poll> { unreachable!("this impl only appears in the rendered docs") } + } + pub trait ReadExt: futures_io::AsyncRead { #[doc = r#" Reads some bytes from the byte stream. diff --git a/src/io/seek.rs b/src/io/seek.rs index 274eee73..2d1c4d37 100644 --- a/src/io/seek.rs +++ b/src/io/seek.rs @@ -33,7 +33,7 @@ extension_trait! { https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncSeek.html [provided methods]: #provided-methods "#] - 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 +42,9 @@ extension_trait! { cx: &mut Context<'_>, pos: SeekFrom, ) -> Poll>; + } + pub trait SeekExt: futures_io::AsyncSeek { #[doc = r#" Seeks to a new position in a byte stream. @@ -70,7 +72,7 @@ extension_trait! { fn seek( &mut self, pos: SeekFrom, - ) -> impl Future> [SeekFuture<'_, Self>] + ) -> impl Future> + '_ [SeekFuture<'_, Self>] where Self: Unpin, { diff --git a/src/io/stderr.rs b/src/io/stderr.rs index 64e0b186..34d4d1dc 100644 --- a/src/io/stderr.rs +++ b/src/io/stderr.rs @@ -5,7 +5,7 @@ use cfg_if::cfg_if; use crate::future::Future; use crate::io::{self, Write}; -use crate::task::{blocking, Context, Poll}; +use crate::task::{blocking, Context, JoinHandle, Poll}; /// Constructs a new handle to the standard error of the current process. /// @@ -56,7 +56,7 @@ enum State { /// The stderr is blocked on an asynchronous operation. /// /// Awaiting this operation will result in the new state of the stderr. - Busy(blocking::JoinHandle), + Busy(JoinHandle), } /// Inner representation of the asynchronous stderr. @@ -116,8 +116,8 @@ impl Write for Stderr { inner.buf[..buf.len()].copy_from_slice(buf); // Start the operation asynchronously. - *state = State::Busy(blocking::spawn(async move { - let res = std::io::Write::write(&mut inner.stderr, &mut inner.buf); + *state = State::Busy(blocking::spawn(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 +144,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(blocking::spawn(move || { let res = std::io::Write::flush(&mut inner.stderr); inner.last_op = Some(Operation::Flush(res)); State::Idle(Some(inner)) diff --git a/src/io/stdin.rs b/src/io/stdin.rs index d2e9ec04..178e819d 100644 --- a/src/io/stdin.rs +++ b/src/io/stdin.rs @@ -5,7 +5,7 @@ use cfg_if::cfg_if; use crate::future::{self, Future}; use crate::io::{self, Read}; -use crate::task::{blocking, Context, Poll}; +use crate::task::{blocking, Context, JoinHandle, Poll}; /// Constructs a new handle to the standard input of the current process. /// @@ -57,7 +57,7 @@ enum State { /// The stdin is blocked on an asynchronous operation. /// /// Awaiting this operation will result in the new state of the stdin. - Busy(blocking::JoinHandle), + Busy(JoinHandle), } /// Inner representation of the asynchronous stdin. @@ -119,7 +119,7 @@ impl Stdin { let mut inner = opt.take().unwrap(); // Start the operation asynchronously. - *state = State::Busy(blocking::spawn(async move { + *state = State::Busy(blocking::spawn(move || { inner.line.clear(); let res = inner.stdin.read_line(&mut inner.line); inner.last_op = Some(Operation::ReadLine(res)); @@ -172,7 +172,7 @@ impl Read for Stdin { } // Start the operation asynchronously. - *state = State::Busy(blocking::spawn(async move { + *state = State::Busy(blocking::spawn(move || { let res = std::io::Read::read(&mut inner.stdin, &mut inner.buf); inner.last_op = Some(Operation::Read(res)); State::Idle(Some(inner)) 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 b7fee709..4128aae0 100644 --- a/src/io/stdout.rs +++ b/src/io/stdout.rs @@ -5,7 +5,7 @@ use cfg_if::cfg_if; use crate::future::Future; use crate::io::{self, Write}; -use crate::task::{blocking, Context, Poll}; +use crate::task::{blocking, Context, JoinHandle, Poll}; /// Constructs a new handle to the standard output of the current process. /// @@ -56,7 +56,7 @@ enum State { /// The stdout is blocked on an asynchronous operation. /// /// Awaiting this operation will result in the new state of the stdout. - Busy(blocking::JoinHandle), + Busy(JoinHandle), } /// Inner representation of the asynchronous stdout. @@ -116,8 +116,8 @@ impl Write for Stdout { inner.buf[..buf.len()].copy_from_slice(buf); // Start the operation asynchronously. - *state = State::Busy(blocking::spawn(async move { - let res = std::io::Write::write(&mut inner.stdout, &mut inner.buf); + *state = State::Busy(blocking::spawn(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 +144,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(blocking::spawn(move || { let res = std::io::Write::flush(&mut inner.stdout); inner.last_op = Some(Operation::Flush(res)); State::Idle(Some(inner)) diff --git a/src/io/timeout.rs b/src/io/timeout.rs index 2d1b29e7..9fcc15ef 100644 --- a/src/io/timeout.rs +++ b/src/io/timeout.rs @@ -1,6 +1,9 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time::Duration; -use futures_timer::TryFutureExt; +use futures_timer::Delay; +use pin_utils::unsafe_pinned; use crate::future::Future; use crate::io; @@ -33,5 +36,48 @@ 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 +} + +/// Future returned by the `FutureExt::timeout` method. +#[derive(Debug)] +pub struct Timeout +where + F: Future>, +{ + future: F, + timeout: Delay, +} + +impl Timeout +where + F: Future>, +{ + unsafe_pinned!(future: F); + unsafe_pinned!(timeout: Delay); +} + +impl Future for Timeout +where + F: Future>, +{ + type Output = io::Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.as_mut().future().poll(cx) { + Poll::Pending => {} + other => return other, + } + + if self.timeout().poll(cx).is_ready() { + let err = Err(io::Error::new(io::ErrorKind::TimedOut, "future timed out").into()); + Poll::Ready(err) + } else { + Poll::Pending + } + } } diff --git a/src/io/write/mod.rs b/src/io/write/mod.rs index 5e1ecc8b..be69b7e2 100644 --- a/src/io/write/mod.rs +++ b/src/io/write/mod.rs @@ -1,11 +1,13 @@ 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; @@ -13,12 +15,12 @@ use cfg_if::cfg_if; use crate::io::IoSlice; use crate::utils::extension_trait; +use crate::io; + cfg_if! { if #[cfg(feature = "docs")] { use std::pin::Pin; use std::ops::{Deref, DerefMut}; - - use crate::io; use crate::task::{Context, Poll}; } } @@ -47,7 +49,7 @@ extension_trait! { [`poll_flush`]: #tymethod.poll_flush [`poll_close`]: #tymethod.poll_close "#] - pub trait Write [WriteExt: futures_io::AsyncWrite] { + pub trait Write { #[doc = r#" Attempt to write bytes from `buf` into the object. "#] @@ -78,7 +80,9 @@ extension_trait! { Attempt to close the object. "#] fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; + } + pub trait WriteExt: futures_io::AsyncWrite { #[doc = r#" Writes some bytes into the byte stream. @@ -197,6 +201,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_fmt.rs b/src/io/write/write_fmt.rs new file mode 100644 index 00000000..a1149cde --- /dev/null +++ b/src/io/write/write_fmt.rs @@ -0,0 +1,50 @@ +use std::pin::Pin; + +use crate::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 mut 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, &mut buffer))?; + if i == 0 { + return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); + } + *amt += i as u64; + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 839a0ec2..e138c87d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,6 +55,7 @@ pub mod future; pub mod io; pub mod net; pub mod os; +pub mod path; pub mod prelude; pub mod stream; pub mod sync; @@ -64,6 +65,8 @@ cfg_if! { if #[cfg(any(feature = "unstable", feature = "docs"))] { #[cfg_attr(feature = "docs", doc(cfg(unstable)))] pub mod pin; + #[cfg_attr(feature = "docs", doc(cfg(unstable)))] + pub mod process; mod unit; mod vec; @@ -74,4 +77,10 @@ cfg_if! { } } +mod macros; pub(crate) mod utils; + +#[cfg(any(feature = "unstable", feature = "docs"))] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[doc(inline)] +pub use std::{write, writeln}; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 00000000..d70b779f --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,163 @@ +/// 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::prelude::*; +/// use async_std::io; +/// 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(any(feature = "unstable", feature = "docs"))] +#[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(any(feature = "unstable", feature = "docs"))] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*))) +} + +/// 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(any(feature = "unstable", feature = "docs"))] +#[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(any(feature = "unstable", feature = "docs"))] +#[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; + } + ); +} diff --git a/src/net/addr.rs b/src/net/addr.rs index 71f43a5c..adc24083 100644 --- a/src/net/addr.rs +++ b/src/net/addr.rs @@ -1,4 +1,4 @@ -use std::marker::PhantomData; +use std::mem; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; use std::pin::Pin; @@ -7,30 +7,42 @@ use cfg_if::cfg_if; use crate::future::Future; use crate::io; -use crate::task::blocking; -use crate::task::{Context, Poll}; +use crate::task::{blocking, Context, JoinHandle, Poll}; cfg_if! { if #[cfg(feature = "docs")] { #[doc(hidden)] - pub struct ImplFuture<'a, T>(std::marker::PhantomData<&'a T>); + pub struct ImplFuture(std::marker::PhantomData); macro_rules! ret { - ($a:lifetime, $f:tt, $i:ty) => (ImplFuture<$a, io::Result<$i>>); + (impl Future, $fut:ty) => (ImplFuture<$out>); } } else { macro_rules! ret { - ($a:lifetime, $f:tt, $i:ty) => ($f<$a, $i>); + (impl Future, $fut:ty) => ($fut); } } } -/// A trait for objects which can be converted or resolved to one or more [`SocketAddr`] values. +/// Converts or resolves addresses to [`SocketAddr`] values. /// /// This trait is an async version of [`std::net::ToSocketAddrs`]. /// /// [`std::net::ToSocketAddrs`]: https://doc.rust-lang.org/std/net/trait.ToSocketAddrs.html -/// [`SocketAddr`]: https://doc.rust-lang.org/std/net/enum.SocketAddr.html +/// [`SocketAddr`]: enum.SocketAddr.html +/// +/// # Examples +/// +/// ``` +/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async { +/// # +/// use async_std::net::ToSocketAddrs; +/// +/// let addr = "localhost:8080".to_socket_addrs().await?.next().unwrap(); +/// println!("resolved: {:?}", addr); +/// # +/// # Ok(()) }) } +/// ``` pub trait ToSocketAddrs { /// Returned iterator over socket addresses which this type may correspond to. type Iter: Iterator; @@ -41,28 +53,39 @@ pub trait ToSocketAddrs { /// resolution performed. /// /// Note that this function may block a backend thread while resolution is performed. - fn to_socket_addrs(&self) -> ret!('_, ToSocketAddrsFuture, Self::Iter); + fn to_socket_addrs( + &self, + ) -> ret!( + impl Future, + ToSocketAddrsFuture + ); } #[doc(hidden)] #[allow(missing_debug_implementations)] -pub enum ToSocketAddrsFuture<'a, I> { - Phantom(PhantomData<&'a ()>), - Join(blocking::JoinHandle>), - Ready(Option>), +pub enum ToSocketAddrsFuture { + Resolving(JoinHandle>), + Ready(io::Result), + Done, } -impl> Future for ToSocketAddrsFuture<'_, I> { +impl> Future for ToSocketAddrsFuture { type Output = io::Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match unsafe { self.get_unchecked_mut() } { - ToSocketAddrsFuture::Join(f) => Pin::new(&mut *f).poll(cx), - ToSocketAddrsFuture::Ready(res) => { - let res = res.take().expect("polled a completed future"); - Poll::Ready(res) + let this = unsafe { self.get_unchecked_mut() }; + let state = mem::replace(this, ToSocketAddrsFuture::Done); + + match state { + ToSocketAddrsFuture::Resolving(mut task) => { + let poll = Pin::new(&mut task).poll(cx); + if poll.is_pending() { + *this = ToSocketAddrsFuture::Resolving(task); + } + poll } - _ => unreachable!(), + ToSocketAddrsFuture::Ready(res) => Poll::Ready(res), + ToSocketAddrsFuture::Done => panic!("polled a completed future"), } } } @@ -70,87 +93,157 @@ impl> Future for ToSocketAddrsFuture<'_, I> { impl ToSocketAddrs for SocketAddr { type Iter = std::option::IntoIter; - fn to_socket_addrs(&self) -> ret!('_, ToSocketAddrsFuture, Self::Iter) { - ToSocketAddrsFuture::Ready(Some(std::net::ToSocketAddrs::to_socket_addrs(self))) + fn to_socket_addrs( + &self, + ) -> ret!( + impl Future, + ToSocketAddrsFuture + ) { + ToSocketAddrsFuture::Ready(Ok(Some(*self).into_iter())) } } impl ToSocketAddrs for SocketAddrV4 { type Iter = std::option::IntoIter; - fn to_socket_addrs(&self) -> ret!('_, ToSocketAddrsFuture, Self::Iter) { - ToSocketAddrsFuture::Ready(Some(std::net::ToSocketAddrs::to_socket_addrs(self))) + fn to_socket_addrs( + &self, + ) -> ret!( + impl Future, + ToSocketAddrsFuture + ) { + SocketAddr::V4(*self).to_socket_addrs() } } impl ToSocketAddrs for SocketAddrV6 { type Iter = std::option::IntoIter; - fn to_socket_addrs(&self) -> ret!('_, ToSocketAddrsFuture, Self::Iter) { - ToSocketAddrsFuture::Ready(Some(std::net::ToSocketAddrs::to_socket_addrs(self))) + fn to_socket_addrs( + &self, + ) -> ret!( + impl Future, + ToSocketAddrsFuture + ) { + SocketAddr::V6(*self).to_socket_addrs() } } impl ToSocketAddrs for (IpAddr, u16) { type Iter = std::option::IntoIter; - fn to_socket_addrs(&self) -> ret!('_, ToSocketAddrsFuture, Self::Iter) { - ToSocketAddrsFuture::Ready(Some(std::net::ToSocketAddrs::to_socket_addrs(self))) + fn to_socket_addrs( + &self, + ) -> ret!( + impl Future, + ToSocketAddrsFuture + ) { + let (ip, port) = *self; + match ip { + IpAddr::V4(a) => (a, port).to_socket_addrs(), + IpAddr::V6(a) => (a, port).to_socket_addrs(), + } } } impl ToSocketAddrs for (Ipv4Addr, u16) { type Iter = std::option::IntoIter; - fn to_socket_addrs(&self) -> ret!('_, ToSocketAddrsFuture, Self::Iter) { - ToSocketAddrsFuture::Ready(Some(std::net::ToSocketAddrs::to_socket_addrs(self))) + fn to_socket_addrs( + &self, + ) -> ret!( + impl Future, + ToSocketAddrsFuture + ) { + let (ip, port) = *self; + SocketAddrV4::new(ip, port).to_socket_addrs() } } impl ToSocketAddrs for (Ipv6Addr, u16) { type Iter = std::option::IntoIter; - fn to_socket_addrs(&self) -> ret!('_, ToSocketAddrsFuture, Self::Iter) { - ToSocketAddrsFuture::Ready(Some(std::net::ToSocketAddrs::to_socket_addrs(self))) + fn to_socket_addrs( + &self, + ) -> ret!( + impl Future, + ToSocketAddrsFuture + ) { + let (ip, port) = *self; + SocketAddrV6::new(ip, port, 0, 0).to_socket_addrs() } } impl ToSocketAddrs for (&str, u16) { type Iter = std::vec::IntoIter; - fn to_socket_addrs(&self) -> ret!('_, ToSocketAddrsFuture, Self::Iter) { - let host = self.0.to_string(); - let port = self.1; - let join = blocking::spawn(async move { + fn to_socket_addrs( + &self, + ) -> ret!( + impl Future, + ToSocketAddrsFuture + ) { + let (host, port) = *self; + + if let Ok(addr) = host.parse::() { + let addr = SocketAddrV4::new(addr, port); + return ToSocketAddrsFuture::Ready(Ok(vec![SocketAddr::V4(addr)].into_iter())); + } + + if let Ok(addr) = host.parse::() { + let addr = SocketAddrV6::new(addr, port, 0, 0); + return ToSocketAddrsFuture::Ready(Ok(vec![SocketAddr::V6(addr)].into_iter())); + } + + let host = host.to_string(); + let task = blocking::spawn(move || { std::net::ToSocketAddrs::to_socket_addrs(&(host.as_str(), port)) }); - ToSocketAddrsFuture::Join(join) + ToSocketAddrsFuture::Resolving(task) } } impl ToSocketAddrs for str { type Iter = std::vec::IntoIter; - fn to_socket_addrs(&self) -> ret!('_, ToSocketAddrsFuture, Self::Iter) { - let socket_addrs = self.to_string(); - let join = - blocking::spawn(async move { std::net::ToSocketAddrs::to_socket_addrs(&socket_addrs) }); - ToSocketAddrsFuture::Join(join) + fn to_socket_addrs( + &self, + ) -> ret!( + impl Future, + ToSocketAddrsFuture + ) { + if let Some(addr) = self.parse().ok() { + return ToSocketAddrsFuture::Ready(Ok(vec![addr].into_iter())); + } + + let addr = self.to_string(); + let task = blocking::spawn(move || std::net::ToSocketAddrs::to_socket_addrs(addr.as_str())); + ToSocketAddrsFuture::Resolving(task) } } impl<'a> ToSocketAddrs for &'a [SocketAddr] { type Iter = std::iter::Cloned>; - fn to_socket_addrs(&self) -> ret!('_, ToSocketAddrsFuture, Self::Iter) { - ToSocketAddrsFuture::Ready(Some(std::net::ToSocketAddrs::to_socket_addrs(self))) + fn to_socket_addrs( + &self, + ) -> ret!( + impl Future, + ToSocketAddrsFuture + ) { + ToSocketAddrsFuture::Ready(Ok(self.iter().cloned())) } } impl ToSocketAddrs for &T { type Iter = T::Iter; - fn to_socket_addrs(&self) -> ret!('_, ToSocketAddrsFuture, Self::Iter) { + fn to_socket_addrs( + &self, + ) -> ret!( + impl Future, + ToSocketAddrsFuture + ) { (**self).to_socket_addrs() } } @@ -158,7 +251,12 @@ impl ToSocketAddrs for &T { impl ToSocketAddrs for String { type Iter = std::vec::IntoIter; - fn to_socket_addrs(&self) -> ret!('_, ToSocketAddrsFuture, Self::Iter) { - ToSocketAddrs::to_socket_addrs(self.as_str()) + fn to_socket_addrs( + &self, + ) -> ret!( + impl Future, + ToSocketAddrsFuture + ) { + (&**self).to_socket_addrs() } } diff --git a/src/net/mod.rs b/src/net/mod.rs index 259dc1de..b3ae287f 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -28,6 +28,11 @@ //! # }) } //! ``` +pub use std::net::AddrParseError; +pub use std::net::Shutdown; +pub use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; +pub use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6}; + pub use addr::ToSocketAddrs; pub use tcp::{Incoming, TcpListener, TcpStream}; pub use udp::UdpSocket; diff --git a/src/net/tcp/stream.rs b/src/net/tcp/stream.rs index 10569434..1ffd6363 100644 --- a/src/net/tcp/stream.rs +++ b/src/net/tcp/stream.rs @@ -76,7 +76,7 @@ impl TcpStream { let mut last_err = None; for addr in addrs.to_socket_addrs().await? { - let res = blocking::spawn(async move { + let res = blocking::spawn(move || { let std_stream = std::net::TcpStream::connect(addr)?; let mio_stream = mio::net::TcpStream::from_stream(std_stream)?; Ok(TcpStream { diff --git a/src/net/udp/mod.rs b/src/net/udp/mod.rs index a750899d..4588be12 100644 --- a/src/net/udp/mod.rs +++ b/src/net/udp/mod.rs @@ -391,14 +391,14 @@ impl UdpSocket { /// let mdns_addr = Ipv4Addr::new(224, 0, 0, 123); /// /// let socket = UdpSocket::bind("127.0.0.1:0").await?; - /// socket.join_multicast_v4(&mdns_addr, &interface)?; + /// socket.join_multicast_v4(mdns_addr, interface)?; /// # /// # Ok(()) }) } /// ``` - pub fn join_multicast_v4(&self, multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> io::Result<()> { + pub fn join_multicast_v4(&self, multiaddr: Ipv4Addr, interface: Ipv4Addr) -> io::Result<()> { self.watcher .get_ref() - .join_multicast_v4(multiaddr, interface) + .join_multicast_v4(&multiaddr, &interface) } /// Executes an operation of the `IPV6_ADD_MEMBERSHIP` type. @@ -435,10 +435,10 @@ impl UdpSocket { /// For more information about this option, see [`join_multicast_v4`]. /// /// [`join_multicast_v4`]: #method.join_multicast_v4 - pub fn leave_multicast_v4(&self, multiaddr: &Ipv4Addr, interface: &Ipv4Addr) -> io::Result<()> { + pub fn leave_multicast_v4(&self, multiaddr: Ipv4Addr, interface: Ipv4Addr) -> io::Result<()> { self.watcher .get_ref() - .leave_multicast_v4(multiaddr, interface) + .leave_multicast_v4(&multiaddr, &interface) } /// Executes an operation of the `IPV6_DROP_MEMBERSHIP` type. diff --git a/src/os/unix/fs.rs b/src/os/unix/fs.rs index be8932c0..4a155106 100644 --- a/src/os/unix/fs.rs +++ b/src/os/unix/fs.rs @@ -1,10 +1,9 @@ //! Unix-specific filesystem extensions. -use std::path::Path; - use cfg_if::cfg_if; use crate::io; +use crate::path::Path; use crate::task::blocking; /// Creates a new symbolic link on the filesystem. @@ -29,7 +28,7 @@ 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 + blocking::spawn(move || std::os::unix::fs::symlink(&src, &dst)).await } cfg_if! { diff --git a/src/os/unix/net/datagram.rs b/src/os/unix/net/datagram.rs index 1f971f7f..c96afd50 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,6 +10,7 @@ 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::task::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 = blocking::spawn(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 ed4f1f4c..b6e6a298 100644 --- a/src/os/unix/net/listener.rs +++ b/src/os/unix/net/listener.rs @@ -1,7 +1,6 @@ //! Unix-specific networking extensions. use std::fmt; -use std::path::Path; use std::pin::Pin; use mio_uds; @@ -12,6 +11,7 @@ use crate::future::{self, 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}; @@ -68,7 +68,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 = blocking::spawn(move || mio_uds::UnixListener::bind(path)).await?; Ok(UnixListener { watcher: Watcher::new(listener), @@ -93,11 +93,16 @@ impl UnixListener { /// ``` pub async fn accept(&self) -> io::Result<(UnixStream, SocketAddr)> { future::poll_fn(|cx| { - let res = - futures_core::ready!(self.watcher.poll_read_with(cx, |inner| inner.accept_std())); + let res = futures_core::ready!(self.watcher.poll_read_with(cx, |inner| { + match inner.accept_std() { + // Converting to `WouldBlock` so that the watcher will + // add the waker of this task to a list of readers. + Ok(None) => Err(io::ErrorKind::WouldBlock.into()), + res => res, + } + })); match res? { - None => Poll::Pending, Some((io, addr)) => { let mio_stream = mio_uds::UnixStream::from_stream(io)?; let stream = UnixStream { @@ -105,6 +110,8 @@ impl UnixListener { }; Poll::Ready(Ok((stream, addr))) } + // This should never happen since `None` is converted to `WouldBlock` + None => unreachable!(), } }) .await diff --git a/src/os/unix/net/mod.rs b/src/os/unix/net/mod.rs index a719a484..2c79e8c3 100644 --- a/src/os/unix/net/mod.rs +++ b/src/os/unix/net/mod.rs @@ -13,7 +13,8 @@ mod stream; cfg_if! { if #[cfg(feature = "docs")] { use std::fmt; - use std::path::Path; + + use crate::path::Path; /// An address associated with a Unix socket. /// @@ -65,9 +66,8 @@ cfg_if! { /// With a pathname: /// /// ```no_run - /// use std::path::Path; - /// /// use async_std::os::unix::net::UnixListener; + /// use async_std::path::Path; /// /// let socket = UnixListener::bind("/tmp/socket").await?; /// let addr = socket.local_addr()?; diff --git a/src/os/unix/net/stream.rs b/src/os/unix/net/stream.rs index ae30b5bf..b16f2a3c 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,6 +11,7 @@ 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::path::Path; use crate::task::{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 { + blocking::spawn(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/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/mod.rs b/src/path/mod.rs new file mode 100644 index 00000000..36b9f2f0 --- /dev/null +++ b/src/path/mod.rs @@ -0,0 +1,29 @@ +//! Cross-platform path manipulation. +//! +//! This module is an async version of [`std::path`]. +//! +//! [`std::path`]: https://doc.rust-lang.org/std/path/index.html + +mod ancestors; +mod path; +mod pathbuf; + +// Structs re-export +#[doc(inline)] +pub use std::path::{Components, Display, Iter, PrefixComponent, StripPrefixError}; + +// Enums re-export +#[doc(inline)] +pub use std::path::{Component, Prefix}; + +// Constants re-export +#[doc(inline)] +pub use std::path::MAIN_SEPARATOR; + +// Functions re-export +#[doc(inline)] +pub use std::path::is_separator; + +use ancestors::Ancestors; +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..aa15ab22 --- /dev/null +++ b/src/path/path.rs @@ -0,0 +1,812 @@ +use std::ffi::OsStr; + +use crate::path::{Ancestors, Components, Display, Iter, PathBuf, StripPrefixError}; +use crate::{fs, io}; + +/// This struct is an async version of [`std::path::Path`]. +/// +/// [`std::path::Path`]: https://doc.rust-lang.org/std/path/struct.Path.html +#[derive(Debug, PartialEq)] +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) } + } + + /// Yields the underlying [`OsStr`] slice. + /// + /// [`OsStr`]: https://doc.rust-lang.org/std/ffi/struct.OsStr.html + pub fn as_os_str(&self) -> &OsStr { + self.inner.as_os_str() + } + + /// Yields 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) -> std::borrow::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. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// assert!(!Path::new("foo.txt").is_absolute()); + /// ``` + /// + /// [`has_root`]: #method.has_root + 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. + /// + /// # Examples + /// + /// ``` + /// use async_std::path::Path; + /// + /// assert!(Path::new("foo.txt").is_relative()); + /// ``` + /// + /// [`is_absolute`]: #method.is_absolute + 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`. + /// + /// # 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); + /// ``` + /// + /// [`None`]: https://doc.rust-lang.org/std/option/enum.Option.html + /// [`parent`]: struct.Path.html#method.parent + 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 async_std::path::Path; + /// use std::ffi::OsStr; + /// + /// 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, when joined onto `base`, yields `self`. + /// + /// # 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 [`self.file_name`]. + /// + /// [`self.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 [`self.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 `.` + /// + /// [`self.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`). + /// + /// # Examples + /// + /// ``` + /// use async_std::path::{Path, Component}; + /// use std::ffi::OsStr; + /// + /// 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) + /// ``` + /// + /// [`Component`]: enum.Component.html + /// [`CurDir`]: enum.Component.html#variant.CurDir + pub fn components(&self) -> Components<'_> { + 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 async_std::path::{self, Path}; + /// use std::ffi::OsStr; + /// + /// 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<'_> { + self.inner.iter() + } + + /// 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() + } + + /// Queries the file system to get information about a file, directory, etc. + /// + /// 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.expect("metadata call failed"); + /// println!("{:?}", metadata.file_type()); + /// # + /// # Ok(()) }) } + /// ``` + pub async fn metadata(&self) -> io::Result { + fs::metadata(self).await + } + + /// Queries the metadata about a file without following symlinks. + /// + /// 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.expect("symlink_metadata call failed"); + /// println!("{:?}", metadata.file_type()); + /// # + /// # Ok(()) }) } + /// ``` + pub async fn symlink_metadata(&self) -> io::Result { + fs::symlink_metadata(self).await + } + + /// Returns the canonical, absolute form of the path 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.unwrap(), 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.expect("read_link call failed"); + /// # + /// # Ok(()) }) } + /// ``` + pub async fn read_link(&self) -> io::Result { + fs::read_link(self).await + } + + /// Returns an iterator over the entries within a directory. + /// + /// The iterator 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::path::Path; + /// use async_std::fs; + /// use futures_util::stream::StreamExt; + /// + /// let path = Path::new("/laputa"); + /// let mut dir = fs::read_dir(&path).await.expect("read_dir call failed"); + /// 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 + 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<'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 Path { + fn as_ref(&self) -> &Path { + self + } +} + +impl AsRef for Path { + fn as_ref(&self) -> &OsStr { + self.inner.as_ref() + } +} + +impl AsRef for OsStr { + 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 std::path::PathBuf { + fn as_ref(&self) -> &Path { + Path::new(self.into()) + } +} + +impl std::borrow::ToOwned for Path { + type Owned = PathBuf; + + fn to_owned(&self) -> PathBuf { + self.to_path_buf() + } +} diff --git a/src/path/pathbuf.rs b/src/path/pathbuf.rs new file mode 100644 index 00000000..64744e14 --- /dev/null +++ b/src/path/pathbuf.rs @@ -0,0 +1,235 @@ +use std::ffi::{OsStr, OsString}; + +use crate::path::Path; + +/// This struct is an async version of [`std::path::PathBuf`]. +/// +/// [`std::path::Path`]: https://doc.rust-lang.org/std/path/struct.PathBuf.html +#[derive(Debug, PartialEq)] +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.as_ref()); + /// p.pop(); + /// assert_eq!(Path::new("/"), p.as_ref()); + /// ``` + 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`, yielding 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 std::ops::Deref for PathBuf { + type Target = Path; + + fn deref(&self) -> &Path { + self.as_ref() + } +} + +impl std::borrow::Borrow for PathBuf { + fn borrow(&self) -> &Path { + &**self + } +} + +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.into() + } +} + +impl From for PathBuf { + fn from(path: OsString) -> PathBuf { + std::path::PathBuf::from(path).into() + } +} + +impl From<&str> for PathBuf { + fn from(path: &str) -> PathBuf { + std::path::PathBuf::from(path).into() + } +} + +impl AsRef for PathBuf { + fn as_ref(&self) -> &Path { + Path::new(&self.inner) + } +} + +impl AsRef for PathBuf { + fn as_ref(&self) -> &std::path::Path { + self.inner.as_ref() + } +} diff --git a/src/prelude.rs b/src/prelude.rs index 4c26a28a..6c670cc7 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -11,6 +11,8 @@ //! use async_std::prelude::*; //! ``` +use cfg_if::cfg_if; + #[doc(no_inline)] pub use crate::future::Future; #[doc(no_inline)] @@ -21,11 +23,13 @@ pub use crate::io::Read as _; pub use crate::io::Seek as _; #[doc(no_inline)] pub use crate::io::Write as _; -#[doc(hidden)] +#[doc(no_inline)] pub use crate::stream::Stream; #[doc(no_inline)] pub use crate::task_local; +#[doc(hidden)] +pub use crate::future::future::FutureExt as _; #[doc(hidden)] pub use crate::io::buf_read::BufReadExt as _; #[doc(hidden)] @@ -36,3 +40,13 @@ pub use crate::io::seek::SeekExt as _; pub use crate::io::write::WriteExt as _; #[doc(hidden)] pub use crate::stream::stream::StreamExt as _; + +cfg_if! { + if #[cfg(any(feature = "unstable", feature = "docs"))] { + #[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/stream/empty.rs b/src/stream/empty.rs index c9deea86..ceb91fea 100644 --- a/src/stream/empty.rs +++ b/src/stream/empty.rs @@ -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 async_std::prelude::*; /// use async_std::stream; @@ -18,7 +18,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..ef236910 --- /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) +/// # } +/// # } +/// # } +/// # fn main() { 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()); +/// # }); +/// # } +/// ``` +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[cfg(any(feature = "unstable", feature = "docs"))] +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/from_fn.rs b/src/stream/from_fn.rs new file mode 100644 index 00000000..c1cb97af --- /dev/null +++ b/src/stream/from_fn.rs @@ -0,0 +1,100 @@ +use std::marker::PhantomData; +use std::pin::Pin; + +use crate::future::Future; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +/// A stream that yields elements by calling a closure. +/// +/// This stream is constructed by [`from_fn`] function. +/// +/// [`from_fn`]: fn.from_fn.html +#[derive(Debug)] +pub struct FromFn { + f: F, + future: Option, + __t: PhantomData, +} + +/// 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 +/// +/// ``` +/// # fn main() { async_std::task::block_on(async { +/// # +/// use async_std::prelude::*; +/// use async_std::sync::Mutex; +/// use std::sync::Arc; +/// use async_std::stream; +/// +/// let count = Arc::new(Mutex::new(0u8)); +/// let s = stream::from_fn(|| { +/// let count = Arc::clone(&count); +/// +/// async move { +/// *count.lock().await += 1; +/// +/// if *count.lock().await > 3 { +/// None +/// } else { +/// Some(*count.lock().await) +/// } +/// } +/// }); +/// +/// 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() -> Fut, + Fut: Future>, +{ + FromFn { + f, + future: None, + __t: PhantomData, + } +} + +impl FromFn { + pin_utils::unsafe_unpinned!(f: F); + pin_utils::unsafe_pinned!(future: Option); +} + +impl Stream for FromFn +where + F: FnMut() -> Fut, + Fut: Future>, +{ + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + loop { + match &self.future { + Some(_) => { + let next = + futures_core::ready!(self.as_mut().future().as_pin_mut().unwrap().poll(cx)); + self.as_mut().future().set(None); + + return Poll::Ready(next); + } + None => { + let fut = (self.as_mut().f())(); + self.as_mut().future().set(Some(fut)); + } + } + } + } +} diff --git a/src/stream/from_stream.rs b/src/stream/from_stream.rs index 984e5c82..047dab8f 100644 --- a/src/stream/from_stream.rs +++ b/src/stream/from_stream.rs @@ -9,6 +9,102 @@ use std::pin::Pin; /// /// See also: [`IntoStream`]. /// +/// # Examples +/// +/// Basic usage: +/// +/// ``` +/// # 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; +/// +/// let five_fives = stream::repeat(5).take(5); +/// +/// let v = Vec::from_stream(five_fives).await; +/// +/// 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); +/// +/// let v: Vec = five_fives.collect().await; +/// +/// 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 std::pin::Pin; +/// +/// // A sample collection, that's just a wrapper over Vec +/// #[derive(Debug)] +/// struct MyCollection(Vec); +/// +/// // Let's give it some methods so we can create one and add things +/// // to it. +/// impl MyCollection { +/// fn new() -> MyCollection { +/// MyCollection(Vec::new()) +/// } +/// +/// fn add(&mut self, elem: i32) { +/// self.0.push(elem); +/// } +/// } +/// +/// // and we'll implement FromIterator +/// impl FromStream for MyCollection { +/// fn from_stream<'a, S: IntoStream + 'a>( +/// stream: S, +/// ) -> 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; +/// +/// for i in v { +/// c.add(i); +/// } +/// c +/// }) +/// } +/// } +/// +/// # 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); +/// +/// // ...and make a MyCollection out of it +/// let c = MyCollection::from_stream(stream).await; +/// +/// assert_eq!(c.0, vec![5, 5, 5, 5, 5]); +/// +/// // collect works too! +/// +/// let stream = stream::repeat(5).take(5); +/// let c: MyCollection = stream.collect().await; +/// +/// assert_eq!(c.0, vec![5, 5, 5, 5, 5]); +/// # Ok(()) }) } +///``` +/// /// [`IntoStream`]: trait.IntoStream.html #[cfg_attr(feature = "docs", doc(cfg(unstable)))] #[cfg(any(feature = "unstable", feature = "docs"))] @@ -20,9 +116,17 @@ pub trait FromStream { /// Basic usage: /// /// ``` - /// // use async_std::stream::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; + /// + /// let five_fives = stream::repeat(5).take(5); + /// + /// let v = Vec::from_stream(five_fives).await; /// - /// // let _five_fives = async_std::stream::repeat(5).take(5); + /// assert_eq!(v, vec![5, 5, 5, 5, 5]); + /// # Ok(()) }) } /// ``` fn from_stream<'a, S: IntoStream + 'a>( stream: S, diff --git a/src/stream/fused_stream.rs b/src/stream/fused_stream.rs new file mode 100644 index 00000000..42e7e6f3 --- /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(any(feature = "unstable", feature = "docs"))] +#[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..21ac0329 --- /dev/null +++ b/src/stream/interval.rs @@ -0,0 +1,199 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::{Duration, Instant}; + +use futures_core::future::Future; +use futures_core::stream::Stream; +use pin_utils::unsafe_pinned; + +use futures_timer::Delay; + +/// 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(any(feature = "unstable", feature = "docs"))] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[doc(inline)] +pub fn interval(dur: Duration) -> Interval { + Interval { + delay: Delay::new(dur), + interval: dur, + } +} + +/// A stream representing notifications at fixed interval +/// +#[derive(Debug)] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[doc(inline)] +pub struct Interval { + delay: Delay, + interval: Duration, +} + +impl Interval { + unsafe_pinned!(delay: Delay); +} + +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::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 { + if a == b { + true + } else if a > b { + a - b < Duration::from_millis(1) + } else { + 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/mod.rs b/src/stream/mod.rs index f2fcdebe..3b5f4610 100644 --- a/src/stream/mod.rs +++ b/src/stream/mod.rs @@ -1,4 +1,4 @@ -//! Asynchronous iteration. +//! Composable asynchronous iteration. //! //! This module is an async version of [`std::iter`]. //! @@ -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,20 +18,24 @@ //! assert_eq!(v, 9); //! } //! # -//! # }) } +//! # }) //! ``` use cfg_if::cfg_if; pub use empty::{empty, Empty}; +pub use from_fn::{from_fn, FromFn}; pub use once::{once, Once}; pub use repeat::{repeat, Repeat}; pub use repeat_with::{repeat_with, RepeatWith}; -pub use stream::{Chain, Filter, Fuse, Inspect, Scan, Skip, SkipWhile, StepBy, Stream, Take, Zip}; +pub use stream::{ + Chain, Filter, Fuse, Inspect, Scan, Skip, SkipWhile, StepBy, Stream, Take, TakeWhile, Zip, +}; pub(crate) mod stream; mod empty; +mod from_fn; mod once; mod repeat; mod repeat_with; @@ -39,17 +43,25 @@ mod repeat_with; cfg_if! { if #[cfg(any(feature = "unstable", feature = "docs"))] { mod double_ended_stream; + mod exact_size_stream; mod extend; mod from_stream; + mod fused_stream; + mod interval; mod into_stream; + mod product; + mod sum; pub use double_ended_stream::DoubleEndedStream; + pub use exact_size_stream::ExactSizeStream; pub use 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 sum::Sum; - #[cfg_attr(feature = "docs", doc(cfg(unstable)))] - #[doc(inline)] - pub use async_macros::{join_stream as join, JoinStream as Join}; + pub use stream::Merge; } } diff --git a/src/stream/once.rs b/src/stream/once.rs index 133a155c..be875e41 100644 --- a/src/stream/once.rs +++ b/src/stream/once.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, None); /// # -/// # }) } +/// # }) /// ``` pub fn once(t: T) -> Once { Once { value: Some(t) } diff --git a/src/stream/product.rs b/src/stream/product.rs new file mode 100644 index 00000000..b3227761 --- /dev/null +++ b/src/stream/product.rs @@ -0,0 +1,23 @@ +use crate::future::Future; +use crate::stream::Stream; + +/// Trait to represent types that can be created by productming up 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_attr(feature = "docs", doc(cfg(unstable)))] +#[cfg(any(feature = "unstable", feature = "docs"))] +pub trait Product: Sized { + /// Method which takes a stream and generates `Self` from the elements by + /// multiplying the items. + fn product(stream: S) -> F + where + S: Stream, + F: Future; +} diff --git a/src/stream/repeat.rs b/src/stream/repeat.rs index 1a6da411..75fd6973 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 diff --git a/src/stream/stream/cmp.rs b/src/stream/stream/cmp.rs new file mode 100644 index 00000000..fc7161ad --- /dev/null +++ b/src/stream/stream/cmp.rs @@ -0,0 +1,91 @@ +use std::cmp::Ordering; +use std::pin::Pin; + +use super::fuse::Fuse; +use crate::future::Future; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +// Lexicographically compares the elements of this `Stream` with those +// of another using `Ord`. +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub struct CmpFuture { + l: Fuse, + r: Fuse, + l_cache: Option, + r_cache: Option, +} + +impl CmpFuture { + pin_utils::unsafe_pinned!(l: Fuse); + pin_utils::unsafe_pinned!(r: Fuse); + pin_utils::unsafe_unpinned!(l_cache: Option); + pin_utils::unsafe_unpinned!(r_cache: Option); + + pub(super) fn new(l: L, r: R) -> Self { + CmpFuture { + 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(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + // Stream that completes earliest can be considered Less, etc + let l_complete = self.l.done && self.as_mut().l_cache.is_none(); + let r_complete = self.r.done && self.as_mut().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 !self.l.done && self.as_mut().l_cache.is_none() { + let l_next = futures_core::ready!(self.as_mut().l().poll_next(cx)); + if let Some(item) = l_next { + *self.as_mut().l_cache() = Some(item); + } + } + + if !self.r.done && self.as_mut().r_cache.is_none() { + let r_next = futures_core::ready!(self.as_mut().r().poll_next(cx)); + if let Some(item) = r_next { + *self.as_mut().r_cache() = Some(item); + } + } + + // Compare if both values are available. + if self.as_mut().l_cache.is_some() && self.as_mut().r_cache.is_some() { + let l_value = self.as_mut().l_cache().take().unwrap(); + let r_value = self.as_mut().r_cache().take().unwrap(); + let result = l_value.cmp(&r_value); + + if let Ordering::Equal = result { + // Reset cache to prepare for next comparison + *self.as_mut().l_cache() = None; + *self.as_mut().r_cache() = None; + } else { + // Return non equal value + return Poll::Ready(result); + } + } + } + } +} diff --git a/src/stream/stream/filter.rs b/src/stream/stream/filter.rs index 3fd54539..8ed282ce 100644 --- a/src/stream/stream/filter.rs +++ b/src/stream/stream/filter.rs @@ -36,13 +36,11 @@ where let next = futures_core::ready!(self.as_mut().stream().poll_next(cx)); match next { - Some(v) => match (self.as_mut().predicate())(&v) { - true => Poll::Ready(Some(v)), - false => { - cx.waker().wake_by_ref(); - Poll::Pending - } - }, + Some(v) if (self.as_mut().predicate())(&v) => Poll::Ready(Some(v)), + Some(_) => { + cx.waker().wake_by_ref(); + Poll::Pending + } None => Poll::Ready(None), } } diff --git a/src/stream/stream/find.rs b/src/stream/stream/find.rs index 014c8b78..93624c03 100644 --- a/src/stream/stream/find.rs +++ b/src/stream/stream/find.rs @@ -36,13 +36,11 @@ where let item = futures_core::ready!(Pin::new(&mut *self.stream).poll_next(cx)); match item { - Some(v) => match (&mut self.p)(&v) { - true => Poll::Ready(Some(v)), - false => { - cx.waker().wake_by_ref(); - Poll::Pending - } - }, + Some(v) if (&mut self.p)(&v) => Poll::Ready(Some(v)), + Some(_) => { + cx.waker().wake_by_ref(); + Poll::Pending + } None => Poll::Ready(None), } } diff --git a/src/stream/stream/ge.rs b/src/stream/stream/ge.rs new file mode 100644 index 00000000..eb9786b5 --- /dev/null +++ b/src/stream/stream/ge.rs @@ -0,0 +1,47 @@ +use std::cmp::Ordering; +use std::pin::Pin; + +use super::partial_cmp::PartialCmpFuture; +use crate::future::Future; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +// 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 { + partial_cmp: PartialCmpFuture, +} + +impl GeFuture +where + L::Item: PartialOrd, +{ + pin_utils::unsafe_pinned!(partial_cmp: PartialCmpFuture); + + pub(super) fn new(l: L, r: R) -> Self { + GeFuture { + partial_cmp: l.partial_cmp(r), + } + } +} + +impl Future for GeFuture +where + L: Stream + Sized, + R: Stream + Sized, + L::Item: PartialOrd, +{ + type Output = bool; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let result = futures_core::ready!(self.as_mut().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..6c480a25 --- /dev/null +++ b/src/stream/stream/gt.rs @@ -0,0 +1,47 @@ +use std::cmp::Ordering; +use std::pin::Pin; + +use super::partial_cmp::PartialCmpFuture; +use crate::future::Future; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +// Determines if the elements of this `Stream` are lexicographically +// greater than those of another. +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub struct GtFuture { + partial_cmp: PartialCmpFuture, +} + +impl GtFuture +where + L::Item: PartialOrd, +{ + pin_utils::unsafe_pinned!(partial_cmp: PartialCmpFuture); + + pub(super) fn new(l: L, r: R) -> Self { + GtFuture { + 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(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let result = futures_core::ready!(self.as_mut().partial_cmp().poll(cx)); + + match result { + Some(Ordering::Greater) => Poll::Ready(true), + _ => Poll::Ready(false), + } + } +} diff --git a/src/stream/stream/last.rs b/src/stream/stream/last.rs new file mode 100644 index 00000000..c58dd66a --- /dev/null +++ b/src/stream/stream/last.rs @@ -0,0 +1,42 @@ +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 LastFuture { + stream: S, + last: Option, +} + +impl LastFuture { + pin_utils::unsafe_pinned!(stream: S); + pin_utils::unsafe_unpinned!(last: Option); + + pub(crate) fn new(stream: S) -> Self { + LastFuture { stream, last: None } + } +} + +impl Future for LastFuture +where + S: Stream + Unpin + Sized, + S::Item: Copy, +{ + 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)); + + match next { + Some(new) => { + cx.waker().wake_by_ref(); + *self.as_mut().last() = Some(new); + Poll::Pending + } + None => Poll::Ready(self.last), + } + } +} diff --git a/src/stream/stream/le.rs b/src/stream/stream/le.rs new file mode 100644 index 00000000..37b62d83 --- /dev/null +++ b/src/stream/stream/le.rs @@ -0,0 +1,47 @@ +use std::cmp::Ordering; +use std::pin::Pin; + +use super::partial_cmp::PartialCmpFuture; +use crate::future::Future; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +/// 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 { + partial_cmp: PartialCmpFuture, +} + +impl LeFuture +where + L::Item: PartialOrd, +{ + pin_utils::unsafe_pinned!(partial_cmp: PartialCmpFuture); + + pub(super) fn new(l: L, r: R) -> Self { + LeFuture { + 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(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let result = futures_core::ready!(self.as_mut().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..b774d7b4 --- /dev/null +++ b/src/stream/stream/lt.rs @@ -0,0 +1,47 @@ +use std::cmp::Ordering; +use std::pin::Pin; + +use super::partial_cmp::PartialCmpFuture; +use crate::future::Future; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +// Determines if the elements of this `Stream` are lexicographically +// less than those of another. +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub struct LtFuture { + partial_cmp: PartialCmpFuture, +} + +impl LtFuture +where + L::Item: PartialOrd, +{ + pin_utils::unsafe_pinned!(partial_cmp: PartialCmpFuture); + + pub(super) fn new(l: L, r: R) -> Self { + LtFuture { + 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(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let result = futures_core::ready!(self.as_mut().partial_cmp().poll(cx)); + + match result { + Some(Ordering::Less) => Poll::Ready(true), + _ => Poll::Ready(false), + } + } +} diff --git a/src/stream/stream/merge.rs b/src/stream/stream/merge.rs new file mode 100644 index 00000000..ab97d2cb --- /dev/null +++ b/src/stream/stream/merge.rs @@ -0,0 +1,44 @@ +use std::pin::Pin; +use std::task::{Context, Poll}; + +use futures_core::Stream; + +/// A stream that merges two other streams into a single stream. +/// +/// This stream is returned by [`Stream::merge`]. +/// +/// [`Stream::merge`]: trait.Stream.html#method.merge +#[cfg(any(feature = "unstable", feature = "docs"))] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[derive(Debug)] +pub struct Merge { + left: L, + right: R, +} + +impl Unpin for Merge {} + +impl Merge { + pub(crate) fn new(left: L, right: R) -> Self { + Self { left, right } + } +} + +impl Stream for Merge +where + L: Stream + Unpin, + R: Stream + Unpin, +{ + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if let Poll::Ready(Some(item)) = Pin::new(&mut self.left).poll_next(cx) { + // The first stream made progress. The Merge needs to be polled + // again to check the progress of the second stream. + cx.waker().wake_by_ref(); + Poll::Ready(Some(item)) + } else { + Pin::new(&mut self.right).poll_next(cx) + } + } +} diff --git a/src/stream/stream/mod.rs b/src/stream/stream/mod.rs index 8849605c..764dc978 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,12 +18,13 @@ //! assert_eq!(v, 9); //! } //! # -//! # }) } +//! # }) //! ``` mod all; mod any; mod chain; +mod cmp; mod enumerate; mod filter; mod filter_map; @@ -32,30 +33,46 @@ 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 min_by; mod next; mod nth; +mod partial_cmp; 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 enumerate::Enumerate; 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 min_by::MinByFuture; use next::NextFuture; use nth::NthFuture; +use partial_cmp::PartialCmpFuture; +use try_fold::TryFoldFuture; use try_for_each::TryForEeachFuture; pub use chain::Chain; @@ -68,6 +85,7 @@ 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; @@ -87,10 +105,14 @@ cfg_if! { cfg_if! { if #[cfg(any(feature = "unstable", feature = "docs"))] { + mod merge; + use std::pin::Pin; use crate::future::Future; use crate::stream::FromStream; + + pub use merge::Merge; } } @@ -114,7 +136,7 @@ extension_trait! { https://docs.rs/futures-preview/0.3.0-alpha.17/futures/stream/trait.Stream.html [provided methods]: #provided-methods "#] - pub trait Stream [StreamExt: futures_core::stream::Stream] { + pub trait Stream { #[doc = r#" The type of items yielded by this stream. "#] @@ -172,7 +194,9 @@ extension_trait! { ``` "#] fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; + } + pub trait StreamExt: futures_core::stream::Stream { #[doc = r#" Advances the stream and returns the next value. @@ -235,6 +259,35 @@ extension_trait! { } } + #[doc = r#" + Creates a stream that yields elements based on a predicate. + + # Examples + ``` + # fn main() { async_std::task::block_on(async { + # + use std::collections::VecDeque; + + use async_std::prelude::*; + + let s: VecDeque = vec![1, 2, 3, 4].into_iter().collect(); + 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#" Creates a stream that yields each `step`th element. @@ -405,6 +458,54 @@ extension_trait! { Inspect::new(self, f) } + #[doc = r#" + Returns the last element of the stream. + + # Examples + + Basic usage: + + ``` + # fn main() { async_std::task::block_on(async { + # + use std::collections::VecDeque; + + use async_std::prelude::*; + + let s: VecDeque = vec![1, 2, 3].into_iter().collect(); + + 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 std::collections::VecDeque; + + use async_std::prelude::*; + + let s: VecDeque = vec![].into_iter().collect(); + + let last = s.last().await; + assert_eq!(last, None); + # + # }) } + ``` + + "#] + fn last( + self, + ) -> impl Future> [LastFuture] + where + Self: Sized, + { + LastFuture::new(self) + } + #[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 @@ -993,6 +1094,46 @@ extension_trait! { Skip::new(self, n) } + #[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 std::collections::VecDeque; + + let s: VecDeque = vec![1, 2, 3].into_iter().collect(); + 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( + self, + init: T, + f: F, + ) -> impl Future> [TryFoldFuture] + where + Self: 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. @@ -1147,6 +1288,264 @@ 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. + + # Examples + + ``` + # async_std::task::block_on(async { + use async_std::prelude::*; + use async_std::stream; + + let a = stream::once(1u8); + let b = stream::once(2u8); + let c = stream::once(3u8); + + let mut s = a.merge(b).merge(c); + + assert_eq!(s.next().await, Some(1u8)); + assert_eq!(s.next().await, Some(2u8)); + assert_eq!(s.next().await, Some(3u8)); + assert_eq!(s.next().await, None); + # }); + ``` + "#] + #[cfg(any(feature = "unstable", feature = "docs"))] + #[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 std::collections::VecDeque; + + use std::cmp::Ordering; + + let s1 = VecDeque::from(vec![1]); + let s2 = VecDeque::from(vec![1, 2]); + let s3 = VecDeque::from(vec![1, 2, 3]); + let s4 = VecDeque::from(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#" + 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 std::collections::VecDeque; + + use std::cmp::Ordering; + let s1 = VecDeque::from(vec![1]); + let s2 = VecDeque::from(vec![1, 2]); + let s3 = VecDeque::from(vec![1, 2, 3]); + let s4 = VecDeque::from(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#" + 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 std::collections::VecDeque; + + let single: VecDeque = vec![1].into_iter().collect(); + let single_gt: VecDeque = vec![10].into_iter().collect(); + let multi: VecDeque = vec![1,2].into_iter().collect(); + let multi_gt: VecDeque = vec![1,5].into_iter().collect(); + 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 + greater than those of another. + + # Examples + + ``` + # fn main() { async_std::task::block_on(async { + # + use async_std::prelude::*; + use std::collections::VecDeque; + + let single = VecDeque::from(vec![1]); + let single_gt = VecDeque::from(vec![10]); + let multi = VecDeque::from(vec![1,2]); + let multi_gt = VecDeque::from(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 std::collections::VecDeque; + + let single = VecDeque::from(vec![1]); + let single_gt = VecDeque::from(vec![10]); + let multi = VecDeque::from(vec![1,2]); + let multi_gt = VecDeque::from(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 std::collections::VecDeque; + + let single = VecDeque::from(vec![1]); + let single_gt = VecDeque::from(vec![10]); + let multi = VecDeque::from(vec![1,2]); + let multi_gt = VecDeque::from(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) + } } impl Stream for Box { diff --git a/src/stream/stream/partial_cmp.rs b/src/stream/stream/partial_cmp.rs new file mode 100644 index 00000000..fac2705d --- /dev/null +++ b/src/stream/stream/partial_cmp.rs @@ -0,0 +1,92 @@ +use std::cmp::Ordering; +use std::pin::Pin; + +use super::fuse::Fuse; +use crate::future::Future; +use crate::prelude::*; +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +// Lexicographically compares the elements of this `Stream` with those +// of another. +#[doc(hidden)] +#[allow(missing_debug_implementations)] +pub struct PartialCmpFuture { + l: Fuse, + r: Fuse, + l_cache: Option, + r_cache: Option, +} + +impl PartialCmpFuture { + pin_utils::unsafe_pinned!(l: Fuse); + pin_utils::unsafe_pinned!(r: Fuse); + pin_utils::unsafe_unpinned!(l_cache: Option); + pin_utils::unsafe_unpinned!(r_cache: Option); + + pub(super) fn new(l: L, r: R) -> Self { + PartialCmpFuture { + 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(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + loop { + // Short circuit logic + // Stream that completes earliest can be considered Less, etc + let l_complete = self.l.done && self.as_mut().l_cache.is_none(); + let r_complete = self.r.done && self.as_mut().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 !self.l.done && self.as_mut().l_cache.is_none() { + let l_next = futures_core::ready!(self.as_mut().l().poll_next(cx)); + if let Some(item) = l_next { + *self.as_mut().l_cache() = Some(item); + } + } + + if !self.r.done && self.as_mut().r_cache.is_none() { + let r_next = futures_core::ready!(self.as_mut().r().poll_next(cx)); + if let Some(item) = r_next { + *self.as_mut().r_cache() = Some(item); + } + } + + // Compare if both values are available. + if self.as_mut().l_cache.is_some() && self.as_mut().r_cache.is_some() { + let l_value = self.as_mut().l_cache().take().unwrap(); + let r_value = self.as_mut().r_cache().take().unwrap(); + let result = l_value.partial_cmp(&r_value); + + if let Some(Ordering::Equal) = result { + // Reset cache to prepare for next comparison + *self.as_mut().l_cache() = None; + *self.as_mut().r_cache() = None; + } else { + // Return non equal value + return Poll::Ready(result); + } + } + } + } +} diff --git a/src/stream/stream/skip_while.rs b/src/stream/stream/skip_while.rs index a54b0510..b1a8d9ea 100644 --- a/src/stream/stream/skip_while.rs +++ b/src/stream/stream/skip_while.rs @@ -38,13 +38,12 @@ where match next { Some(v) => match self.as_mut().predicate() { - Some(p) => match p(&v) { - true => (), - false => { + Some(p) => { + if !p(&v) { *self.as_mut().predicate() = None; return Poll::Ready(Some(v)); } - }, + } None => return Poll::Ready(Some(v)), }, None => return Poll::Ready(None), diff --git a/src/stream/stream/take_while.rs b/src/stream/stream/take_while.rs new file mode 100644 index 00000000..6f3cc8f2 --- /dev/null +++ b/src/stream/stream/take_while.rs @@ -0,0 +1,47 @@ +use std::marker::PhantomData; +use std::pin::Pin; + +use crate::stream::Stream; +use crate::task::{Context, Poll}; + +/// A stream that yields elements based on a predicate. +#[derive(Debug)] +pub struct TakeWhile { + stream: S, + predicate: P, + __t: PhantomData, +} + +impl TakeWhile { + pin_utils::unsafe_pinned!(stream: S); + pin_utils::unsafe_unpinned!(predicate: P); + + pub(super) fn new(stream: S, predicate: P) -> Self { + TakeWhile { + stream, + predicate, + __t: PhantomData, + } + } +} + +impl Stream for TakeWhile +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)); + + match next { + Some(v) if (self.as_mut().predicate())(&v) => Poll::Ready(Some(v)), + Some(_) => { + cx.waker().wake_by_ref(); + Poll::Pending + } + None => Poll::Ready(None), + } + } +} diff --git a/src/stream/stream/try_fold.rs b/src/stream/stream/try_fold.rs new file mode 100644 index 00000000..212b0589 --- /dev/null +++ b/src/stream/stream/try_fold.rs @@ -0,0 +1,59 @@ +use std::marker::PhantomData; +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 { + stream: S, + f: F, + acc: Option, + __t: PhantomData, +} + +impl TryFoldFuture { + pin_utils::unsafe_pinned!(stream: S); + pin_utils::unsafe_unpinned!(f: F); + pin_utils::unsafe_unpinned!(acc: Option); + + pub(super) fn new(stream: S, init: T, f: F) -> Self { + TryFoldFuture { + stream, + f, + acc: Some(init), + __t: PhantomData, + } + } +} + +impl Future for TryFoldFuture +where + S: Stream + Sized, + 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!(self.as_mut().stream().poll_next(cx)); + + match next { + Some(v) => { + let old = self.as_mut().acc().take().unwrap(); + let new = (self.as_mut().f())(old, v); + + match new { + Ok(o) => { + *self.as_mut().acc() = Some(o); + } + Err(e) => return Poll::Ready(Err(e)), + } + } + None => return Poll::Ready(Ok(self.as_mut().acc().take().unwrap())), + } + } + } +} diff --git a/src/stream/sum.rs b/src/stream/sum.rs new file mode 100644 index 00000000..fd5d7d5e --- /dev/null +++ b/src/stream/sum.rs @@ -0,0 +1,23 @@ +use crate::future::Future; +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_attr(feature = "docs", doc(cfg(unstable)))] +#[cfg(any(feature = "unstable", feature = "docs"))] +pub trait Sum: Sized { + /// Method which takes a stream and generates `Self` from the elements by + /// "summing up" the items. + fn sum(stream: S) -> F + where + S: Stream, + F: Future; +} diff --git a/src/string/extend.rs b/src/string/extend.rs index 72260a54..8572cc3c 100644 --- a/src/string/extend.rs +++ b/src/string/extend.rs @@ -10,9 +10,8 @@ impl Extend for String { stream: S, ) -> Pin + 'a>> { let stream = stream.into_stream(); - //TODO: Add this back in when size_hint is added to Stream/StreamExt - // let (lower_bound, _) = stream.size_hint(); - // self.reserve(lower_bound); + + self.reserve(stream.size_hint().0); Box::pin(stream.for_each(move |c| self.push(c))) } diff --git a/src/sync/barrier.rs b/src/sync/barrier.rs index 694bf99d..43488ee4 100644 --- a/src/sync/barrier.rs +++ b/src/sync/barrier.rs @@ -157,7 +157,7 @@ impl Barrier { drop(lock); while local_gen == generation_id && count < self.n { - let (g, c) = wait.recv().await.expect("sender hasn not been closed"); + let (g, c) = wait.recv().await.expect("sender has not been closed"); generation_id = g; count = c; } diff --git a/src/sync/mod.rs b/src/sync/mod.rs index df1d71ab..3d3b7b80 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs @@ -9,7 +9,7 @@ //! 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; //! @@ -26,7 +26,7 @@ //! //! assert_eq!(*m1.lock().await, 1); //! # -//! # }) } +//! # }) //! ``` #[doc(inline)] diff --git a/src/sync/mutex.rs b/src/sync/mutex.rs index 8ae51ad4..cd7a3577 100644 --- a/src/sync/mutex.rs +++ b/src/sync/mutex.rs @@ -10,7 +10,7 @@ use crate::future::Future; use crate::task::{Context, Poll, Waker}; /// Set if the mutex is locked. -const LOCK: usize = 1 << 0; +const LOCK: usize = 1; /// Set if there are tasks blocked on the mutex. const BLOCKED: usize = 1 << 1; @@ -24,7 +24,7 @@ const BLOCKED: usize = 1 << 1; /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # /// use std::sync::Arc; /// @@ -46,7 +46,7 @@ const BLOCKED: usize = 1 << 1; /// } /// assert_eq!(*m.lock().await, 10); /// # -/// # }) } +/// # }) /// ``` pub struct Mutex { state: AtomicUsize, @@ -82,7 +82,7 @@ impl Mutex { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use std::sync::Arc; /// @@ -99,7 +99,7 @@ impl Mutex { /// /// assert_eq!(*m2.lock().await, 20); /// # - /// # }) } + /// # }) /// ``` pub async fn lock(&self) -> MutexGuard<'_, T> { pub struct LockFuture<'a, T> { @@ -196,7 +196,7 @@ impl Mutex { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use std::sync::Arc; /// @@ -217,7 +217,7 @@ impl Mutex { /// /// assert_eq!(*m2.lock().await, 20); /// # - /// # }) } + /// # }) /// ``` pub fn try_lock(&self) -> Option> { if self.state.fetch_or(LOCK, Ordering::Acquire) & LOCK == 0 { @@ -249,7 +249,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 +257,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() } diff --git a/src/sync/rwlock.rs b/src/sync/rwlock.rs index 36f475b5..ed1d2185 100644 --- a/src/sync/rwlock.rs +++ b/src/sync/rwlock.rs @@ -10,7 +10,7 @@ use crate::future::Future; use crate::task::{Context, Poll, Waker}; /// Set if a write lock is held. -const WRITE_LOCK: usize = 1 << 0; +const WRITE_LOCK: usize = 1; /// Set if there are read operations blocked on the lock. const BLOCKED_READS: usize = 1 << 1; @@ -33,7 +33,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,7 +51,7 @@ const READ_COUNT_MASK: usize = !(ONE_READ - 1); /// *w += 1; /// assert_eq!(*w, 6); /// # -/// # }) } +/// # }) /// ``` pub struct RwLock { state: AtomicUsize, @@ -89,7 +89,7 @@ impl RwLock { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::sync::RwLock; /// @@ -100,7 +100,7 @@ impl RwLock { /// /// assert!(lock.try_read().is_some()); /// # - /// # }) } + /// # }) /// ``` pub async fn read(&self) -> RwLockReadGuard<'_, T> { pub struct LockFuture<'a, T> { @@ -211,7 +211,7 @@ impl RwLock { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::sync::RwLock; /// @@ -222,7 +222,7 @@ impl RwLock { /// /// assert!(lock.try_read().is_some()); /// # - /// # }) } + /// # }) /// ``` pub fn try_read(&self) -> Option> { let mut state = self.state.load(Ordering::Acquire); @@ -253,7 +253,7 @@ impl RwLock { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::sync::RwLock; /// @@ -264,7 +264,7 @@ impl RwLock { /// /// assert!(lock.try_read().is_none()); /// # - /// # }) } + /// # }) /// ``` pub async fn write(&self) -> RwLockWriteGuard<'_, T> { pub struct LockFuture<'a, T> { @@ -374,7 +374,7 @@ impl RwLock { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::sync::RwLock; /// @@ -385,7 +385,7 @@ impl RwLock { /// /// assert!(lock.try_write().is_none()); /// # - /// # }) } + /// # }) /// ``` pub fn try_write(&self) -> Option> { let mut state = self.state.load(Ordering::Acquire); @@ -431,7 +431,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 +439,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() } diff --git a/src/task/block_on.rs b/src/task/block_on.rs index 2d49dcaa..db46f025 100644 --- a/src/task/block_on.rs +++ b/src/task/block_on.rs @@ -19,6 +19,10 @@ use kv_log_macro::trace; /// Calling this function is similar to [spawning] a thread and immediately [joining] it, except an /// asynchronous task will be spawned. /// +/// See also: [`task::spawn_blocking`]. +/// +/// [`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 /// @@ -27,11 +31,9 @@ use kv_log_macro::trace; /// ```no_run /// use async_std::task; /// -/// fn main() { -/// task::block_on(async { -/// println!("Hello, world!"); -/// }) -/// } +/// task::block_on(async { +/// println!("Hello, world!"); +/// }) /// ``` pub fn block_on(future: F) -> T where @@ -69,12 +71,11 @@ where let future = task_local::add_finalizer(future); let future = async move { - let res = future.await; + future.await; trace!("block_on completed", { parent_id: parent_id, child_id: child_id, }); - res }; // Pin the future onto the stack. diff --git a/src/task/blocking.rs b/src/task/blocking.rs index 41177bc9..3216012a 100644 --- a/src/task/blocking.rs +++ b/src/task/blocking.rs @@ -1,7 +1,5 @@ //! A thread pool for running blocking functions asynchronously. -use std::fmt; -use std::pin::Pin; use std::sync::atomic::{AtomicU64, Ordering}; use std::thread; use std::time::Duration; @@ -9,8 +7,7 @@ use std::time::Duration; use crossbeam_channel::{bounded, Receiver, Sender}; use lazy_static::lazy_static; -use crate::future::Future; -use crate::task::{Context, Poll}; +use crate::task::task::{JoinHandle, Tag}; use crate::utils::abort_on_panic; const MAX_THREADS: u64 = 10_000; @@ -18,8 +15,8 @@ const MAX_THREADS: u64 = 10_000; static DYNAMIC_THREAD_COUNT: AtomicU64 = AtomicU64::new(0); struct Pool { - sender: Sender>, - receiver: Receiver>, + sender: Sender>, + receiver: Receiver>, } lazy_static! { @@ -85,7 +82,7 @@ fn maybe_create_another_blocking_thread() { // 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<()>) { +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 @@ -98,35 +95,16 @@ fn schedule(t: async_task::Task<()>) { /// Spawns a blocking task. /// /// The task will be spawned onto a thread pool specifically dedicated to blocking tasks. -pub fn spawn(future: F) -> JoinHandle +pub(crate) fn spawn(f: F) -> JoinHandle where - F: Future + Send + 'static, + F: FnOnce() -> R + Send + 'static, R: Send + 'static, { - let (task, handle) = async_task::spawn(future, schedule, ()); + let tag = Tag::new(None); + let future = async move { f() }; + let (task, handle) = async_task::spawn(future, schedule, tag); task.schedule(); - JoinHandle(handle) -} - -/// A handle to a blocking task. -pub struct JoinHandle(async_task::JoinHandle); - -impl Unpin for JoinHandle {} - -impl Future for JoinHandle { - type Output = R; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Pin::new(&mut self.0).poll(cx).map(|out| out.unwrap()) - } -} - -impl fmt::Debug for JoinHandle { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("JoinHandle") - .field("handle", &self.0) - .finish() - } + JoinHandle::new(handle) } /// Generates a random number in `0..n`. @@ -135,7 +113,7 @@ fn random(n: u32) -> u32 { use std::num::Wrapping; thread_local! { - static RNG: Cell> = Cell::new(Wrapping(1406868647)); + static RNG: Cell> = Cell::new(Wrapping(1_406_868_647)); } RNG.with(|rng| { @@ -152,6 +130,6 @@ fn random(n: u32) -> u32 { // // Author: Daniel Lemire // Source: https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ - ((x.0 as u64).wrapping_mul(n as u64) >> 32) as u32 + ((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 630876c2..a43b42bc 100644 --- a/src/task/builder.rs +++ b/src/task/builder.rs @@ -4,7 +4,7 @@ use crate::future::Future; use crate::io; /// Task builder that configures the settings of a new task. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct Builder { pub(crate) name: Option, } diff --git a/src/task/mod.rs b/src/task/mod.rs index 9e92f35d..e2c89aea 100644 --- a/src/task/mod.rs +++ b/src/task/mod.rs @@ -1,32 +1,126 @@ -//! 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 +//! }); +//! ``` +//! +//! 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: //! //! ``` -//! # fn main() { async_std::task::block_on(async { +//! 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(feature = "unstable")] -#[cfg_attr(feature = "docs", doc(cfg(unstable)))] #[doc(inline)] pub use async_macros::ready; @@ -48,3 +142,49 @@ mod task_local; mod worker; pub(crate) mod blocking; + +cfg_if::cfg_if! { + if #[cfg(any(feature = "unstable", feature = "docs"))] { + mod yield_now; + pub use yield_now::yield_now; + } +} + +/// 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: +/// +/// ``` +/// # async_std::task::block_on(async { +/// # +/// use async_std::task; +/// +/// task::spawn_blocking(|| { +/// 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 spawn_blocking(f: F) -> task::JoinHandle +where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, +{ + blocking::spawn(f) +} diff --git a/src/task/pool.rs b/src/task/pool.rs index 49fbfe49..bfaa17d4 100644 --- a/src/task/pool.rs +++ b/src/task/pool.rs @@ -22,7 +22,7 @@ use crate::utils::abort_on_panic; /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # async_std::task::block_on(async { /// # /// use async_std::task; /// @@ -32,7 +32,7 @@ use crate::utils::abort_on_panic; /// /// assert_eq!(handle.await, 3); /// # -/// # }) } +/// # }) /// ``` pub fn spawn(future: F) -> JoinHandle where 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/sleepers.rs b/src/task/sleepers.rs index a9071758..4e701295 100644 --- a/src/task/sleepers.rs +++ b/src/task/sleepers.rs @@ -30,7 +30,7 @@ impl Sleepers { pub fn wait(&self) { let mut sleep = self.sleep.lock().unwrap(); - if self.notified.swap(false, Ordering::SeqCst) == false { + if !self.notified.swap(false, Ordering::SeqCst) { *sleep += 1; let _ = self.wake.wait(sleep).unwrap(); } @@ -38,7 +38,7 @@ impl Sleepers { /// Notifies one thread. pub fn notify_one(&self) { - if self.notified.load(Ordering::SeqCst) == false { + if !self.notified.load(Ordering::SeqCst) { let mut sleep = self.sleep.lock().unwrap(); if *sleep > 0 { diff --git a/src/task/task.rs b/src/task/task.rs index 8a1addea..ca3cac14 100644 --- a/src/task/task.rs +++ b/src/task/task.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::future::Future; use std::i64; use std::mem; use std::num::NonZeroU64; @@ -7,7 +8,6 @@ use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering}; use std::sync::Arc; use super::task_local; -use crate::future::Future; use crate::task::{Context, Poll}; /// A handle to a task. @@ -68,7 +68,7 @@ impl JoinHandle { /// # Examples /// /// ``` - /// # fn main() { async_std::task::block_on(async { + /// # async_std::task::block_on(async { /// # /// use async_std::task; /// @@ -77,7 +77,7 @@ impl JoinHandle { /// }); /// println!("id = {}", handle.task().id()); /// # - /// # }) } + /// # }) pub fn task(&self) -> &Task { self.0.tag().task() } @@ -122,7 +122,7 @@ impl TaskId { unsafe { TaskId(NonZeroU64::new_unchecked(id)) } } - pub(crate) fn as_u64(&self) -> u64 { + pub(crate) fn as_u64(self) -> u64 { self.0.get() } } diff --git a/src/task/task_local.rs b/src/task/task_local.rs index 8347e34b..c72937f6 100644 --- a/src/task/task_local.rs +++ b/src/task/task_local.rs @@ -1,13 +1,13 @@ use std::cell::UnsafeCell; use std::error::Error; use std::fmt; +use std::future::Future; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Mutex; use lazy_static::lazy_static; use super::worker; -use crate::future::Future; use crate::utils::abort_on_panic; /// Declares task-local values. diff --git a/src/task/worker.rs b/src/task/worker.rs index fc2a6e7e..f7b08e16 100644 --- a/src/task/worker.rs +++ b/src/task/worker.rs @@ -22,13 +22,13 @@ use crate::utils::abort_on_panic; /// # Examples /// /// ``` -/// # fn main() { async_std::task::block_on(async { +/// # 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") diff --git a/src/task/yield_now.rs b/src/task/yield_now.rs new file mode 100644 index 00000000..6f596388 --- /dev/null +++ b/src/task/yield_now.rs @@ -0,0 +1,53 @@ +use crate::future::Future; +use crate::task::{Context, Poll}; + +use std::pin::Pin; + +/// 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: +/// +/// ``` +/// # fn main() { async_std::task::block_on(async { +/// # +/// use async_std::task; +/// +/// task::yield_now().await; +/// # +/// # }) } +/// ``` +#[cfg(any(feature = "unstable", feature = "docs"))] +#[cfg_attr(feature = "docs", doc(cfg(unstable)))] +#[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/utils.rs b/src/utils.rs index bdf0f3b5..76db50b8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -20,7 +20,7 @@ pub fn abort_on_panic(f: impl FnOnce() -> T) -> T { t } -/// Defines an extension trait for a base trait from the `futures` crate. +/// 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 @@ -35,10 +35,14 @@ 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)* + } + + pub trait $ext:ident: $base:path { + $($body_ext:tt)* } // Shim trait impls that only appear in docs. @@ -58,11 +62,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,9 +74,9 @@ macro_rules! extension_trait { pub use $base as $name; // The extension trait that adds methods to any type implementing the base trait. - $(#[$attr])* + /// Extension trait. pub trait $ext: $base { - extension_trait!(@ext () $($body)*); + extension_trait!(@ext () $($body_ext)*); } // Blanket implementation of the extension trait for any type implementing the base trait. @@ -82,39 +86,15 @@ macro_rules! extension_trait { $(#[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)*); - }; - - // Parse the return type in an extension method where the future doesn't borrow. + // Parse the return type in an extension method. (@doc ($($head:tt)*) -> impl Future [$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 +102,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)*); }; diff --git a/src/vec/extend.rs b/src/vec/extend.rs index ca3373d6..ecf68c83 100644 --- a/src/vec/extend.rs +++ b/src/vec/extend.rs @@ -9,9 +9,9 @@ impl Extend for Vec { stream: S, ) -> Pin + 'a>> { let stream = stream.into_stream(); - //TODO: Add this back in when size_hint is added to Stream/StreamExt - //let (lower_bound, _) = stream.size_hint(); - //self.reserve(lower_bound); + + self.reserve(stream.size_hint().0); + Box::pin(stream.for_each(move |item| self.push(item))) } } diff --git a/tests/buf_writer.rs b/tests/buf_writer.rs new file mode 100644 index 00000000..fa0e1ed3 --- /dev/null +++ b/tests/buf_writer.rs @@ -0,0 +1,71 @@ +use async_std::io::{self, BufWriter, SeekFrom}; +use async_std::prelude::*; +use async_std::task; + +#[test] +fn test_buffered_writer() { + task::block_on(async { + let inner = Vec::new(); + let mut writer = BufWriter::with_capacity(2, inner); + + writer.write(&[0, 1]).await.unwrap(); + assert_eq!(writer.buffer(), []); + assert_eq!(*writer.get_ref(), [0, 1]); + + writer.write(&[2]).await.unwrap(); + assert_eq!(writer.buffer(), [2]); + assert_eq!(*writer.get_ref(), [0, 1]); + + writer.write(&[3]).await.unwrap(); + assert_eq!(writer.buffer(), [2, 3]); + assert_eq!(*writer.get_ref(), [0, 1]); + + writer.flush().await.unwrap(); + assert_eq!(writer.buffer(), []); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3]); + + writer.write(&[4]).await.unwrap(); + writer.write(&[5]).await.unwrap(); + assert_eq!(writer.buffer(), [4, 5]); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3]); + + writer.write(&[6]).await.unwrap(); + assert_eq!(writer.buffer(), [6]); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5]); + + writer.write(&[7, 8]).await.unwrap(); + assert_eq!(writer.buffer(), []); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8]); + + writer.write(&[9, 10, 11]).await.unwrap(); + assert_eq!(writer.buffer(), []); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); + + writer.flush().await.unwrap(); + assert_eq!(writer.buffer(), []); + assert_eq!(*writer.get_ref(), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); + }) +} + +#[test] +fn test_buffered_writer_inner_into_inner_does_not_flush() { + 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().await.unwrap(); + assert_eq!(w, []); + }) +} + +#[test] +fn test_buffered_writer_seek() { + task::block_on(async { + let mut w = BufWriter::with_capacity(3, io::Cursor::new(Vec::new())); + w.write_all(&[0, 1, 2, 3, 4, 5]).await.unwrap(); + w.write_all(&[6, 7]).await.unwrap(); + assert_eq!(w.seek(SeekFrom::Current(0)).await.ok(), Some(8)); + assert_eq!(&w.get_ref().get_ref()[..], &[0, 1, 2, 3, 4, 5, 6, 7][..]); + assert_eq!(w.seek(SeekFrom::Start(2)).await.ok(), Some(2)); + }) +} diff --git a/tests/uds.rs b/tests/uds.rs index e64af3ce..3ab4d6ba 100644 --- a/tests/uds.rs +++ b/tests/uds.rs @@ -1,9 +1,14 @@ #![cfg(unix)] use async_std::io; -use async_std::os::unix::net::UnixDatagram; +use async_std::os::unix::net::{UnixDatagram, UnixListener, UnixStream}; +use async_std::prelude::*; use async_std::task; +use tempdir::TempDir; + +use std::time::Duration; + const JULIUS_CAESAR: &[u8] = b" Friends, Romans, countrymen - lend me your ears! I come not to praise Caesar, but to bury him. @@ -39,3 +44,53 @@ fn into_raw_fd() -> io::Result<()> { Ok(()) }) } + +const PING: &[u8] = b"ping"; +const PONG: &[u8] = b"pong"; +const TEST_TIMEOUT: Duration = Duration::from_secs(3); + +#[test] +fn socket_ping_pong() { + let tmp_dir = TempDir::new("socket_ping_pong").expect("Temp dir not created"); + let sock_path = tmp_dir.as_ref().join("sock"); + let iter_cnt = 16; + + let listener = + task::block_on(async { UnixListener::bind(&sock_path).await.expect("Socket bind") }); + + let server_handle = std::thread::spawn(move || { + task::block_on(async { ping_pong_server(listener, iter_cnt).await }).unwrap() + }); + + let client_handle = std::thread::spawn(move || { + task::block_on(async { ping_pong_client(&sock_path, iter_cnt).await }).unwrap() + }); + + client_handle.join().unwrap(); + server_handle.join().unwrap(); +} + +async fn ping_pong_server(listener: UnixListener, iterations: u32) -> std::io::Result<()> { + let mut incoming = listener.incoming(); + let mut buf = [0; 1024]; + for _ix in 0..iterations { + if let Some(s) = incoming.next().await { + let mut s = s?; + let n = s.read(&mut buf[..]).await?; + assert_eq!(&buf[..n], PING); + s.write_all(&PONG).await?; + } + } + Ok(()) +} + +async fn ping_pong_client(socket: &std::path::PathBuf, iterations: u32) -> std::io::Result<()> { + let mut buf = [0; 1024]; + for _ix in 0..iterations { + let mut socket = UnixStream::connect(&socket).await?; + socket.write_all(&PING).await?; + let n = async_std::io::timeout(TEST_TIMEOUT, socket.read(&mut buf[..])).await?; + assert_eq!(&buf[..n], PONG); + } + Ok(()) +}