forked from mirror/async-std
Compare commits
7 commits
master
...
tosocketad
Author | SHA1 | Date | |
---|---|---|---|
|
15445e3ac1 | ||
|
ddb4dc87a1 | ||
|
144e1c4c1b | ||
|
798aa19409 | ||
|
d5e8d11fb9 | ||
|
a81f7b67f7 | ||
|
33ba7f7e13 |
317 changed files with 6127 additions and 25252 deletions
3
.github/CONTRIBUTING.md
vendored
3
.github/CONTRIBUTING.md
vendored
|
@ -1,3 +0,0 @@
|
|||
Our contribution policy can be found at [async.rs/contribute][policy].
|
||||
|
||||
[policy]: https://async.rs/contribute/
|
183
.github/workflows/ci.yml
vendored
183
.github/workflows/ci.yml
vendored
|
@ -1,183 +0,0 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- staging
|
||||
- trying
|
||||
|
||||
env:
|
||||
RUSTFLAGS: -Dwarnings
|
||||
|
||||
jobs:
|
||||
build_and_test:
|
||||
name: Build and test
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macOS-latest]
|
||||
rust: [nightly, beta, stable]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install ${{ matrix.rust }}
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
|
||||
- name: Cache cargo registry
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cargo/registry
|
||||
key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-registry-${{ hashFiles('**/Cargo.toml') }}
|
||||
|
||||
- name: Cache cargo index
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cargo/git
|
||||
key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-index-${{ hashFiles('**/Cargo.toml') }}
|
||||
|
||||
- name: Cache cargo build
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: target
|
||||
key: ${{ matrix.os }}-${{ matrix.rust }}-cargo-build-target-${{ hashFiles('**/Cargo.toml') }}
|
||||
|
||||
- name: check
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all --bins --tests
|
||||
|
||||
- name: check unstable
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --features unstable --all --bins --examples --tests
|
||||
|
||||
- name: check wasm
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
args: --features unstable --all --bins --tests
|
||||
|
||||
- name: check bench
|
||||
uses: actions-rs/cargo@v1
|
||||
if: matrix.rust == 'nightly'
|
||||
with:
|
||||
command: check
|
||||
args: --benches
|
||||
|
||||
- name: check std only
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --no-default-features --features std
|
||||
|
||||
- name: check attributes
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --features attributes
|
||||
|
||||
- name: tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --all --features "unstable attributes"
|
||||
|
||||
build__with_no_std:
|
||||
name: Build with no-std
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: setup
|
||||
run: |
|
||||
rustup default nightly
|
||||
rustup target add thumbv7m-none-eabi
|
||||
|
||||
- name: check no_std
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --no-default-features --features alloc --target thumbv7m-none-eabi -Z avoid-dev-deps
|
||||
|
||||
check_tokio_02_feature:
|
||||
name: Check tokio02 feature
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: check tokio02
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --all --features tokio02
|
||||
|
||||
cross:
|
||||
name: Cross compile
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- i686-unknown-linux-gnu
|
||||
- powerpc-unknown-linux-gnu
|
||||
- powerpc64-unknown-linux-gnu
|
||||
- mips-unknown-linux-gnu
|
||||
- arm-linux-androideabi
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Install nightly
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
override: true
|
||||
|
||||
- name: Install cross
|
||||
run: cargo install cross
|
||||
|
||||
- name: check
|
||||
run: cross check --all --target ${{ matrix.target }}
|
||||
|
||||
- name: check unstable
|
||||
run: cross check --all --features unstable --target ${{ matrix.target }}
|
||||
|
||||
- name: test
|
||||
run: cross test --all --features unstable --target ${{ matrix.target }}
|
||||
|
||||
check_fmt_and_docs:
|
||||
name: Checking fmt and docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
override: true
|
||||
components: rustfmt
|
||||
|
||||
- name: setup
|
||||
run: |
|
||||
rustup component add rustfmt
|
||||
test -x $HOME/.cargo/bin/mdbook || ./ci/install-mdbook.sh
|
||||
rustc --version
|
||||
|
||||
- name: mdbook
|
||||
run: |
|
||||
mdbook build docs
|
||||
- name: fmt
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
- name: Docs
|
||||
run: cargo doc --features docs
|
26
.travis.yml
Normal file
26
.travis.yml
Normal file
|
@ -0,0 +1,26 @@
|
|||
language: rust
|
||||
|
||||
env:
|
||||
- RUSTFLAGS="-D warnings"
|
||||
|
||||
before_script:
|
||||
- rustup component add rustfmt
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- rust: nightly
|
||||
os: linux
|
||||
env: BUILD_DOCS=1
|
||||
- rust: nightly
|
||||
os: osx
|
||||
osx_image: xcode9.2
|
||||
env: BUILD_DOCS=1
|
||||
- rust: nightly-x86_64-pc-windows-msvc
|
||||
os: windows
|
||||
|
||||
script:
|
||||
- cargo check --all --benches --bins --examples --tests
|
||||
- cargo test --all
|
||||
- cargo fmt --all -- --check
|
||||
- if [[ -n "$BUILD_DOCS" ]]; then cargo doc --features docs; fi
|
770
CHANGELOG.md
770
CHANGELOG.md
|
@ -1,771 +1,3 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to async-std will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://book.async.rs/overview/stability-guarantees.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
# [1.6.2] - 2020-06-19
|
||||
|
||||
## Added
|
||||
|
||||
- Add `UdpSocket::peer_addr` ([#816](https://github.com/async-rs/async-std/pull/816))
|
||||
|
||||
## Changed
|
||||
|
||||
## Fixed
|
||||
|
||||
- Ensure the reactor is running for sockets and timers ([#819](https://github.com/async-rs/async-std/pull/819)).
|
||||
- Avoid excessive polling in `flatten` and `flat_map` ([#701](https://github.com/async-rs/async-std/pull/701))
|
||||
|
||||
|
||||
# [1.6.1] - 2020-06-11
|
||||
|
||||
## Added
|
||||
|
||||
- Added `tokio02` feature flag, to allow compatability usage with tokio@0.2 ([#804](https://github.com/async-rs/async-std/pull/804)).
|
||||
|
||||
## Changed
|
||||
|
||||
- Removed unstable `stdio` lock methods, due to their unsoundness ([#807](https://github.com/async-rs/async-std/pull/807)).
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fixed wrong slice index for file reading ([#802](https://github.com/async-rs/async-std/pull/802)).
|
||||
- Fixed recursive calls to `block_on` ([#799](https://github.com/async-rs/async-std/pull/799)) and ([#809](https://github.com/async-rs/async-std/pull/809)).
|
||||
- Remove `default` feature requirement for the `unstable` feature ([#806](https://github.com/async-rs/async-std/pull/806)).
|
||||
|
||||
# [1.6.0] - 2020-05-22
|
||||
|
||||
See `1.6.0-beta.1` and `1.6.0-beta.2`.
|
||||
|
||||
# [1.6.0-beta.2] - 2020-05-19
|
||||
|
||||
## Added
|
||||
|
||||
- Added an environment variable to configure the thread pool size of the runtime. ([#774](https://github.com/async-rs/async-std/pull/774))
|
||||
- Implement `Clone` for `UnixStream` ([#772](https://github.com/async-rs/async-std/pull/772))
|
||||
|
||||
## Changed
|
||||
|
||||
- For `wasm`, switched underlying `Timer` implementation to [`futures-timer`](https://github.com/async-rs/futures-timer). ([#776](https://github.com/async-rs/async-std/pull/776))
|
||||
|
||||
## Fixed
|
||||
|
||||
- Use `smol::block_on` to handle drop of `File`, avoiding nested executor panic. ([#768](https://github.com/async-rs/async-std/pull/768))
|
||||
|
||||
# [1.6.0-beta.1] - 2020-05-07
|
||||
|
||||
## Added
|
||||
|
||||
- Added `task::spawn_local`. ([#757](https://github.com/async-rs/async-std/pull/757))
|
||||
- Added out of the box support for `wasm`. ([#757](https://github.com/async-rs/async-std/pull/757))
|
||||
- Added `JoinHandle::cancel` ([#757](https://github.com/async-rs/async-std/pull/757))
|
||||
- Added `sync::Condvar` ([#369](https://github.com/async-rs/async-std/pull/369))
|
||||
- Added `sync::Sender::try_send` and `sync::Receiver::try_recv` ([#585](https://github.com/async-rs/async-std/pull/585))
|
||||
- Added `no_std` support for `task`, `future` and `stream` ([#680](https://github.com/async-rs/async-std/pull/680))
|
||||
|
||||
## Changed
|
||||
|
||||
- Switched underlying runtime to [`smol`](https://github.com/stjepang/smol/). ([#757](https://github.com/async-rs/async-std/pull/757))
|
||||
- Switched implementation of `sync::Barrier` to use `sync::Condvar` like `std` does. ([#581](https://github.com/async-rs/async-std/pull/581))
|
||||
|
||||
## Fixed
|
||||
|
||||
- Allow compilation on 32 bit targets, by using `AtomicUsize` for `TaskId`. ([#756](https://github.com/async-rs/async-std/pull/756))
|
||||
|
||||
# [1.5.0] - 2020-02-03
|
||||
|
||||
[API Documentation](https://docs.rs/async-std/1.5.0/async-std)
|
||||
|
||||
This patch includes various quality of life improvements to async-std.
|
||||
Including improved performance, stability, and the addition of various
|
||||
`Clone` impls that replace the use of `Arc` in many cases.
|
||||
|
||||
## Added
|
||||
|
||||
- Added links to various ecosystem projects from the README ([#660](https://github.com/async-rs/async-std/pull/660))
|
||||
- Added an example on `FromStream` for `Result<T, E>` ([#643](https://github.com/async-rs/async-std/pull/643))
|
||||
- Added `stream::pending` as "unstable" ([#615](https://github.com/async-rs/async-std/pull/615))
|
||||
- Added an example of `stream::timeout` to document the error flow ([#675](https://github.com/async-rs/async-std/pull/675))
|
||||
- Implement `Clone` for `DirEntry` ([#682](https://github.com/async-rs/async-std/pull/682))
|
||||
- Implement `Clone` for `TcpStream` ([#689](https://github.com/async-rs/async-std/pull/689))
|
||||
|
||||
## Changed
|
||||
|
||||
- Removed internal comment on `stream::Interval` ([#645](https://github.com/async-rs/async-std/pull/645))
|
||||
- The "unstable" feature can now be used without requiring the "default" feature ([#647](https://github.com/async-rs/async-std/pull/647))
|
||||
- Removed unnecessary trait bound on `stream::FlatMap` ([#651](https://github.com/async-rs/async-std/pull/651))
|
||||
- Updated the "broadcaster" dependency used by "unstable" to `1.0.0` ([#681](https://github.com/async-rs/async-std/pull/681))
|
||||
- Updated `async-task` to 1.2.1 ([#676](https://github.com/async-rs/async-std/pull/676))
|
||||
- `task::block_on` now parks after a single poll, improving performance in many cases ([#684](https://github.com/async-rs/async-std/pull/684))
|
||||
- Improved reading flow of the "client" part of the async-std tutorial ([#550](https://github.com/async-rs/async-std/pull/550))
|
||||
- Use `take_while` instead of `scan` in `impl` of `Product`, `Sum` and `FromStream` ([#667](https://github.com/async-rs/async-std/pull/667))
|
||||
- `TcpStream::connect` no longer uses a thread from the threadpool, improving performance ([#687](https://github.com/async-rs/async-std/pull/687))
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fixed crate documentation typo ([#655](https://github.com/async-rs/async-std/pull/655))
|
||||
- Fixed documentation for `UdpSocket::recv` ([#648](https://github.com/async-rs/async-std/pull/648))
|
||||
- Fixed documentation for `UdpSocket::send` ([#671](https://github.com/async-rs/async-std/pull/671))
|
||||
- Fixed typo in stream documentation ([#650](https://github.com/async-rs/async-std/pull/650))
|
||||
- Fixed typo on `sync::JoinHandle` documentation ([#659](https://github.com/async-rs/async-std/pull/659))
|
||||
- Removed use of `std::error::Error::description` which failed CI ([#661](https://github.com/async-rs/async-std/pull/662))
|
||||
- Removed the use of rustfmt's unstable `format_code_in_doc_comments` option which failed CI ([#685](https://github.com/async-rs/async-std/pull/685))
|
||||
- Fixed a code typo in the `task::sleep` example ([#688](https://github.com/async-rs/async-std/pull/688))
|
||||
|
||||
# [1.4.0] - 2019-12-20
|
||||
|
||||
[API Documentation](https://docs.rs/async-std/1.4.0/async-std)
|
||||
|
||||
This patch adds `Future::timeout`, providing a method counterpart to the
|
||||
`future::timeout` free function. And includes several bug fixes around missing
|
||||
APIs. Notably we're not shipping our new executor yet, first announced [on our
|
||||
blog](https://async.rs/blog/stop-worrying-about-blocking-the-new-async-std-runtime/).
|
||||
|
||||
## Examples
|
||||
|
||||
```rust
|
||||
use async_std::prelude::*;
|
||||
use async_std::future;
|
||||
use std::time::Duration;
|
||||
|
||||
let fut = future::pending::<()>(); // This future will never resolve.
|
||||
let res = fut.timeout(Duration::from_millis(100)).await;
|
||||
assert!(res.is_err()); // The future timed out, returning an err.
|
||||
```
|
||||
|
||||
## Added
|
||||
|
||||
- Added `Future::timeout` as "unstable" [(#600)](https://github.com/async-rs/async-std/pull/600)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fixed a doc test and enabled it on CI [(#597)](https://github.com/async-rs/async-std/pull/597)
|
||||
- Fixed a rendering issue with the `stream` submodule documentation [(#621)](https://github.com/async-rs/async-std/pull/621)
|
||||
- `Write::write_fmt`'s future is now correctly marked as `#[must_use]` [(#628)](https://github.com/async-rs/async-std/pull/628)
|
||||
- Fixed the missing `io::Bytes` export [(#633)](https://github.com/async-rs/async-std/pull/633)
|
||||
- Fixed the missing `io::Chain` export [(#633)](https://github.com/async-rs/async-std/pull/633)
|
||||
- Fixed the missing `io::Take` export [(#633)](https://github.com/async-rs/async-std/pull/633)
|
||||
|
||||
# [1.3.0] - 2019-12-12
|
||||
|
||||
[API Documentation](https://docs.rs/async-std/1.3.0/async-std)
|
||||
|
||||
This patch introduces `Stream::delay`, more methods on `DoubleEndedStream`,
|
||||
and improves compile times. `Stream::delay` is a new API that's similar to
|
||||
[`task::sleep`](https://docs.rs/async-std/1.2.0/async_std/task/fn.sleep.html),
|
||||
but can be passed as part of as stream, rather than as a separate block. This is
|
||||
useful for examples, or when manually debugging race conditions.
|
||||
|
||||
## Examples
|
||||
|
||||
```rust
|
||||
let start = Instant::now();
|
||||
let mut s = stream::from_iter(vec![0u8, 1]).delay(Duration::from_millis(200));
|
||||
|
||||
// The first time will take more than 200ms due to delay.
|
||||
s.next().await;
|
||||
assert!(start.elapsed().as_millis() >= 200);
|
||||
|
||||
// There will be no delay after the first time.
|
||||
s.next().await;
|
||||
assert!(start.elapsed().as_millis() <= 210);
|
||||
```
|
||||
|
||||
## Added
|
||||
|
||||
- Added `Stream::delay` as "unstable" [(#309)](https://github.com/async-rs/async-std/pull/309)
|
||||
- Added `DoubleEndedStream::next_back` as "unstable" [(#562)](https://github.com/async-rs/async-std/pull/562)
|
||||
- Added `DoubleEndedStream::nth_back` as "unstable" [(#562)](https://github.com/async-rs/async-std/pull/562)
|
||||
- Added `DoubleEndedStream::rfind` as "unstable" [(#562)](https://github.com/async-rs/async-std/pull/562)
|
||||
- Added `DoubleEndedStream::rfold` as "unstable" [(#562)](https://github.com/async-rs/async-std/pull/562)
|
||||
- Added `DoubleEndedStream::try_rfold` as "unstable" [(#562)](https://github.com/async-rs/async-std/pull/562)
|
||||
- `stream::Once` now implements `DoubleEndedStream` [(#562)](https://github.com/async-rs/async-std/pull/562)
|
||||
- `stream::FromIter` now implements `DoubleEndedStream` [(#562)](https://github.com/async-rs/async-std/pull/562)
|
||||
|
||||
## Changed
|
||||
|
||||
- Removed our dependency on `async-macros`, speeding up compilation [(#610)](https://github.com/async-rs/async-std/pull/610)
|
||||
|
||||
## Fixes
|
||||
|
||||
- Fixed a link in the task docs [(#598)](https://github.com/async-rs/async-std/pull/598)
|
||||
- Fixed the `UdpSocket::recv` example [(#603)](https://github.com/async-rs/async-std/pull/603)
|
||||
- Fixed a link to `task::block_on` [(#608)](https://github.com/async-rs/async-std/pull/608)
|
||||
- Fixed an incorrect API mention in `task::Builder` [(#612)](https://github.com/async-rs/async-std/pull/612)
|
||||
- Fixed leftover mentions of `futures-preview` [(#595)](https://github.com/async-rs/async-std/pull/595)
|
||||
- Fixed a typo in the tutorial [(#614)](https://github.com/async-rs/async-std/pull/614)
|
||||
- `<TcpStream as Write>::poll_close` now closes the write half of the stream [(#618)](https://github.com/async-rs/async-std/pull/618)
|
||||
|
||||
# [1.2.0] - 2019-11-27
|
||||
|
||||
[API Documentation](https://docs.rs/async-std/1.2.0/async-std)
|
||||
|
||||
This patch includes some minor quality-of-life improvements, introduces a
|
||||
new `Stream::unzip` API, and adds verbose errors to our networking types.
|
||||
|
||||
This means if you can't connect to a socket, you'll never have to wonder again
|
||||
*which* address it was you couldn't connect to, instead of having to go through
|
||||
the motions to debug what the address was.
|
||||
|
||||
## Example
|
||||
|
||||
Unzip a stream of tuples into two collections:
|
||||
|
||||
```rust
|
||||
use async_std::prelude::*;
|
||||
use async_std::stream;
|
||||
|
||||
let s = stream::from_iter(vec![(1,2), (3,4)]);
|
||||
|
||||
let (left, right): (Vec<_>, Vec<_>) = s.unzip().await;
|
||||
|
||||
assert_eq!(left, [1, 3]);
|
||||
assert_eq!(right, [2, 4]);
|
||||
```
|
||||
|
||||
## Added
|
||||
|
||||
- Added `Stream::unzip` as "unstable".
|
||||
- Added verbose errors to the networking types.
|
||||
|
||||
## Changed
|
||||
|
||||
- Enabled CI on master branch.
|
||||
- `Future::join` and `Future::try_join` can now join futures with different
|
||||
output types.
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fixed the docs and `Debug` output of `BufWriter`.
|
||||
- Fixed a bug in `Stream::throttle` that made it consume too much CPU.
|
||||
|
||||
# [1.1.0] - 2019-11-21
|
||||
|
||||
[API Documentation](https://docs.rs/async-std/1.1.0/async-std)
|
||||
|
||||
This patch introduces a faster scheduler algorithm, `Stream::throttle`, and
|
||||
stabilizes `task::yield_now`. Additionally we're introducing several more stream
|
||||
APIs, bringing us to almost complete parity with the standard library.
|
||||
|
||||
Furthermore our `path` submodule now returns more context in errors. So if
|
||||
opening a file fails, async-std will tell you *which* file was failed to open,
|
||||
making it easier to write and debug programs.
|
||||
|
||||
## Examples
|
||||
|
||||
```rust
|
||||
let start = Instant::now();
|
||||
|
||||
let mut s = stream::interval(Duration::from_millis(5))
|
||||
.throttle(Duration::from_millis(10))
|
||||
.take(2);
|
||||
|
||||
s.next().await;
|
||||
assert!(start.elapsed().as_millis() >= 5);
|
||||
|
||||
s.next().await;
|
||||
assert!(start.elapsed().as_millis() >= 15);
|
||||
|
||||
s.next().await;
|
||||
assert!(start.elapsed().as_millis() >= 25);
|
||||
```
|
||||
|
||||
## Added
|
||||
|
||||
- Added `Stream::throttle` as "unstable".
|
||||
- Added `Stream::count` as "unstable".
|
||||
- Added `Stream::max` as "unstable".
|
||||
- Added `Stream::successors` as "unstable".
|
||||
- Added `Stream::by_ref` as "unstable".
|
||||
- Added `Stream::partition` as "unstable".
|
||||
- Added contextual errors to the `path` submodule.
|
||||
- Added `os::windows::symlink_dir` as "unstable".
|
||||
- Added `os::windows::symlink_file` as "unstable".
|
||||
- Stabilized `task::yield_now`.
|
||||
|
||||
## Fixes
|
||||
|
||||
- We now ignore seek errors when rolling back failed `read` calls on `File`.
|
||||
- Fixed a bug where `Stream::max_by_key` was returning the wrong result.
|
||||
- Fixed a bug where `Stream::min_by_key` was returning the wrong result.
|
||||
|
||||
## Changed
|
||||
|
||||
- Applied various fixes to the tutorial.
|
||||
- Fixed an issue with Clippy.
|
||||
- Optimized an internal code generation macro, improving compilation speeds.
|
||||
- Removed an `Unpin` bound from `stream::Once`.
|
||||
- Removed various extra internal uses of `pin_mut!`.
|
||||
- Simplified `Stream::any` and `Stream::all`'s internals.
|
||||
- The `surf` example is now enabled again.
|
||||
- Tweaked some streams internals.
|
||||
- Updated `futures-timer` to 2.0.0, improving compilation speed.
|
||||
- Upgraded `async-macros` to 2.0.0.
|
||||
- `Stream::merge` now uses randomized ordering to reduce overall latency.
|
||||
- The scheduler is now more efficient by keeping a slot for the next task to
|
||||
run. This is similar to Go's scheduler, and Tokio's scheduler.
|
||||
- Fixed the documentation of the `channel` types to link back to the `channel`
|
||||
function.
|
||||
|
||||
# [1.0.1] - 2019-11-12
|
||||
|
||||
[API Documentation](https://docs.rs/async-std/1.0.1/async-std)
|
||||
|
||||
We were seeing a regression in our fs performance, caused by too many
|
||||
long-running tasks. This patch fixes that regression by being more proactive
|
||||
about closing down idle threads.
|
||||
|
||||
## Changes
|
||||
|
||||
- Improved thread startup/shutdown algorithm in `task::spawn_blocking`.
|
||||
- Fixed a typo in the tutorial.
|
||||
|
||||
# [1.0.0] - 2019-11-11
|
||||
|
||||
[API Documentation](https://docs.rs/async-std/1.0.0/async-std)
|
||||
|
||||
This release marks the `1.0.0` release of async-std; a major milestone for our
|
||||
development. This release itself mostly includes quality of life improvements
|
||||
for all of modules, including more consistent API bounds for a lot of our
|
||||
submodules.
|
||||
|
||||
The biggest change is that we're now using the full semver range,
|
||||
`major.minor.patch`, and any breaking changes to our "stable" APIs will require
|
||||
an update of the `major` number.
|
||||
|
||||
We're excited we've hit this milestone together with you all. Thank you!
|
||||
|
||||
## Added
|
||||
|
||||
- Added `Future::join` as "unstable", replacing `future::join!`.
|
||||
- Added `Future::try_join` as "unstable", replacing `future::try_join!`.
|
||||
- Enabled `stable` and `beta` channel testing on CI.
|
||||
- Implemented `FromIterator` and `Extend` for `PathBuf`.
|
||||
- Implemented `FromStream` for `PathBuf`.
|
||||
- Loosened the trait bounds of `io::copy` on "unstable".
|
||||
|
||||
## Changed
|
||||
|
||||
- Added a `Sync` bound to `RwLock`, resolving a memory safety issue.
|
||||
- Fixed a bug in `Stream::take_while` where it could continue after it should've
|
||||
ended.
|
||||
- Fixed a bug where our `attributes` Cargo feature wasn't working as intended.
|
||||
- Improved documentation of `Stream::merge`, documenting ordering guarantees.
|
||||
- Update doc imports in examples to prefer async-std's types.
|
||||
- Various quality of life improvements to the `future` submodule.
|
||||
- Various quality of life improvements to the `path` submodule.
|
||||
- Various quality of life improvements to the `stream` submodule.
|
||||
|
||||
## Removed
|
||||
|
||||
- Removed `future::join!` in favor of `Future::join`.
|
||||
- Removed `future::try_join!` in favor of `Future::try_join`.
|
||||
|
||||
# [0.99.12] - 2019-11-07
|
||||
|
||||
[API Documentation](https://docs.rs/async-std/0.99.12/async-std)
|
||||
|
||||
This patch upgrades us to `futures` 0.3, support for `async/await` on Rust
|
||||
Stable, performance improvements, and brand new module-level documentation.
|
||||
|
||||
## Added
|
||||
|
||||
- Added `Future::flatten` as "unstable".
|
||||
- Added `Future::race` as "unstable" (replaces `future::select!`).
|
||||
- Added `Future::try_race` as "unstable" (replaces `future::try_select!`).
|
||||
- Added `Stderr::lock` as "unstable".
|
||||
- Added `Stdin::lock` as "unstable".
|
||||
- Added `Stdout::lock` as "unstable".
|
||||
- Added `Stream::copied` as "unstable".
|
||||
- Added `Stream::eq` as "unstable".
|
||||
- Added `Stream::max_by_key` as "unstable".
|
||||
- Added `Stream::min` as "unstable".
|
||||
- Added `Stream::ne` as "unstable".
|
||||
- Added `Stream::position` as "unstable".
|
||||
- Added `StreamExt` and `FutureExt` as enumerable in the `prelude`.
|
||||
- Added `TcpListener` and `TcpStream` integration tests.
|
||||
- Added `stream::from_iter`.
|
||||
- Added `sync::WakerSet` for internal use.
|
||||
- Added an example to handle both `IP v4` and `IP v6` connections.
|
||||
- Added the `default` Cargo feature.
|
||||
- Added the `attributes` Cargo feature.
|
||||
- Added the `std` Cargo feature.
|
||||
|
||||
## Changed
|
||||
|
||||
- Fixed a bug in the blocking threadpool where it didn't spawn more than one thread.
|
||||
- Fixed a bug with `Stream::merge` where sometimes it ended too soon.
|
||||
- Fixed a bug with our GitHub actions setup.
|
||||
- Fixed an issue where our channels could spuriously deadlock.
|
||||
- Refactored the `task` module.
|
||||
- Removed a deprecated GitHub action.
|
||||
- Replaced `futures-preview` with `futures`.
|
||||
- Replaced `lazy_static` with `once_cell`.
|
||||
- Replaced all uses of `VecDequeue` in the examples with `stream::from_iter`.
|
||||
- Simplified `sync::RwLock` using the internal `sync::WakerSet` type.
|
||||
- Updated the `path` submodule documentation to match std.
|
||||
- Updated the mod-level documentation to match std.
|
||||
|
||||
## Removed
|
||||
|
||||
- Removed `future::select!` (replaced by `Future::race`).
|
||||
- Removed `future::try_select!` (replaced by `Future::try_race`).
|
||||
|
||||
# [0.99.11] - 2019-10-29
|
||||
|
||||
This patch introduces `async_std::sync::channel`, a novel asynchronous port of
|
||||
the ultra-fast Crossbeam channels. This has been one of the most anticipated
|
||||
features for async-std, and we're excited to be providing a first version of
|
||||
this!
|
||||
|
||||
In addition to channels, this patch has the regular list of new methods, types,
|
||||
and doc fixes.
|
||||
|
||||
## Examples
|
||||
|
||||
__Send and receive items from a channel__
|
||||
```rust
|
||||
// Create a bounded channel with a max-size of 1
|
||||
let (s, r) = channel(1);
|
||||
|
||||
// This call returns immediately because there is enough space in the channel.
|
||||
s.send(1).await;
|
||||
|
||||
task::spawn(async move {
|
||||
// This call blocks the current task because the channel is full.
|
||||
// It will be able to complete only after the first message is received.
|
||||
s.send(2).await;
|
||||
});
|
||||
|
||||
// Receive items from the channel
|
||||
task::sleep(Duration::from_secs(1)).await;
|
||||
assert_eq!(r.recv().await, Some(1));
|
||||
assert_eq!(r.recv().await, Some(2));
|
||||
```
|
||||
|
||||
## Added
|
||||
- Added `Future::delay` as "unstable"
|
||||
- Added `Stream::flat_map` as "unstable"
|
||||
- Added `Stream::flatten` as "unstable"
|
||||
- Added `Stream::product` as "unstable"
|
||||
- Added `Stream::sum` as "unstable"
|
||||
- Added `Stream::min_by_key`
|
||||
- Added `Stream::max_by`
|
||||
- Added `Stream::timeout` as "unstable"
|
||||
- Added `sync::channel` as "unstable".
|
||||
- Added doc links from instantiated structs to the methods that create them.
|
||||
- Implemented `Extend` + `FromStream` for `PathBuf`.
|
||||
|
||||
## Changed
|
||||
- Fixed an issue with `block_on` so it works even when nested.
|
||||
- Fixed issues with our Clippy check on CI.
|
||||
- Replaced our uses of `cfg_if` with our own macros, simplifying the codebase.
|
||||
- Updated the homepage link in `Cargo.toml` to point to [async.rs](https://async.rs).
|
||||
- Updated the module-level documentation for `stream` and `sync`.
|
||||
- Various typos and grammar fixes.
|
||||
- Removed redundant file flushes, improving the performance of `File` operations
|
||||
|
||||
## Removed
|
||||
Nothing was removed in this release.
|
||||
|
||||
# [0.99.10] - 2019-10-16
|
||||
|
||||
This patch stabilizes several core concurrency macros, introduces async versions
|
||||
of `Path` and `PathBuf`, and adds almost 100 other commits.
|
||||
|
||||
## Examples
|
||||
|
||||
__Asynchronously read directories from the filesystem__
|
||||
```rust
|
||||
use async_std::fs;
|
||||
use async_std::path::Path;
|
||||
use async_std::prelude::*;
|
||||
|
||||
let path = Path::new("/laputa");
|
||||
let mut dir = fs::read_dir(&path).await.unwrap();
|
||||
while let Some(entry) = dir.next().await {
|
||||
if let Ok(entry) = entry {
|
||||
println!("{:?}", entry.path());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
__Cooperatively reschedule the current task on the executor__
|
||||
```rust
|
||||
use async_std::prelude::*;
|
||||
use async_std::task;
|
||||
|
||||
task::spawn(async {
|
||||
let x = fibonnacci(1000); // Do expensive work
|
||||
task::yield_now().await; // Allow other tasks to run
|
||||
x + fibonnacci(100) // Do more work
|
||||
})
|
||||
```
|
||||
|
||||
__Create an interval stream__
|
||||
```rust
|
||||
use async_std::prelude::*;
|
||||
use async_std::stream;
|
||||
use std::time::Duration;
|
||||
|
||||
let mut interval = stream::interval(Duration::from_secs(4));
|
||||
while let Some(_) = interval.next().await {
|
||||
println!("prints every four seconds");
|
||||
}
|
||||
```
|
||||
|
||||
## Added
|
||||
|
||||
- Added `FutureExt` to the `prelude`, allowing us to extend `Future`
|
||||
- Added `Stream::cmp`
|
||||
- Added `Stream::ge`
|
||||
- Added `Stream::last`
|
||||
- Added `Stream::le`
|
||||
- Added `Stream::lt`
|
||||
- Added `Stream::merge` as "unstable", replacing `stream::join!`
|
||||
- Added `Stream::partial_cmp`
|
||||
- Added `Stream::take_while`
|
||||
- Added `Stream::try_fold`
|
||||
- Added `future::IntoFuture` as "unstable"
|
||||
- Added `io::BufRead::split`
|
||||
- Added `io::Write::write_fmt`
|
||||
- Added `print!`, `println!`, `eprint!`, `eprintln!` macros as "unstable"
|
||||
- Added `process` as "unstable", re-exporting std types only for now
|
||||
- Added `std::net` re-exports to the `net` submodule
|
||||
- Added `std::path::PathBuf` with all associated methods
|
||||
- Added `std::path::Path` with all associated methods
|
||||
- Added `stream::ExactSizeStream` as "unstable"
|
||||
- Added `stream::FusedStream` as "unstable"
|
||||
- Added `stream::Product`
|
||||
- Added `stream::Sum`
|
||||
- Added `stream::from_fn`
|
||||
- Added `stream::interval` as "unstable"
|
||||
- Added `stream::repeat_with`
|
||||
- Added `task::spawn_blocking` as "unstable", replacing `task::blocking`
|
||||
- Added `task::yield_now`
|
||||
- Added `write!` and `writeln!` macros as "unstable"
|
||||
- Stabilized `future::join!` and `future::try_join!`
|
||||
- Stabilized `future::timeout`
|
||||
- Stabilized `path`
|
||||
- Stabilized `task::ready!`
|
||||
|
||||
## Changed
|
||||
|
||||
- Fixed `BufWriter::into_inner` so it calls `flush` before yielding
|
||||
- Refactored `io::BufWriter` internals
|
||||
- Refactored `net::ToSocketAddrs` internals
|
||||
- Removed Travis CI entirely
|
||||
- Rewrote the README.md
|
||||
- Stabilized `io::Cursor`
|
||||
- Switched bors over to use GitHub actions
|
||||
- Updated the `io` documentation to match std's `io` docs
|
||||
- Updated the `task` documentation to match std's `thread` docs
|
||||
|
||||
## Removed
|
||||
|
||||
- Removed the "unstable" `stream::join!` in favor of `Stream::merge`
|
||||
- Removed the "unstable" `task::blocking` in favor of `task::spawn_blocking`
|
||||
|
||||
# [0.99.9] - 2019-10-08
|
||||
|
||||
This patch upgrades our `futures-rs` version, allowing us to build on the 1.39
|
||||
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<u8, u8> = 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
|
||||
|
||||
- Added README to examples directory.
|
||||
- Added concurrency documentation to the futures submodule.
|
||||
- Added `io::Read::take` method.
|
||||
- Added `io::Read::by_ref` method.
|
||||
- Added `io::Read::chain` method.
|
||||
|
||||
## Changed
|
||||
|
||||
- Pin futures-preview to `0.3.0-alpha.18`, to avoid rustc upgrade problems.
|
||||
- Simplified extension traits using a macro.
|
||||
- Use the `broadcast` module with `std::sync::Mutex`, reducing dependencies.
|
||||
|
||||
# [0.99.7] - 2019-09-26
|
||||
|
||||
## Added
|
||||
|
||||
- Added `future::join` macro as "unstable"
|
||||
- Added `future::select` macro as "unstable"
|
||||
- Added `future::try_join` macro as "unstable"
|
||||
- Added `future::try_select` macro as "unstable"
|
||||
- Added `io::BufWriter` struct
|
||||
- Added `stream::Extend` trait
|
||||
- Added `stream::Stream::chain` method
|
||||
- Added `stream::Stream::filter` method
|
||||
- Added `stream::Stream::inspect` method
|
||||
- Added `stream::Stream::skip_while` method
|
||||
- Added `stream::Stream::skip` method
|
||||
- Added `stream::Stream::step_by` method
|
||||
- Added `sync::Arc` struct from stdlib
|
||||
- Added `sync::Barrier` struct as "unstable"
|
||||
- Added `sync::Weak` struct from stdlib
|
||||
- Added `task::ready` macro as "unstable"
|
||||
|
||||
## Changed
|
||||
|
||||
- Correctly marked the `pin` submodule as "unstable" in the docs
|
||||
- Updated tutorial to have certain functions suffixed with `_loop`
|
||||
- `io` traits are now re-exports of futures-rs types, allowing them to be
|
||||
implemented
|
||||
- `stream` traits are now re-exports of futures-rs types, allowing them to be
|
||||
implemented
|
||||
- `prelude::*` now needs to be in scope for functions `io` and `stream` traits
|
||||
to work
|
||||
|
||||
# [0.99.6] - 2019-09-19
|
||||
|
||||
## Added
|
||||
|
||||
- Added `stream::Stream::collect` as "unstable"
|
||||
- Added `stream::Stream::enumerate`
|
||||
- Added `stream::Stream::fuse`
|
||||
- Added `stream::Stream::fold`
|
||||
- Added `stream::Stream::scan`
|
||||
- Added `stream::Stream::zip`
|
||||
- Added `stream::join` macro as "unstable"
|
||||
- Added `stream::DoubleEndedStream` as "unstable"
|
||||
- Added `stream::FromStream` trait as "unstable"
|
||||
- Added `stream::IntoStream` trait as "unstable"
|
||||
- Added `io::Cursor` as "unstable"
|
||||
- Added `io::BufRead::consume` method
|
||||
- Added `io::repeat`
|
||||
- Added `io::Slice` and `io::SliceMut`
|
||||
- Added documentation for feature flags
|
||||
- Added `pin` submodule as "unstable"
|
||||
- Added the ability to `collect` a stream of `Result<T, E>`s into a
|
||||
`Result<impl FromStream<T>, E>`
|
||||
|
||||
## Changed
|
||||
|
||||
- Refactored the scheduling algorithm of our executor to use work stealing
|
||||
- Refactored the network driver, removing 400 lines of code
|
||||
- Removed the `Send` bound from `task::block_on`
|
||||
- Removed `Unpin` bound from `impl<T: futures::stream::Stream> Stream for T`
|
||||
|
||||
# [0.99.5] - 2019-09-12
|
||||
|
||||
## Added
|
||||
|
||||
- Added tests for `io::timeout`
|
||||
- Added `io::BufRead::fill_buf`, an `async fn` counterpart to `poll_fill_buf`
|
||||
- Added `fs::create_dir_all`
|
||||
- Added `future::timeout`, a free function to time out futures after a threshold
|
||||
- Added `io::prelude`
|
||||
- Added `net::ToSocketAddrs`, a non-blocking version of std's `ToSocketAddrs`
|
||||
- Added `stream::Stream::all`
|
||||
- Added `stream::Stream::filter_map`
|
||||
- Added `stream::Stream::find_map`
|
||||
- Added `stream::Stream::find`
|
||||
- Added `stream::Stream::min_by`
|
||||
- Added `stream::Stream::nth`
|
||||
|
||||
## Changed
|
||||
|
||||
- Polished the text and examples of the tutorial
|
||||
- `cargo fmt` on all examples
|
||||
- Simplified internals of `TcpStream::connect_to`
|
||||
- Modularized our CI setup, enabled a rustfmt fallback, and improved caching
|
||||
- Reduced our dependency on the `futures-rs` crate, improving compilation times
|
||||
- Split `io::Read`, `io::Write`, `io::BufRead`, and `stream::Stream` into
|
||||
multiple files
|
||||
- `fs::File` now flushes more often to prevent flushes during `seek`
|
||||
- Updated all dependencies
|
||||
- Fixed a bug in the conversion of `File` into raw handle
|
||||
- Fixed compilation errors on the latest nightly
|
||||
|
||||
## Removed
|
||||
|
||||
# [0.99.4] - 2019-08-21
|
||||
|
||||
## Changes
|
||||
|
||||
- Many small changes in the book, mostly typos
|
||||
- Documentation fixes correcting examples
|
||||
- Now works with recent nightly with stabilised async/await (> 2019-08-21)
|
||||
|
||||
# [0.99.3] - 2019-08-16
|
||||
# Version 0.99.3
|
||||
|
||||
- Initial beta release
|
||||
|
||||
[Unreleased]: https://github.com/async-rs/async-std/compare/v1.6.2...HEAD
|
||||
[1.6.2]: https://github.com/async-rs/async-std/compare/v1.6.1...v1.6.2
|
||||
[1.6.1]: https://github.com/async-rs/async-std/compare/v1.6.0...v1.6.1
|
||||
[1.6.0]: https://github.com/async-rs/async-std/compare/v1.5.0...v1.6.0
|
||||
[1.6.0-beta.2]: https://github.com/async-rs/async-std/compare/v1.6.0-beta.1...v1.6.0-beta.2
|
||||
[1.6.0-beta.1]: https://github.com/async-rs/async-std/compare/v1.5.0...v1.6.0-beta.1
|
||||
[1.5.0]: https://github.com/async-rs/async-std/compare/v1.4.0...v1.5.0
|
||||
[1.4.0]: https://github.com/async-rs/async-std/compare/v1.3.0...v1.4.0
|
||||
[1.3.0]: https://github.com/async-rs/async-std/compare/v1.2.0...v1.3.0
|
||||
[1.2.0]: https://github.com/async-rs/async-std/compare/v1.1.0...v1.2.0
|
||||
[1.1.0]: https://github.com/async-rs/async-std/compare/v1.0.1...v1.1.0
|
||||
[1.0.1]: https://github.com/async-rs/async-std/compare/v1.0.0...v1.0.1
|
||||
[1.0.0]: https://github.com/async-rs/async-std/compare/v0.99.12...v1.0.0
|
||||
[0.99.12]: https://github.com/async-rs/async-std/compare/v0.99.11...v0.99.12
|
||||
[0.99.11]: https://github.com/async-rs/async-std/compare/v0.99.10...v0.99.11
|
||||
[0.99.10]: https://github.com/async-rs/async-std/compare/v0.99.9...v0.99.10
|
||||
[0.99.9]: https://github.com/async-rs/async-std/compare/v0.99.8...v0.99.9
|
||||
[0.99.8]: https://github.com/async-rs/async-std/compare/v0.99.7...v0.99.8
|
||||
[0.99.7]: https://github.com/async-rs/async-std/compare/v0.99.6...v0.99.7
|
||||
[0.99.6]: https://github.com/async-rs/async-std/compare/v0.99.5...v0.99.6
|
||||
[0.99.5]: https://github.com/async-rs/async-std/compare/v0.99.4...v0.99.5
|
||||
[0.99.4]: https://github.com/async-rs/async-std/compare/v0.99.3...v0.99.4
|
||||
[0.99.3]: https://github.com/async-rs/async-std/tree/v0.99.3
|
||||
|
|
102
Cargo.toml
102
Cargo.toml
|
@ -1,16 +1,14 @@
|
|||
[package]
|
||||
name = "async-std"
|
||||
version = "1.6.2"
|
||||
version = "0.99.3"
|
||||
authors = [
|
||||
"Stjepan Glavina <stjepang@gmail.com>",
|
||||
"Yoshua Wuyts <yoshuawuyts@gmail.com>",
|
||||
"Friedel Ziegelmayer <me@dignifiedquire.com>",
|
||||
"Contributors to async-std",
|
||||
"The async-std Project Developers",
|
||||
]
|
||||
edition = "2018"
|
||||
license = "Apache-2.0/MIT"
|
||||
repository = "https://github.com/async-rs/async-std"
|
||||
homepage = "https://async.rs"
|
||||
homepage = "https://github.com/async-rs/async-std"
|
||||
documentation = "https://docs.rs/async-std"
|
||||
description = "Async version of the Rust standard library"
|
||||
keywords = ["async", "await", "future", "std", "task"]
|
||||
|
@ -22,86 +20,24 @@ features = ["docs"]
|
|||
rustdoc-args = ["--cfg", "feature=\"docs\""]
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"std",
|
||||
"async-task",
|
||||
"kv-log-macro",
|
||||
"log",
|
||||
"num_cpus",
|
||||
"pin-project-lite",
|
||||
"smol",
|
||||
]
|
||||
docs = ["attributes", "unstable", "default"]
|
||||
unstable = [
|
||||
"std",
|
||||
"futures-timer",
|
||||
]
|
||||
attributes = ["async-attributes"]
|
||||
std = [
|
||||
"alloc",
|
||||
"crossbeam-utils",
|
||||
"futures-core/std",
|
||||
"futures-io",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
"wasm-bindgen-futures",
|
||||
"futures-channel",
|
||||
"async-mutex",
|
||||
]
|
||||
alloc = [
|
||||
"futures-core/alloc",
|
||||
"pin-project-lite",
|
||||
]
|
||||
tokio02 = ["smol/tokio02"]
|
||||
docs = []
|
||||
|
||||
[dependencies]
|
||||
async-attributes = { version = "1.1.1", optional = true }
|
||||
async-task = { version = "3.0.0", optional = true }
|
||||
async-mutex = { version = "1.1.3", optional = true }
|
||||
crossbeam-utils = { version = "0.7.2", optional = true }
|
||||
futures-core = { version = "0.3.4", optional = true, default-features = false }
|
||||
futures-io = { version = "0.3.4", optional = true }
|
||||
kv-log-macro = { version = "1.0.6", optional = true }
|
||||
log = { version = "0.4.8", features = ["kv_unstable"], optional = true }
|
||||
memchr = { version = "2.3.3", optional = true }
|
||||
num_cpus = { version = "1.12.0", optional = true }
|
||||
once_cell = { version = "1.3.1", optional = true }
|
||||
pin-project-lite = { version = "0.1.4", optional = true }
|
||||
pin-utils = { version = "0.1.0-alpha.4", optional = true }
|
||||
slab = { version = "0.4.2", optional = true }
|
||||
futures-timer = { version = "3.0.2", optional = true }
|
||||
|
||||
# Devdepencency, but they are not allowed to be optional :/
|
||||
surf = { version = "1.0.3", optional = true }
|
||||
|
||||
[target.'cfg(not(target_os = "unknown"))'.dependencies]
|
||||
smol = { version = "0.1.17", optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
futures-timer = { version = "3.0.2", optional = true, features = ["wasm-bindgen"] }
|
||||
wasm-bindgen-futures = { version = "0.4.10", optional = true }
|
||||
futures-channel = { version = "0.3.4", optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.10"
|
||||
async-task = "1.0.0"
|
||||
cfg-if = "0.1.9"
|
||||
crossbeam-channel = "0.3.9"
|
||||
futures-preview = "0.3.0-alpha.17"
|
||||
futures-timer = "0.3.0"
|
||||
lazy_static = "1.3.0"
|
||||
log = { version = "0.4.8", features = ["kv_unstable"] }
|
||||
memchr = "2.2.1"
|
||||
mio = "0.6.19"
|
||||
mio-uds = "0.6.7"
|
||||
num_cpus = "1.10.0"
|
||||
pin-utils = "0.1.0-alpha.4"
|
||||
slab = "0.4.2"
|
||||
|
||||
[dev-dependencies]
|
||||
femme = "1.3.0"
|
||||
rand = "0.7.3"
|
||||
femme = "1.1.0"
|
||||
tempdir = "0.3.7"
|
||||
futures = "0.3.4"
|
||||
rand_xorshift = "0.2.0"
|
||||
|
||||
[[test]]
|
||||
name = "stream"
|
||||
required-features = ["unstable"]
|
||||
|
||||
[[example]]
|
||||
name = "tcp-ipv4-and-6-echo"
|
||||
required-features = ["unstable"]
|
||||
|
||||
[[example]]
|
||||
name = "surf-web"
|
||||
required-features = ["surf"]
|
||||
surf = "1.0.1"
|
||||
|
|
230
README.md
230
README.md
|
@ -1,159 +1,127 @@
|
|||
<h1 align="center">async-std</h1>
|
||||
<div align="center">
|
||||
<strong>
|
||||
Async version of the Rust standard library
|
||||
</strong>
|
||||
</div>
|
||||
# Async version of Rust's standard library
|
||||
|
||||
<br />
|
||||
[![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)
|
||||
|
||||
<div align="center">
|
||||
<!-- CI status -->
|
||||
<a href="https://github.com/async-rs/async-std/actions">
|
||||
<img src="https://github.com/async-rs/async-std/workflows/CI/badge.svg"
|
||||
alt="CI Status" />
|
||||
</a>
|
||||
<!-- Crates version -->
|
||||
<a href="https://crates.io/crates/async-std">
|
||||
<img src="https://img.shields.io/crates/v/async-std.svg?style=flat-square"
|
||||
alt="Crates.io version" />
|
||||
</a>
|
||||
<!-- Downloads -->
|
||||
<a href="https://crates.io/crates/async-std">
|
||||
<img src="https://img.shields.io/crates/d/async-std.svg?style=flat-square"
|
||||
alt="Download" />
|
||||
</a>
|
||||
<!-- docs.rs docs -->
|
||||
<a href="https://docs.rs/async-std">
|
||||
<img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
|
||||
alt="docs.rs docs" />
|
||||
</a>
|
||||
|
||||
<a href="https://discord.gg/JvZeVNe">
|
||||
<img src="https://img.shields.io/discord/598880689856970762.svg?logo=discord&style=flat-square"
|
||||
alt="chat" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div align="center">
|
||||
<h3>
|
||||
<a href="https://docs.rs/async-std">
|
||||
API Docs
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://book.async.rs">
|
||||
Book
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://github.com/async-rs/async-std/releases">
|
||||
Releases
|
||||
</a>
|
||||
<span> | </span>
|
||||
<a href="https://async.rs/contribute">
|
||||
Contributing
|
||||
</a>
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
## Features
|
||||
## Documentation
|
||||
|
||||
- __Modern:__ Built from the ground up for `std::future` and `async/await` with
|
||||
blazing fast compilation time.
|
||||
- __Fast:__ Our robust allocator and threadpool designs provide ultra-high
|
||||
throughput with predictably low latency.
|
||||
- __Intuitive:__ Complete parity with the stdlib means you only need to learn
|
||||
APIs once.
|
||||
- __Clear:__ [Detailed documentation][docs] and [accessible guides][book] mean
|
||||
using async Rust was never easier.
|
||||
`async-std` comes with [extensive API documentation][docs] and a [book][book].
|
||||
|
||||
[docs]: https://docs.rs/async-std
|
||||
[book]: https://book.async.rs
|
||||
|
||||
## Examples
|
||||
## Quickstart
|
||||
|
||||
```rust
|
||||
use async_std::task;
|
||||
Add the following lines to you `Cargo.toml`:
|
||||
|
||||
async fn say_hello() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
task::block_on(say_hello())
|
||||
}
|
||||
```toml
|
||||
[dependencies]
|
||||
async-std = "0.99"
|
||||
```
|
||||
|
||||
More examples, including networking and file access, can be found in our
|
||||
[`examples`] directory and in our [documentation].
|
||||
|
||||
[`examples`]: https://github.com/async-rs/async-std/tree/master/examples
|
||||
[documentation]: https://docs.rs/async-std#examples
|
||||
[`task::block_on`]: https://docs.rs/async-std/*/async_std/task/fn.block_on.html
|
||||
[`"attributes"` feature]: https://docs.rs/async-std/#features
|
||||
|
||||
## Philosophy
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Installation
|
||||
|
||||
With [cargo add][cargo-add] installed run:
|
||||
Or use [cargo add][cargo-add] if you have it installed:
|
||||
|
||||
```sh
|
||||
$ cargo add async-std
|
||||
```
|
||||
|
||||
We also provide a set of "unstable" features with async-std. See the [features
|
||||
documentation] on how to enable them.
|
||||
|
||||
[cargo-add]: https://github.com/killercup/cargo-edit
|
||||
[features documentation]: https://docs.rs/async-std/#features
|
||||
|
||||
## Ecosystem
|
||||
|
||||
* [async-tls](https://crates.io/crates/async-tls) — Async TLS/SSL streams using **Rustls**.
|
||||
|
||||
* [async-native-tls](https://crates.io/crates/async-native-tls) — **Native TLS** for Async. Native TLS for futures and async-std.
|
||||
|
||||
* [async-tungstenite](https://crates.io/crates/async-tungstenite) — Asynchronous **WebSockets** for async-std, tokio, gio and any std Futures runtime.
|
||||
|
||||
* [Tide](https://crates.io/crates/tide) — Serve the web. A modular **web framework** built around async/await.
|
||||
## Hello world
|
||||
|
||||
* [SQLx](https://crates.io/crates/sqlx) — The Rust **SQL** Toolkit. SQLx is a 100% safe Rust library for Postgres and MySQL with compile-time checked queries.
|
||||
```rust
|
||||
#![feature(async_await)]
|
||||
|
||||
use async_std::task;
|
||||
|
||||
fn main() {
|
||||
task::block_on(async {
|
||||
println!("Hello, world!");
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Low-Friction Sockets with Built-In Timeouts
|
||||
|
||||
```rust
|
||||
#![feature(async_await)]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use async_std::{
|
||||
prelude::*,
|
||||
task,
|
||||
io,
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
async fn get() -> io::Result<Vec<u8>> {
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Take a look around
|
||||
|
||||
Clone the repo:
|
||||
|
||||
```
|
||||
git clone git@github.com:async-rs/async-std.git && cd async-std
|
||||
```
|
||||
|
||||
Generate docs:
|
||||
|
||||
```
|
||||
cargo doc --features docs.rs --open
|
||||
```
|
||||
|
||||
Check out the [examples](examples). To run an example:
|
||||
|
||||
```
|
||||
cargo run --example hello-world
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
See [our contribution document][contribution].
|
||||
|
||||
[contribution]: https://async.rs/contribute
|
||||
|
||||
* [Surf](https://crates.io/crates/surf) — Surf the web. Surf is a friendly **HTTP client** built for casual Rustaceans and veterans alike.
|
||||
|
||||
* [Xactor](https://crates.io/crates/xactor) — Xactor is a rust actors framework based on async-std.
|
||||
|
||||
* [async-graphql](https://crates.io/crates/async-graphql) — A GraphQL server library implemented in rust, with full support for async/await.
|
||||
|
||||
## License
|
||||
|
||||
<sup>
|
||||
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
|
||||
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
|
||||
</sup>
|
||||
Licensed under either of
|
||||
|
||||
<br/>
|
||||
* 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.
|
||||
|
||||
#### Contribution
|
||||
|
||||
<sub>
|
||||
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||
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.
|
||||
</sub>
|
||||
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.
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use async_std::sync::{Arc, Mutex};
|
||||
use async_std::task;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn create(b: &mut Bencher) {
|
||||
b.iter(|| Mutex::new(()));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn contention(b: &mut Bencher) {
|
||||
b.iter(|| task::block_on(run(10, 1000)));
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn no_contention(b: &mut Bencher) {
|
||||
b.iter(|| task::block_on(run(1, 10000)));
|
||||
}
|
||||
|
||||
async fn run(task: usize, iter: usize) {
|
||||
let m = Arc::new(Mutex::new(()));
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
for _ in 0..task {
|
||||
let m = m.clone();
|
||||
tasks.push(task::spawn(async move {
|
||||
for _ in 0..iter {
|
||||
let _ = m.lock().await;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
for t in tasks {
|
||||
t.await;
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#![feature(test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
use async_std::task;
|
||||
use test::Bencher;
|
||||
|
||||
#[bench]
|
||||
fn block_on(b: &mut Bencher) {
|
||||
b.iter(|| task::block_on(async {}));
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
#![feature(test)]
|
||||
#![feature(async_await, test)]
|
||||
|
||||
extern crate test;
|
||||
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
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",
|
||||
]
|
|
@ -1,19 +0,0 @@
|
|||
set -euxo pipefail
|
||||
|
||||
# Based on the Rust-Embedded WG's book CI
|
||||
# https://github.com/rust-embedded/book/blob/master/ci/install.sh
|
||||
|
||||
main() {
|
||||
# Note - this will only accept releases tagged with v0.3.x
|
||||
local tag=$(git ls-remote --tags --refs --exit-code \
|
||||
https://github.com/rust-lang-nursery/mdbook \
|
||||
| cut -d/ -f3 \
|
||||
| grep -E '^v0\.3\.[0-9]+$' \
|
||||
| sort --version-sort \
|
||||
| tail -n1)
|
||||
|
||||
curl -LSfs https://japaric.github.io/trust/install.sh | \
|
||||
sh -s -- --git rust-lang-nursery/mdbook --tag $tag
|
||||
}
|
||||
|
||||
main
|
|
@ -19,9 +19,8 @@
|
|||
- [Clean Shutdown](./tutorial/clean_shutdown.md)
|
||||
- [Handling Disconnection](./tutorial/handling_disconnection.md)
|
||||
- [Implementing a Client](./tutorial/implementing_a_client.md)
|
||||
- [Async Patterns](./patterns.md)
|
||||
- [TODO: Async Patterns](./patterns.md)
|
||||
- [TODO: Collected Small Patterns](./patterns/small-patterns.md)
|
||||
- [Production-Ready Accept Loop](./patterns/accept-loop.md)
|
||||
- [Security practices](./security/index.md)
|
||||
- [Security Disclosures and Policy](./security/policy.md)
|
||||
- [Glossary](./glossary.md)
|
||||
|
|
|
@ -24,7 +24,11 @@ To sum up: Rust gives us the ability to safely abstract over important propertie
|
|||
|
||||
## An easy view of computation
|
||||
|
||||
While computation is a subject to write a whole [book](https://computationbook.com/) about, a very simplified view suffices for us: A sequence of composable operations which can branch based on a decision, run to succession and yield a result or yield an error
|
||||
While computation is a subject to write a whole [book](https://computationbook.com/) about, a very simplified view suffices for us:
|
||||
|
||||
- computation is a sequence of composable operations
|
||||
- they can branch based on a decision
|
||||
- they either run to succession and yield a result, or they can yield an error
|
||||
|
||||
## Deferring computation
|
||||
|
||||
|
@ -46,14 +50,12 @@ Remember the talk about "deferred computation" in the intro? That's all it is. I
|
|||
|
||||
Let's have a look at a simple function, specifically the return value:
|
||||
|
||||
```rust,edition2018
|
||||
# use std::{fs::File, io, io::prelude::*};
|
||||
#
|
||||
fn read_file(path: &str) -> io::Result<String> {
|
||||
let mut file = File::open(path)?;
|
||||
```rust
|
||||
fn read_file(path: &str) -> Result<String, io::Error> {
|
||||
let mut file = File.open(path)?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
Ok(contents)
|
||||
contents
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -62,14 +64,12 @@ Note that this return value talks about the past. The past has a drawback: all d
|
|||
|
||||
But we wanted to abstract over *computation* and let someone else choose how to run it. That's fundamentally incompatible with looking at the results of previous computation all the time. So, let's find a type that *describes* a computation without running it. Let's look at the function again:
|
||||
|
||||
```rust,edition2018
|
||||
# use std::{fs::File, io, io::prelude::*};
|
||||
#
|
||||
fn read_file(path: &str) -> io::Result<String> {
|
||||
let mut file = File::open(path)?;
|
||||
```rust
|
||||
fn read_file(path: &str) -> Result<String, io::Error> {
|
||||
let mut file = File.open(path)?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents)?;
|
||||
Ok(contents)
|
||||
contents
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -79,11 +79,10 @@ This is the moment where we could reach for [threads](https://en.wikipedia.org/w
|
|||
|
||||
What we are searching for is something that represents ongoing work towards a result in the future. Whenever we say "something" in Rust, we almost always mean a trait. Let's start with an incomplete definition of the `Future` trait:
|
||||
|
||||
```rust,edition2018
|
||||
# use std::{pin::Pin, task::{Context, Poll}};
|
||||
#
|
||||
```rust
|
||||
trait Future {
|
||||
type Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>;
|
||||
}
|
||||
```
|
||||
|
@ -106,21 +105,20 @@ Note that calling `poll` again after case 1 happened may result in confusing beh
|
|||
|
||||
While the `Future` trait has existed in Rust for a while, it was inconvenient to build and describe them. For this, Rust now has a special syntax: `async`. The example from above, implemented with `async-std`, would look like this:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# use async_std::{fs::File, io, io::prelude::*};
|
||||
#
|
||||
async fn read_file(path: &str) -> io::Result<String> {
|
||||
let mut file = File::open(path).await?;
|
||||
```rust
|
||||
use async_std::fs::File;
|
||||
|
||||
async fn read_file(path: &str) -> Result<String, io::Error> {
|
||||
let mut file = File.open(path).await?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents).await?;
|
||||
Ok(contents)
|
||||
contents
|
||||
}
|
||||
```
|
||||
|
||||
Amazingly little difference, right? All we did is label the function `async` and insert 2 special commands: `.await`.
|
||||
|
||||
This `async` function sets up a deferred computation. When this function is called, it will produce a `Future<Output = io::Result<String>>` instead of immediately returning a `io::Result<String>`. (Or, more precisely, generate a type for you that implements `Future<Output = io::Result<String>>`.)
|
||||
This `async` function sets up a deferred computation. When this function is called, it will produce a `Future<Output=Result<String, io::Error>>` instead of immediately returning a `Result<String, io::Error>`. (Or, more precisely, generate a type for you that implements `Future<Output=Result<String, io::Error>>`.)
|
||||
|
||||
## What does `.await` do?
|
||||
|
||||
|
@ -132,11 +130,11 @@ When executing 2 or more of these functions at the same time, our runtime system
|
|||
|
||||
## Conclusion
|
||||
|
||||
Working from values, we searched for something that expresses *working towards a value available later*. From there, we talked about the concept of polling.
|
||||
Working from values, we searched for something that expresses *working towards a value available sometime later*. From there, we talked about the concept of polling.
|
||||
|
||||
A `Future` is any data type that does not represent a value, but the ability to *produce a value at some point in the future*. Implementations of this are very varied and detailed depending on use-case, but the interface is simple.
|
||||
|
||||
Next, we will introduce you to `tasks`, which we will use to actually *run* Futures.
|
||||
Next, we will introduce you to `tasks`, which we need to actually *run* Futures.
|
||||
|
||||
[^1]: Two parties reading while it is guaranteed that no one is writing is always safe.
|
||||
|
||||
|
|
|
@ -4,15 +4,15 @@ Now that we know what Futures are, we want to run them!
|
|||
|
||||
In `async-std`, the [`tasks`][tasks] module is responsible for this. The simplest way is using the `block_on` function:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
use async_std::{fs::File, io, prelude::*, task};
|
||||
```rust
|
||||
use async_std::fs::File;
|
||||
use async_std::task;
|
||||
|
||||
async fn read_file(path: &str) -> io::Result<String> {
|
||||
async fn read_file(path: &str) -> Result<String, io::Error> {
|
||||
let mut file = File::open(path).await?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents).await?;
|
||||
Ok(contents)
|
||||
contents
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -31,34 +31,24 @@ fn main() {
|
|||
|
||||
This asks the runtime baked into `async_std` to execute the code that reads a file. Let's go one by one, though, inside to outside.
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# use async_std::{fs::File, io, prelude::*, task};
|
||||
#
|
||||
# async fn read_file(path: &str) -> io::Result<String> {
|
||||
# let mut file = File::open(path).await?;
|
||||
# let mut contents = String::new();
|
||||
# file.read_to_string(&mut contents).await?;
|
||||
# Ok(contents)
|
||||
# }
|
||||
#
|
||||
```rust
|
||||
async {
|
||||
let result = read_file("data.csv").await;
|
||||
match result {
|
||||
Ok(s) => println!("{}", s),
|
||||
Err(e) => println!("Error reading file: {:?}", e)
|
||||
}
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
This is an `async` *block*. Async blocks are necessary to call `async` functions, and will instruct the compiler to include all the relevant instructions to do so. In Rust, all blocks return a value and `async` blocks happen to return a value of the kind `Future`.
|
||||
|
||||
But let's get to the interesting part:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# use async_std::task;
|
||||
task::spawn(async { });
|
||||
```rust
|
||||
|
||||
task::spawn(async { })
|
||||
|
||||
```
|
||||
|
||||
`spawn` takes a `Future` and starts running it on a `Task`. It returns a `JoinHandle`. Futures in Rust are sometimes called *cold* Futures. You need something that starts running them. To run a Future, there may be some additional bookkeeping required, e.g. whether it's running or finished, where it is being placed in memory and what the current state is. This bookkeeping part is abstracted away in a `Task`.
|
||||
|
@ -80,11 +70,9 @@ Tasks in `async_std` are one of the core abstractions. Much like Rust's `thread`
|
|||
|
||||
## Blocking
|
||||
|
||||
`Task`s are assumed to run _concurrently_, potentially by sharing a thread of execution. This means that operations blocking an _operating system thread_, such as `std::thread::sleep` or io function from Rust's `std` library will _stop execution of all tasks sharing this thread_. Other libraries (such as database drivers) have similar behaviour. Note that _blocking the current thread_ is not in and of itself bad behaviour, just something that does not mix well with the concurrent execution model of `async-std`. Essentially, never do this:
|
||||
`Task`s are assumed to run _concurrently_, potentially by sharing a thread of execution. This means that operations blocking an _operating system thread_, such as `std::thread::sleep` or io function from Rust's `std` library will _stop execution of all tasks sharing this thread_. Other libraries (such as database drivers) have similar behaviour. Note that _blocking the current thread_ is not in and by itself bad behaviour, just something that does not mix well with the concurrent execution model of `async-std`. Essentially, never do this:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# use async_std::task;
|
||||
```rust
|
||||
fn main() {
|
||||
task::block_on(async {
|
||||
// this is std::fs, which blocks
|
||||
|
@ -103,9 +91,7 @@ In case of `panic`, behaviour differs depending on whether there's a reasonable
|
|||
|
||||
In practice, that means that `block_on` propagates panics to the blocking component:
|
||||
|
||||
```rust,edition2018,should_panic
|
||||
# extern crate async_std;
|
||||
# use async_std::task;
|
||||
```rust
|
||||
fn main() {
|
||||
task::block_on(async {
|
||||
panic!("test");
|
||||
|
@ -120,10 +106,7 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
|
|||
|
||||
While panicing a spawned task will abort:
|
||||
|
||||
```rust,edition2018,should_panic
|
||||
# extern crate async_std;
|
||||
# use async_std::task;
|
||||
# use std::time::Duration;
|
||||
```rust
|
||||
task::spawn(async {
|
||||
panic!("test");
|
||||
});
|
||||
|
|
|
@ -4,4 +4,4 @@
|
|||
|
||||
`async-std` provides an interface to all important primitives: filesystem operations, network operations and concurrency basics like timers. It also exposes a `task` in a model similar to the `thread` module found in the Rust standard lib. But it does not only include I/O primitives, but also `async/await` compatible versions of primitives like `Mutex`.
|
||||
|
||||
[organization]: https://github.com/async-rs
|
||||
[organization]: https://github.com/async-rs/async-std
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
In short: we are versioning our software as `MAJOR.MINOR.PATCH`. We increase the:
|
||||
|
||||
* MAJOR version when there are incompatible API changes,
|
||||
* MINOR version when we introduce functionality in a backwards-compatible manner
|
||||
* MINOR version when we introducece functionality in a backwards-compatible manner
|
||||
* PATCH version when we make backwards-compatible bug fixes
|
||||
|
||||
We will provide migration documentation between major versions.
|
||||
|
@ -31,7 +31,7 @@ In general, this crate will be conservative with respect to the minimum supporte
|
|||
|
||||
## Security fixes
|
||||
|
||||
Security fixes will be applied to _all_ minor branches of this library in all _supported_ major revisions. This policy might change in the future, in which case we give a notice at least _3 months_ ahead.
|
||||
Security fixes will be applied to _all_ minor branches of this library in all _supported_ major revisions. This policy might change in the future, in which case we give at least _3 month_ of ahead notice.
|
||||
|
||||
## Credits
|
||||
|
||||
|
|
|
@ -4,13 +4,13 @@ Rust has two kinds of types commonly referred to as `Future`:
|
|||
|
||||
|
||||
- the first is `std::future::Future` from Rust’s [standard library](https://doc.rust-lang.org/std/future/trait.Future.html).
|
||||
- the second is `futures::future::Future` from the [futures-rs crate](https://docs.rs/futures/0.3/futures/prelude/trait.Future.html).
|
||||
- the second is `futures::future::Future` from the [futures-rs crate](https://docs.rs/futures-preview/0.3.0-alpha.17/futures/prelude/trait.Future.html), currently released as `futures-preview`.
|
||||
|
||||
The future defined in the [futures-rs](https://docs.rs/futures/0.3/futures/prelude/trait.Future.html) crate was the original implementation of the type. To enable the `async/await` syntax, the core Future trait was moved into Rust’s standard library and became `std::future::Future`. In some sense, the `std::future::Future` can be seen as a minimal subset of `futures::future::Future`.
|
||||
The future defined in the [futures-rs](https://docs.rs/futures-preview/0.3.0-alpha.17/futures/prelude/trait.Future.html) crate was the original implementation of the type. To enable the `async/await` syntax, the core Future trait was moved into Rust’s standard library and became `std::future::Future`. In some sense, the `std::future::Future` can be seen as a minimal subset of `futures::future::Future`.
|
||||
|
||||
It is critical to understand the difference between `std::future::Future` and `futures::future::Future`, and the approach that `async-std` takes towards them. In itself, `std::future::Future` is not something you want to interact with as a user—except by calling `.await` on it. The inner workings of `std::future::Future` are mostly of interest to people implementing `Future`. Make no mistake—this is very useful! Most of the functionality that used to be defined on `Future` itself has been moved to an extension trait called [`FuturesExt`](https://docs.rs/futures/0.3/futures/future/trait.FutureExt.html). From this information, you might be able to infer that the `futures` library serves as an extension to the core Rust async features.
|
||||
It is critical to understand the difference between `std::future::Future` and `futures::future::Future`, and the approach that `async-std` takes towards them. In itself, `std::future::Future` is not something you want to interact with as a user—except by calling `.await` on it. The inner workings of `std::future::Future` are mostly of interest to people implementing `Future`. Make no mistake—this is very useful! Most of the functionality that used to be defined on `Future` itself has been moved to an extension trait called [`FuturesExt`](https://docs.rs/futures-preview/0.3.0-alpha.17/futures/future/trait.FutureExt.html). From this information, you might be able to infer that the `futures` library serves as an extension to the core Rust async features.
|
||||
|
||||
In the same tradition as `futures`, `async-std` re-exports the core `std::future::Future` type. You can actively opt into the extensions provided by the `futures` crate by adding it to your `Cargo.toml` and importing `FuturesExt`.
|
||||
In the same tradition as `futures`, `async-std` re-exports the core `std::future::Future` type. You can actively opt into the extensions provided by the `futures-preview` crate by adding it to your `Cargo.toml` and importing `FuturesExt`.
|
||||
|
||||
## Interfaces and Stability
|
||||
|
||||
|
|
|
@ -1,266 +0,0 @@
|
|||
# Production-Ready Accept Loop
|
||||
|
||||
A production-ready accept loop needs the following things:
|
||||
1. Handling errors
|
||||
2. Limiting the number of simultanteous connections to avoid deny-of-service
|
||||
(DoS) attacks
|
||||
|
||||
|
||||
## Handling errors
|
||||
|
||||
There are two kinds of errors in an accept loop:
|
||||
1. Per-connection errors. The system uses them to notify that there was a
|
||||
connection in the queue and it's dropped by the peer. Subsequent connections
|
||||
can be already queued so next connection must be accepted immediately.
|
||||
2. Resource shortages. When these are encountered it doesn't make sense to
|
||||
accept the next socket immediately. But the listener stays active, so you server
|
||||
should try to accept socket later.
|
||||
|
||||
Here is the example of a per-connection error (printed in normal and debug mode):
|
||||
```
|
||||
Error: Connection reset by peer (os error 104)
|
||||
Error: Os { code: 104, kind: ConnectionReset, message: "Connection reset by peer" }
|
||||
```
|
||||
|
||||
And the following is the most common example of a resource shortage error:
|
||||
```
|
||||
Error: Too many open files (os error 24)
|
||||
Error: Os { code: 24, kind: Other, message: "Too many open files" }
|
||||
```
|
||||
|
||||
### Testing Application
|
||||
|
||||
To test your application for these errors try the following (this works
|
||||
on unixes only).
|
||||
|
||||
Lower limits and start the application:
|
||||
```
|
||||
$ ulimit -n 100
|
||||
$ cargo run --example your_app
|
||||
Compiling your_app v0.1.0 (/work)
|
||||
Finished dev [unoptimized + debuginfo] target(s) in 5.47s
|
||||
Running `target/debug/examples/your_app`
|
||||
Server is listening on: http://127.0.0.1:1234
|
||||
```
|
||||
Then in another console run the [`wrk`] benchmark tool:
|
||||
```
|
||||
$ wrk -c 1000 http://127.0.0.1:1234
|
||||
Running 10s test @ http://localhost:8080/
|
||||
2 threads and 1000 connections
|
||||
$ telnet localhost 1234
|
||||
Trying ::1...
|
||||
Connected to localhost.
|
||||
```
|
||||
|
||||
Important is to check the following things:
|
||||
|
||||
1. The application doesn't crash on error (but may log errors, see below)
|
||||
2. It's possible to connect to the application again once load is stopped
|
||||
(few seconds after `wrk`). This is what `telnet` does in example above,
|
||||
make sure it prints `Connected to <hostname>`.
|
||||
3. The `Too many open files` error is logged in the appropriate log. This
|
||||
requires to set "maximum number of simultaneous connections" parameter (see
|
||||
below) of your application to a value greater then `100` for this example.
|
||||
4. Check CPU usage of the app while doing a test. It should not occupy 100%
|
||||
of a single CPU core (it's unlikely that you can exhaust CPU by 1000
|
||||
connections in Rust, so this means error handling is not right).
|
||||
|
||||
#### Testing non-HTTP applications
|
||||
|
||||
If it's possible, use the appropriate benchmark tool and set the appropriate
|
||||
number of connections. For example `redis-benchmark` has a `-c` parameter for
|
||||
that, if you implement redis protocol.
|
||||
|
||||
Alternatively, can still use `wrk`, just make sure that connection is not
|
||||
immediately closed. If it is, put a temporary timeout before handing
|
||||
the connection to the protocol handler, like this:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# use std::time::Duration;
|
||||
# use async_std::{
|
||||
# net::{TcpListener, ToSocketAddrs},
|
||||
# prelude::*,
|
||||
# };
|
||||
#
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
#
|
||||
#async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
# let listener = TcpListener::bind(addr).await?;
|
||||
# let mut incoming = listener.incoming();
|
||||
while let Some(stream) = incoming.next().await {
|
||||
task::spawn(async {
|
||||
task::sleep(Duration::from_secs(10)).await; // 1
|
||||
connection_loop(stream).await;
|
||||
});
|
||||
}
|
||||
# Ok(())
|
||||
# }
|
||||
```
|
||||
|
||||
1. Make sure the sleep coroutine is inside the spawned task, not in the loop.
|
||||
|
||||
[`wrk`]: https://github.com/wg/wrk
|
||||
|
||||
|
||||
### Handling Errors Manually
|
||||
|
||||
Here is how basic accept loop could look like:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# use std::time::Duration;
|
||||
# use async_std::{
|
||||
# net::{TcpListener, ToSocketAddrs},
|
||||
# prelude::*,
|
||||
# };
|
||||
#
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
#
|
||||
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
let mut incoming = listener.incoming();
|
||||
while let Some(result) = incoming.next().await {
|
||||
let stream = match stream {
|
||||
Err(ref e) if is_connection_error(e) => continue, // 1
|
||||
Err(e) => {
|
||||
eprintln!("Error: {}. Pausing for 500ms."); // 3
|
||||
task::sleep(Duration::from_millis(500)).await; // 2
|
||||
continue;
|
||||
}
|
||||
Ok(s) => s,
|
||||
};
|
||||
// body
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
1. Ignore per-connection errors.
|
||||
2. Sleep and continue on resource shortage.
|
||||
3. It's important to log the message, because these errors commonly mean the
|
||||
misconfiguration of the system and are helpful for operations people running
|
||||
the application.
|
||||
|
||||
Be sure to [test your application](#testing-application).
|
||||
|
||||
|
||||
### External Crates
|
||||
|
||||
The crate [`async-listen`] has a helper to achieve this task:
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# extern crate async_listen;
|
||||
# use std::time::Duration;
|
||||
# use async_std::{
|
||||
# net::{TcpListener, ToSocketAddrs},
|
||||
# prelude::*,
|
||||
# };
|
||||
#
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
#
|
||||
use async_listen::{ListenExt, error_hint};
|
||||
|
||||
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
let mut incoming = listener
|
||||
.incoming()
|
||||
.log_warnings(log_accept_error) // 1
|
||||
.handle_errors(Duration::from_millis(500));
|
||||
while let Some(socket) = incoming.next().await { // 2
|
||||
// body
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn log_accept_error(e: &io::Error) {
|
||||
eprintln!("Error: {}. Listener paused for 0.5s. {}", e, error_hint(e)) // 3
|
||||
}
|
||||
```
|
||||
|
||||
1. Logs resource shortages (`async-listen` calls them warnings). If you use
|
||||
`log` crate or any other in your app this should go to the log.
|
||||
2. Stream yields sockets without `Result` wrapper after `handle_errors` because
|
||||
all errors are already handled.
|
||||
3. Together with the error we print a hint, which explains some errors for end
|
||||
users. For example, it recommends increasing open file limit and gives
|
||||
a link.
|
||||
|
||||
[`async-listen`]: https://crates.io/crates/async-listen/
|
||||
|
||||
Be sure to [test your application](#testing-application).
|
||||
|
||||
|
||||
## Connections Limit
|
||||
|
||||
Even if you've applied everything described in
|
||||
[Handling Errors](#handling-errors) section, there is still a problem.
|
||||
|
||||
Let's imagine you have a server that needs to open a file to process
|
||||
client request. At some point, you might encounter the following situation:
|
||||
|
||||
1. There are as many client connection as max file descriptors allowed for
|
||||
the application.
|
||||
2. Listener gets `Too many open files` error so it sleeps.
|
||||
3. Some client sends a request via the previously open connection.
|
||||
4. Opening a file to serve request fails, because of the same
|
||||
`Too many open files` error, until some other client drops a connection.
|
||||
|
||||
There are many more possible situations, this is just a small illustation that
|
||||
limiting number of connections is very useful. Generally, it's one of the ways
|
||||
to control resources used by a server and avoiding some kinds of deny of
|
||||
service (DoS) attacks.
|
||||
|
||||
### `async-listen` crate
|
||||
|
||||
Limiting maximum number of simultaneous connections with [`async-listen`]
|
||||
looks like the following:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# extern crate async_listen;
|
||||
# use std::time::Duration;
|
||||
# use async_std::{
|
||||
# net::{TcpListener, TcpStream, ToSocketAddrs},
|
||||
# prelude::*,
|
||||
# };
|
||||
#
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
#
|
||||
use async_listen::{ListenExt, Token, error_hint};
|
||||
|
||||
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
let mut incoming = listener
|
||||
.incoming()
|
||||
.log_warnings(log_accept_error)
|
||||
.handle_errors(Duration::from_millis(500)) // 1
|
||||
.backpressure(100);
|
||||
while let Some((token, socket)) = incoming.next().await { // 2
|
||||
task::spawn(async move {
|
||||
connection_loop(&token, stream).await; // 3
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn connection_loop(_token: &Token, stream: TcpStream) { // 4
|
||||
// ...
|
||||
}
|
||||
# fn log_accept_error(e: &io::Error) {
|
||||
# eprintln!("Error: {}. Listener paused for 0.5s. {}", e, error_hint(e));
|
||||
# }
|
||||
```
|
||||
|
||||
1. We need to handle errors first, because [`backpressure`] helper expects
|
||||
stream of `TcpStream` rather than `Result`.
|
||||
2. The token yielded by a new stream is what is counted by backpressure helper.
|
||||
I.e. if you drop a token, new connection can be established.
|
||||
3. We give the connection loop a reference to token to bind token's lifetime to
|
||||
the lifetime of the connection.
|
||||
4. The token itsellf in the function can be ignored, hence `_token`
|
||||
|
||||
[`backpressure`]: https://docs.rs/async-listen/0.1.2/async_listen/trait.ListenExt.html#method.backpressure
|
||||
|
||||
Be sure to [test this behavior](#testing-application).
|
|
@ -6,11 +6,11 @@ A collection of small, useful patterns.
|
|||
|
||||
`async-std` doesn't provide a `split()` method on `io` handles. Instead, splitting a stream into a read and write half can be done like this:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
use async_std::{io, net::TcpStream};
|
||||
async fn echo(stream: TcpStream) {
|
||||
```rust
|
||||
use async_std::io;
|
||||
|
||||
async fn echo(stream: io::TcpStream) {
|
||||
let (reader, writer) = &mut (&stream, &stream);
|
||||
io::copy(reader, writer).await;
|
||||
io::copy(reader, writer).await?;
|
||||
}
|
||||
```
|
||||
```
|
|
@ -32,7 +32,7 @@ This policy is adapted from the [Rust project](https://www.rust-lang.org/policie
|
|||
|
||||
## PGP Key
|
||||
|
||||
```text
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQENBF1Wu/ABCADJaGt4HwSlqKB9BGHWYKZj/6mTMbmc29vsEOcCSQKo6myCf9zc
|
||||
|
|
|
@ -2,40 +2,38 @@
|
|||
|
||||
Let's implement the scaffold of the server: a loop that binds a TCP socket to an address and starts accepting connections.
|
||||
|
||||
|
||||
First of all, let's add required import boilerplate:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
```rust
|
||||
#![feature(async_await)]
|
||||
|
||||
use std::net::ToSocketAddrs; // 1
|
||||
|
||||
use async_std::{
|
||||
prelude::*, // 1
|
||||
task, // 2
|
||||
net::{TcpListener, ToSocketAddrs}, // 3
|
||||
prelude::*, // 2
|
||||
task, // 3
|
||||
net::TcpListener, // 4
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; // 4
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; // 5
|
||||
```
|
||||
|
||||
1. `prelude` re-exports some traits required to work with futures and streams.
|
||||
2. The `task` module roughly corresponds to the `std::thread` module, but tasks are much lighter weight.
|
||||
1. `async_std` uses `std` types where appropriate.
|
||||
We'll need `ToSocketAddrs` to specify address to listen on.
|
||||
2. `prelude` re-exports some traits required to work with futures and streams.
|
||||
3. The `task` module roughly corresponds to the `std::thread` module, but tasks are much lighter weight.
|
||||
A single thread can run many tasks.
|
||||
3. For the socket type, we use `TcpListener` from `async_std`, which is just like `std::net::TcpListener`, but is non-blocking and uses `async` API.
|
||||
4. We will skip implementing comprehensive error handling in this example.
|
||||
4. For the socket type, we use `TcpListener` from `async_std`, which is just like `std::net::TcpListener`, but is non-blocking and uses `async` API.
|
||||
5. We will skip implementing comprehensive error handling in this example.
|
||||
To propagate the errors, we will use a boxed error trait object.
|
||||
Do you know that there's `From<&'_ str> for Box<dyn Error>` implementation in stdlib, which allows you to use strings with `?` operator?
|
||||
|
||||
|
||||
Now we can write the server's accept loop:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# use async_std::{
|
||||
# net::{TcpListener, ToSocketAddrs},
|
||||
# prelude::*,
|
||||
# };
|
||||
#
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
#
|
||||
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { // 1
|
||||
|
||||
```rust
|
||||
async fn server(addr: impl ToSocketAddrs) -> Result<()> { // 1
|
||||
let listener = TcpListener::bind(addr).await?; // 2
|
||||
let mut incoming = listener.incoming();
|
||||
while let Some(stream) = incoming.next().await { // 3
|
||||
|
@ -45,46 +43,28 @@ async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { // 1
|
|||
}
|
||||
```
|
||||
|
||||
1. We mark the `accept_loop` function as `async`, which allows us to use `.await` syntax inside.
|
||||
1. We mark the `server` function as `async`, which allows us to use `.await` syntax inside.
|
||||
2. `TcpListener::bind` call returns a future, which we `.await` to extract the `Result`, and then `?` to get a `TcpListener`.
|
||||
Note how `.await` and `?` work nicely together.
|
||||
This is exactly how `std::net::TcpListener` works, but with `.await` added.
|
||||
Mirroring API of `std` is an explicit design goal of `async_std`.
|
||||
3. Here, we would like to iterate incoming sockets, just how one would do in `std`:
|
||||
|
||||
```rust,edition2018,should_panic
|
||||
let listener: std::net::TcpListener = unimplemented!();
|
||||
for stream in listener.incoming() {
|
||||
}
|
||||
```
|
||||
```rust
|
||||
let listener: std::net::TcpListener = unimplemented!();
|
||||
for stream in listener.incoming() {
|
||||
|
||||
Unfortunately this doesn't quite work with `async` yet, because there's no support for `async` for-loops in the language yet.
|
||||
For this reason we have to implement the loop manually, by using `while let Some(item) = iter.next().await` pattern.
|
||||
}
|
||||
```
|
||||
|
||||
Unfortunately this doesn't quite work with `async` yet, because there's no support for `async` for-loops in the language yet.
|
||||
For this reason we have to implement the loop manually, by using `while let Some(item) = iter.next().await` pattern.
|
||||
|
||||
Finally, let's add main:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# use async_std::{
|
||||
# net::{TcpListener, ToSocketAddrs},
|
||||
# prelude::*,
|
||||
# task,
|
||||
# };
|
||||
#
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
#
|
||||
# async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> { // 1
|
||||
# let listener = TcpListener::bind(addr).await?; // 2
|
||||
# let mut incoming = listener.incoming();
|
||||
# while let Some(stream) = incoming.next().await { // 3
|
||||
# // TODO
|
||||
# }
|
||||
# Ok(())
|
||||
# }
|
||||
#
|
||||
// main
|
||||
fn run() -> Result<()> {
|
||||
let fut = accept_loop("127.0.0.1:8080");
|
||||
```rust
|
||||
fn main() -> Result<()> {
|
||||
let fut = server("127.0.0.1:8080");
|
||||
task::block_on(fut)
|
||||
}
|
||||
```
|
||||
|
@ -92,5 +72,5 @@ fn run() -> Result<()> {
|
|||
The crucial thing to realise that is in Rust, unlike other languages, calling an async function does **not** run any code.
|
||||
Async functions only construct futures, which are inert state machines.
|
||||
To start stepping through the future state-machine in an async function, you should use `.await`.
|
||||
In a non-async function, a way to execute a future is to hand it to the executor.
|
||||
In a non-async function, a way to execute a future is to handle it to the executor.
|
||||
In this case, we use `task::block_on` to execute a future on the current thread and block until it's done.
|
||||
|
|
|
@ -1,58 +1,53 @@
|
|||
|
||||
## All Together
|
||||
|
||||
At this point, we only need to start the broker to get a fully-functioning (in the happy case!) chat:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# extern crate futures;
|
||||
```rust
|
||||
#![feature(async_await)]
|
||||
|
||||
use std::{
|
||||
net::ToSocketAddrs,
|
||||
sync::Arc,
|
||||
collections::hash_map::{HashMap, Entry},
|
||||
};
|
||||
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
SinkExt,
|
||||
};
|
||||
|
||||
use async_std::{
|
||||
io::BufReader,
|
||||
net::{TcpListener, TcpStream, ToSocketAddrs},
|
||||
prelude::*,
|
||||
task,
|
||||
};
|
||||
use futures::channel::mpsc;
|
||||
use futures::sink::SinkExt;
|
||||
use std::{
|
||||
collections::hash_map::{HashMap, Entry},
|
||||
sync::Arc,
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
type Sender<T> = mpsc::UnboundedSender<T>;
|
||||
type Receiver<T> = mpsc::UnboundedReceiver<T>;
|
||||
|
||||
// main
|
||||
fn run() -> Result<()> {
|
||||
task::block_on(accept_loop("127.0.0.1:8080"))
|
||||
|
||||
fn main() -> Result<()> {
|
||||
task::block_on(server("127.0.0.1:8080"))
|
||||
}
|
||||
|
||||
fn spawn_and_log_error<F>(fut: F) -> task::JoinHandle<()>
|
||||
where
|
||||
F: Future<Output = Result<()>> + Send + 'static,
|
||||
{
|
||||
task::spawn(async move {
|
||||
if let Err(e) = fut.await {
|
||||
eprintln!("{}", e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
async fn server(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
|
||||
let (broker_sender, broker_receiver) = mpsc::unbounded(); // 1
|
||||
let _broker_handle = task::spawn(broker_loop(broker_receiver));
|
||||
let _broker_handle = task::spawn(broker(broker_receiver));
|
||||
let mut incoming = listener.incoming();
|
||||
while let Some(stream) = incoming.next().await {
|
||||
let stream = stream?;
|
||||
println!("Accepting from: {}", stream.peer_addr()?);
|
||||
spawn_and_log_error(connection_loop(broker_sender.clone(), stream));
|
||||
spawn_and_log_error(client(broker_sender.clone(), stream));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn connection_loop(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
|
||||
async fn client(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
|
||||
let stream = Arc::new(stream); // 2
|
||||
let reader = BufReader::new(&*stream);
|
||||
let mut lines = reader.lines();
|
||||
|
@ -71,7 +66,7 @@ async fn connection_loop(mut broker: Sender<Event>, stream: TcpStream) -> Result
|
|||
Some(idx) => (&line[..idx], line[idx + 1 ..].trim()),
|
||||
};
|
||||
let dest: Vec<String> = dest.split(',').map(|name| name.trim().to_string()).collect();
|
||||
let msg: String = msg.to_string();
|
||||
let msg: String = msg.trim().to_string();
|
||||
|
||||
broker.send(Event::Message { // 4
|
||||
from: name.clone(),
|
||||
|
@ -82,7 +77,7 @@ async fn connection_loop(mut broker: Sender<Event>, stream: TcpStream) -> Result
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn connection_writer_loop(
|
||||
async fn client_writer(
|
||||
mut messages: Receiver<String>,
|
||||
stream: Arc<TcpStream>,
|
||||
) -> Result<()> {
|
||||
|
@ -106,7 +101,7 @@ enum Event {
|
|||
},
|
||||
}
|
||||
|
||||
async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
|
||||
async fn broker(mut events: Receiver<Event>) -> Result<()> {
|
||||
let mut peers: HashMap<String, Sender<String>> = HashMap::new();
|
||||
|
||||
while let Some(event) = events.next().await {
|
||||
|
@ -114,8 +109,7 @@ async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
|
|||
Event::Message { from, to, msg } => {
|
||||
for addr in to {
|
||||
if let Some(peer) = peers.get_mut(&addr) {
|
||||
let msg = format!("from {}: {}\n", from, msg);
|
||||
peer.send(msg).await?
|
||||
peer.send(format!("from {}: {}\n", from, msg)).await?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +119,7 @@ async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
|
|||
Entry::Vacant(entry) => {
|
||||
let (client_sender, client_receiver) = mpsc::unbounded();
|
||||
entry.insert(client_sender); // 4
|
||||
spawn_and_log_error(connection_writer_loop(client_receiver, stream)); // 5
|
||||
spawn_and_log_error(client_writer(client_receiver, stream)); // 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -135,8 +129,8 @@ async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
|
|||
}
|
||||
```
|
||||
|
||||
1. Inside the `accept_loop`, we create the broker's channel and `task`.
|
||||
2. Inside `connection_loop`, we need to wrap `TcpStream` into an `Arc`, to be able to share it with the `connection_writer_loop`.
|
||||
1. Inside the `server`, we create the broker's channel and `task`.
|
||||
2. Inside `client`, we need to wrap `TcpStream` into an `Arc`, to be able to share it with the `client_writer`.
|
||||
3. On login, we notify the broker.
|
||||
Note that we `.unwrap` on send: broker should outlive all the clients and if that's not the case the broker probably panicked, so we can escalate the panic as well.
|
||||
4. Similarly, we forward parsed messages to the broker, assuming that it is alive.
|
||||
|
|
|
@ -20,204 +20,37 @@ In `a-chat`, we already have an unidirectional flow of messages: `reader -> brok
|
|||
However, we never wait for broker and writers, which might cause some messages to get dropped.
|
||||
Let's add waiting to the server:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# extern crate futures;
|
||||
# use async_std::{
|
||||
# io::{self, BufReader},
|
||||
# net::{TcpListener, TcpStream, ToSocketAddrs},
|
||||
# prelude::*,
|
||||
# task,
|
||||
# };
|
||||
# use futures::channel::mpsc;
|
||||
# use futures::sink::SinkExt;
|
||||
# use std::{
|
||||
# collections::hash_map::{HashMap, Entry},
|
||||
# sync::Arc,
|
||||
# };
|
||||
#
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
# type Sender<T> = mpsc::UnboundedSender<T>;
|
||||
# type Receiver<T> = mpsc::UnboundedReceiver<T>;
|
||||
#
|
||||
# fn spawn_and_log_error<F>(fut: F) -> task::JoinHandle<()>
|
||||
# where
|
||||
# F: Future<Output = Result<()>> + Send + 'static,
|
||||
# {
|
||||
# task::spawn(async move {
|
||||
# if let Err(e) = fut.await {
|
||||
# eprintln!("{}", e)
|
||||
# }
|
||||
# })
|
||||
# }
|
||||
#
|
||||
#
|
||||
# async fn connection_loop(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
|
||||
# let stream = Arc::new(stream); // 2
|
||||
# let reader = BufReader::new(&*stream);
|
||||
# let mut lines = reader.lines();
|
||||
#
|
||||
# let name = match lines.next().await {
|
||||
# None => Err("peer disconnected immediately")?,
|
||||
# Some(line) => line?,
|
||||
# };
|
||||
# broker.send(Event::NewPeer { name: name.clone(), stream: Arc::clone(&stream) }).await // 3
|
||||
# .unwrap();
|
||||
#
|
||||
# while let Some(line) = lines.next().await {
|
||||
# let line = line?;
|
||||
# let (dest, msg) = match line.find(':') {
|
||||
# None => continue,
|
||||
# Some(idx) => (&line[..idx], line[idx + 1 ..].trim()),
|
||||
# };
|
||||
# let dest: Vec<String> = dest.split(',').map(|name| name.trim().to_string()).collect();
|
||||
# let msg: String = msg.trim().to_string();
|
||||
#
|
||||
# broker.send(Event::Message { // 4
|
||||
# from: name.clone(),
|
||||
# to: dest,
|
||||
# msg,
|
||||
# }).await.unwrap();
|
||||
# }
|
||||
# Ok(())
|
||||
# }
|
||||
#
|
||||
# async fn connection_writer_loop(
|
||||
# mut messages: Receiver<String>,
|
||||
# stream: Arc<TcpStream>,
|
||||
# ) -> Result<()> {
|
||||
# let mut stream = &*stream;
|
||||
# while let Some(msg) = messages.next().await {
|
||||
# stream.write_all(msg.as_bytes()).await?;
|
||||
# }
|
||||
# Ok(())
|
||||
# }
|
||||
#
|
||||
# #[derive(Debug)]
|
||||
# enum Event {
|
||||
# NewPeer {
|
||||
# name: String,
|
||||
# stream: Arc<TcpStream>,
|
||||
# },
|
||||
# Message {
|
||||
# from: String,
|
||||
# to: Vec<String>,
|
||||
# msg: String,
|
||||
# },
|
||||
# }
|
||||
#
|
||||
# async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
|
||||
# let mut peers: HashMap<String, Sender<String>> = HashMap::new();
|
||||
#
|
||||
# while let Some(event) = events.next().await {
|
||||
# match event {
|
||||
# Event::Message { from, to, msg } => {
|
||||
# for addr in to {
|
||||
# if let Some(peer) = peers.get_mut(&addr) {
|
||||
# let msg = format!("from {}: {}\n", from, msg);
|
||||
# peer.send(msg).await?
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# Event::NewPeer { name, stream} => {
|
||||
# match peers.entry(name) {
|
||||
# Entry::Occupied(..) => (),
|
||||
# Entry::Vacant(entry) => {
|
||||
# let (client_sender, client_receiver) = mpsc::unbounded();
|
||||
# entry.insert(client_sender); // 4
|
||||
# spawn_and_log_error(connection_writer_loop(client_receiver, stream)); // 5
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# Ok(())
|
||||
# }
|
||||
#
|
||||
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
```rust
|
||||
async fn server(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
|
||||
let (broker_sender, broker_receiver) = mpsc::unbounded();
|
||||
let broker_handle = task::spawn(broker_loop(broker_receiver));
|
||||
let broker = task::spawn(broker(broker_receiver));
|
||||
let mut incoming = listener.incoming();
|
||||
while let Some(stream) = incoming.next().await {
|
||||
let stream = stream?;
|
||||
println!("Accepting from: {}", stream.peer_addr()?);
|
||||
spawn_and_log_error(connection_loop(broker_sender.clone(), stream));
|
||||
spawn_and_log_error(client(broker_sender.clone(), stream));
|
||||
}
|
||||
drop(broker_sender); // 1
|
||||
broker_handle.await?; // 5
|
||||
broker.await?; // 5
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
And to the broker:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# extern crate futures;
|
||||
# use async_std::{
|
||||
# io::{self, BufReader},
|
||||
# net::{TcpListener, TcpStream, ToSocketAddrs},
|
||||
# prelude::*,
|
||||
# task,
|
||||
# };
|
||||
# use futures::channel::mpsc;
|
||||
# use futures::sink::SinkExt;
|
||||
# use std::{
|
||||
# collections::hash_map::{HashMap, Entry},
|
||||
# sync::Arc,
|
||||
# };
|
||||
#
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
# type Sender<T> = mpsc::UnboundedSender<T>;
|
||||
# type Receiver<T> = mpsc::UnboundedReceiver<T>;
|
||||
#
|
||||
# async fn connection_writer_loop(
|
||||
# mut messages: Receiver<String>,
|
||||
# stream: Arc<TcpStream>,
|
||||
# ) -> Result<()> {
|
||||
# let mut stream = &*stream;
|
||||
# while let Some(msg) = messages.next().await {
|
||||
# stream.write_all(msg.as_bytes()).await?;
|
||||
# }
|
||||
# Ok(())
|
||||
# }
|
||||
#
|
||||
# fn spawn_and_log_error<F>(fut: F) -> task::JoinHandle<()>
|
||||
# where
|
||||
# F: Future<Output = Result<()>> + Send + 'static,
|
||||
# {
|
||||
# task::spawn(async move {
|
||||
# if let Err(e) = fut.await {
|
||||
# eprintln!("{}", e)
|
||||
# }
|
||||
# })
|
||||
# }
|
||||
#
|
||||
# #[derive(Debug)]
|
||||
# enum Event {
|
||||
# NewPeer {
|
||||
# name: String,
|
||||
# stream: Arc<TcpStream>,
|
||||
# },
|
||||
# Message {
|
||||
# from: String,
|
||||
# to: Vec<String>,
|
||||
# msg: String,
|
||||
# },
|
||||
# }
|
||||
#
|
||||
async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
|
||||
```rust
|
||||
async fn broker(mut events: Receiver<Event>) -> Result<()> {
|
||||
let mut writers = Vec::new();
|
||||
let mut peers: HashMap<String, Sender<String>> = HashMap::new();
|
||||
|
||||
while let Some(event) = events.next().await { // 2
|
||||
match event {
|
||||
Event::Message { from, to, msg } => {
|
||||
for addr in to {
|
||||
if let Some(peer) = peers.get_mut(&addr) {
|
||||
let msg = format!("from {}: {}\n", from, msg);
|
||||
peer.send(msg).await?
|
||||
peer.send(format!("from {}: {}\n", from, msg)).await?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -227,7 +60,7 @@ async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
|
|||
Entry::Vacant(entry) => {
|
||||
let (client_sender, client_receiver) = mpsc::unbounded();
|
||||
entry.insert(client_sender);
|
||||
let handle = spawn_and_log_error(connection_writer_loop(client_receiver, stream));
|
||||
let handle = spawn_and_log_error(client_writer(client_receiver, stream));
|
||||
writers.push(handle); // 4
|
||||
}
|
||||
}
|
||||
|
@ -236,7 +69,7 @@ async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
|
|||
}
|
||||
drop(peers); // 3
|
||||
for writer in writers { // 4
|
||||
writer.await;
|
||||
writer.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,55 +1,16 @@
|
|||
|
||||
## Connecting Readers and Writers
|
||||
|
||||
So how do we make sure that messages read in `connection_loop` flow into the relevant `connection_writer_loop`?
|
||||
We should somehow maintain a `peers: HashMap<String, Sender<String>>` map which allows a client to find destination channels.
|
||||
So how do we make sure that messages read in `client` flow into the relevant `client_writer`?
|
||||
We should somehow maintain an `peers: HashMap<String, Sender<String>>` map which allows a client to find destination channels.
|
||||
However, this map would be a bit of shared mutable state, so we'll have to wrap an `RwLock` over it and answer tough questions of what should happen if the client joins at the same moment as it receives a message.
|
||||
|
||||
One trick to make reasoning about state simpler comes from the actor model.
|
||||
We can create a dedicated broker task which owns the `peers` map and communicates with other tasks using channels.
|
||||
By hiding `peers` inside such an "actor" task, we remove the need for mutexes and also make the serialization point explicit.
|
||||
We can create a dedicated broker tasks which owns the `peers` map and communicates with other tasks by channels.
|
||||
By hiding `peers` inside such an "actor" task, we remove the need for mutxes and also make serialization point explicit.
|
||||
The order of events "Bob sends message to Alice" and "Alice joins" is determined by the order of the corresponding events in the broker's event queue.
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# extern crate futures;
|
||||
# use async_std::{
|
||||
# net::TcpStream,
|
||||
# prelude::*,
|
||||
# task,
|
||||
# };
|
||||
# use futures::channel::mpsc;
|
||||
# use futures::sink::SinkExt;
|
||||
# use std::sync::Arc;
|
||||
#
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
# type Sender<T> = mpsc::UnboundedSender<T>;
|
||||
# type Receiver<T> = mpsc::UnboundedReceiver<T>;
|
||||
#
|
||||
# async fn connection_writer_loop(
|
||||
# mut messages: Receiver<String>,
|
||||
# stream: Arc<TcpStream>,
|
||||
# ) -> Result<()> {
|
||||
# let mut stream = &*stream;
|
||||
# while let Some(msg) = messages.next().await {
|
||||
# stream.write_all(msg.as_bytes()).await?;
|
||||
# }
|
||||
# Ok(())
|
||||
# }
|
||||
#
|
||||
# fn spawn_and_log_error<F>(fut: F) -> task::JoinHandle<()>
|
||||
# where
|
||||
# F: Future<Output = Result<()>> + Send + 'static,
|
||||
# {
|
||||
# task::spawn(async move {
|
||||
# if let Err(e) = fut.await {
|
||||
# eprintln!("{}", e)
|
||||
# }
|
||||
# })
|
||||
# }
|
||||
#
|
||||
use std::collections::hash_map::{Entry, HashMap};
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
enum Event { // 1
|
||||
NewPeer {
|
||||
|
@ -63,7 +24,7 @@ enum Event { // 1
|
|||
},
|
||||
}
|
||||
|
||||
async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
|
||||
async fn broker(mut events: Receiver<Event>) -> Result<()> {
|
||||
let mut peers: HashMap<String, Sender<String>> = HashMap::new(); // 2
|
||||
|
||||
while let Some(event) = events.next().await {
|
||||
|
@ -71,8 +32,7 @@ async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
|
|||
Event::Message { from, to, msg } => { // 3
|
||||
for addr in to {
|
||||
if let Some(peer) = peers.get_mut(&addr) {
|
||||
let msg = format!("from {}: {}\n", from, msg);
|
||||
peer.send(msg).await?
|
||||
peer.send(format!("from {}: {}\n", from, msg)).await?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +42,7 @@ async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
|
|||
Entry::Vacant(entry) => {
|
||||
let (client_sender, client_receiver) = mpsc::unbounded();
|
||||
entry.insert(client_sender); // 4
|
||||
spawn_and_log_error(connection_writer_loop(client_receiver, stream)); // 5
|
||||
spawn_and_log_error(client_writer(client_receiver, stream)); // 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,9 +52,9 @@ async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
|
|||
}
|
||||
```
|
||||
|
||||
1. The broker task should handle two types of events: a message or an arrival of a new peer.
|
||||
2. The internal state of the broker is a `HashMap`.
|
||||
1. Broker should handle two types of events: a message or an arrival of a new peer.
|
||||
2. Internal state of the broker is a `HashMap`.
|
||||
Note how we don't need a `Mutex` here and can confidently say, at each iteration of the broker's loop, what is the current set of peers
|
||||
3. To handle a message, we send it over a channel to each destination
|
||||
4. To handle a new peer, we first register it in the peer's map ...
|
||||
4. To handle new peer, we first register it in the peer's map ...
|
||||
5. ... and then spawn a dedicated task to actually write the messages to the socket.
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
## Handling Disconnections
|
||||
|
||||
Currently, we only ever _add_ new peers to the map.
|
||||
Currently, we only ever *add* new peers to the map.
|
||||
This is clearly wrong: if a peer closes connection to the chat, we should not try to send any more messages to it.
|
||||
|
||||
One subtlety with handling disconnection is that we can detect it either in the reader's task, or in the writer's task.
|
||||
The most obvious solution here is to just remove the peer from the `peers` map in both cases, but this would be wrong.
|
||||
If _both_ read and write fail, we'll remove the peer twice, but it can be the case that the peer reconnected between the two failures!
|
||||
If *both* read and write fail, we'll remove the peer twice, but it can be the case that the peer reconnected between the two failures!
|
||||
To fix this, we will only remove the peer when the write side finishes.
|
||||
If the read side finishes we will notify the write side that it should stop as well.
|
||||
That is, we need to add an ability to signal shutdown for the writer task.
|
||||
|
@ -15,20 +15,9 @@ There's a more minimal solution however, which makes clever use of RAII.
|
|||
Closing a channel is a synchronization event, so we don't need to send a shutdown message, we can just drop the sender.
|
||||
This way, we statically guarantee that we issue shutdown exactly once, even if we early return via `?` or panic.
|
||||
|
||||
First, let's add a shutdown channel to the `connection_loop`:
|
||||
First, let's add a shutdown channel to the `client`:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# extern crate futures;
|
||||
# use async_std::net::TcpStream;
|
||||
# use futures::channel::mpsc;
|
||||
# use futures::sink::SinkExt;
|
||||
# use std::sync::Arc;
|
||||
#
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
# type Sender<T> = mpsc::UnboundedSender<T>;
|
||||
# type Receiver<T> = mpsc::UnboundedReceiver<T>;
|
||||
#
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
enum Void {} // 1
|
||||
|
||||
|
@ -46,48 +35,37 @@ enum Event {
|
|||
},
|
||||
}
|
||||
|
||||
async fn connection_loop(mut broker: Sender<Event>, stream: Arc<TcpStream>) -> Result<()> {
|
||||
async fn client(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
|
||||
// ...
|
||||
# let name: String = unimplemented!();
|
||||
|
||||
let (_shutdown_sender, shutdown_receiver) = mpsc::unbounded::<Void>(); // 3
|
||||
broker.send(Event::NewPeer {
|
||||
name: name.clone(),
|
||||
stream: Arc::clone(&stream),
|
||||
shutdown: shutdown_receiver,
|
||||
}).await.unwrap();
|
||||
|
||||
// ...
|
||||
# unimplemented!()
|
||||
}
|
||||
```
|
||||
|
||||
1. To enforce that no messages are sent along the shutdown channel, we use an uninhabited type.
|
||||
2. We pass the shutdown channel to the writer task.
|
||||
1. To enforce that no messages are send along the shutdown channel, we use an uninhabited type.
|
||||
2. We pass the shutdown channel to the writer task
|
||||
3. In the reader, we create a `_shutdown_sender` whose only purpose is to get dropped.
|
||||
|
||||
In the `connection_writer_loop`, we now need to choose between shutdown and message channels.
|
||||
In the `client_writer`, we now need to choose between shutdown and message channels.
|
||||
We use the `select` macro for this purpose:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# extern crate futures;
|
||||
# use async_std::{net::TcpStream, prelude::*};
|
||||
# use futures::channel::mpsc;
|
||||
use futures::{select, FutureExt};
|
||||
# use std::sync::Arc;
|
||||
# type Receiver<T> = mpsc::UnboundedReceiver<T>;
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
# type Sender<T> = mpsc::UnboundedSender<T>;
|
||||
# #[derive(Debug)]
|
||||
# enum Void {} // 1
|
||||
```rust
|
||||
use futures::select;
|
||||
use futures::FutureExt;
|
||||
|
||||
async fn connection_writer_loop(
|
||||
async fn client_writer(
|
||||
messages: &mut Receiver<String>,
|
||||
stream: Arc<TcpStream>,
|
||||
shutdown: Receiver<Void>, // 1
|
||||
mut shutdown: Receiver<Void>, // 1
|
||||
) -> Result<()> {
|
||||
let mut stream = &*stream;
|
||||
let mut messages = messages.fuse();
|
||||
let mut shutdown = shutdown.fuse();
|
||||
loop { // 2
|
||||
select! {
|
||||
msg = messages.next().fuse() => match msg {
|
||||
|
@ -108,30 +86,35 @@ async fn connection_writer_loop(
|
|||
2. Because of `select`, we can't use a `while let` loop, so we desugar it further into a `loop`.
|
||||
3. In the shutdown case we use `match void {}` as a statically-checked `unreachable!()`.
|
||||
|
||||
Another problem is that between the moment we detect disconnection in `connection_writer_loop` and the moment when we actually remove the peer from the `peers` map, new messages might be pushed into the peer's channel.
|
||||
Another problem is that between the moment we detect disconnection in `client_writer` and the moment when we actually remove the peer from the `peers` map, new messages might be pushed into the peer's channel.
|
||||
To not lose these messages completely, we'll return the messages channel back to the broker.
|
||||
This also allows us to establish a useful invariant that the message channel strictly outlives the peer in the `peers` map, and makes the broker itself infallible.
|
||||
This also allows us to establish a useful invariant that the message channel strictly outlives the peer in the `peers` map, and makes the broker itself infailable.
|
||||
|
||||
## Final Code
|
||||
|
||||
The final code looks like this:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# extern crate futures;
|
||||
```rust
|
||||
#![feature(async_await)]
|
||||
|
||||
use std::{
|
||||
net::ToSocketAddrs,
|
||||
sync::Arc,
|
||||
collections::hash_map::{HashMap, Entry},
|
||||
};
|
||||
|
||||
use futures::{
|
||||
channel::mpsc,
|
||||
SinkExt,
|
||||
FutureExt,
|
||||
select,
|
||||
};
|
||||
|
||||
use async_std::{
|
||||
io::BufReader,
|
||||
net::{TcpListener, TcpStream, ToSocketAddrs},
|
||||
prelude::*,
|
||||
task,
|
||||
};
|
||||
use futures::channel::mpsc;
|
||||
use futures::sink::SinkExt;
|
||||
use futures::{select, FutureExt};
|
||||
use std::{
|
||||
collections::hash_map::{Entry, HashMap},
|
||||
future::Future,
|
||||
sync::Arc,
|
||||
net::{TcpListener, TcpStream},
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
@ -141,27 +124,27 @@ type Receiver<T> = mpsc::UnboundedReceiver<T>;
|
|||
#[derive(Debug)]
|
||||
enum Void {}
|
||||
|
||||
// main
|
||||
fn run() -> Result<()> {
|
||||
task::block_on(accept_loop("127.0.0.1:8080"))
|
||||
fn main() -> Result<()> {
|
||||
task::block_on(server("127.0.0.1:8080"))
|
||||
}
|
||||
|
||||
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
async fn server(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
|
||||
let (broker_sender, broker_receiver) = mpsc::unbounded();
|
||||
let broker_handle = task::spawn(broker_loop(broker_receiver));
|
||||
let broker = task::spawn(broker(broker_receiver));
|
||||
let mut incoming = listener.incoming();
|
||||
while let Some(stream) = incoming.next().await {
|
||||
let stream = stream?;
|
||||
println!("Accepting from: {}", stream.peer_addr()?);
|
||||
spawn_and_log_error(connection_loop(broker_sender.clone(), stream));
|
||||
spawn_and_log_error(client(broker_sender.clone(), stream));
|
||||
}
|
||||
drop(broker_sender);
|
||||
broker_handle.await;
|
||||
broker.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn connection_loop(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
|
||||
async fn client(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
|
||||
let stream = Arc::new(stream);
|
||||
let reader = BufReader::new(&*stream);
|
||||
let mut lines = reader.lines();
|
||||
|
@ -196,14 +179,12 @@ async fn connection_loop(mut broker: Sender<Event>, stream: TcpStream) -> Result
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn connection_writer_loop(
|
||||
async fn client_writer(
|
||||
messages: &mut Receiver<String>,
|
||||
stream: Arc<TcpStream>,
|
||||
shutdown: Receiver<Void>,
|
||||
mut shutdown: Receiver<Void>,
|
||||
) -> Result<()> {
|
||||
let mut stream = &*stream;
|
||||
let mut messages = messages.fuse();
|
||||
let mut shutdown = shutdown.fuse();
|
||||
loop {
|
||||
select! {
|
||||
msg = messages.next().fuse() => match msg {
|
||||
|
@ -233,18 +214,18 @@ enum Event {
|
|||
},
|
||||
}
|
||||
|
||||
async fn broker_loop(events: Receiver<Event>) {
|
||||
async fn broker(mut events: Receiver<Event>) {
|
||||
let (disconnect_sender, mut disconnect_receiver) = // 1
|
||||
mpsc::unbounded::<(String, Receiver<String>)>();
|
||||
let mut peers: HashMap<String, Sender<String>> = HashMap::new();
|
||||
let mut events = events.fuse();
|
||||
|
||||
loop {
|
||||
let event = select! {
|
||||
event = events.next().fuse() => match event {
|
||||
event = events.next() => match event {
|
||||
None => break, // 2
|
||||
Some(event) => event,
|
||||
},
|
||||
disconnect = disconnect_receiver.next().fuse() => {
|
||||
disconnect = disconnect_receiver.next() => {
|
||||
let (name, _pending_messages) = disconnect.unwrap(); // 3
|
||||
assert!(peers.remove(&name).is_some());
|
||||
continue;
|
||||
|
@ -254,8 +235,7 @@ async fn broker_loop(events: Receiver<Event>) {
|
|||
Event::Message { from, to, msg } => {
|
||||
for addr in to {
|
||||
if let Some(peer) = peers.get_mut(&addr) {
|
||||
let msg = format!("from {}: {}\n", from, msg);
|
||||
peer.send(msg).await
|
||||
peer.send(format!("from {}: {}\n", from, msg)).await
|
||||
.unwrap() // 6
|
||||
}
|
||||
}
|
||||
|
@ -268,7 +248,7 @@ async fn broker_loop(events: Receiver<Event>) {
|
|||
entry.insert(client_sender);
|
||||
let mut disconnect_sender = disconnect_sender.clone();
|
||||
spawn_and_log_error(async move {
|
||||
let res = connection_writer_loop(&mut client_receiver, stream, shutdown).await;
|
||||
let res = client_writer(&mut client_receiver, stream, shutdown).await;
|
||||
disconnect_sender.send((name, client_receiver)).await // 4
|
||||
.unwrap();
|
||||
res
|
||||
|
|
|
@ -1,40 +1,49 @@
|
|||
## Implementing a client
|
||||
|
||||
Since the protocol is line-based, implementing a client for the chat is straightforward:
|
||||
Let's now implement the client for the chat.
|
||||
Because the protocol is line-based, the implementation is pretty straightforward:
|
||||
|
||||
* Lines read from stdin should be sent over the socket.
|
||||
* Lines read from the socket should be echoed to stdout.
|
||||
|
||||
Although async does not significantly affect client performance (as unlike the server, the client interacts solely with one user and only needs limited concurrency), async is still useful for managing concurrency!
|
||||
Unlike the server, the client needs only limited concurrency, as it interacts with only a single user.
|
||||
For this reason, async doesn't bring a lot of performance benefits in this case.
|
||||
|
||||
The client has to read from stdin and the socket *simultaneously*.
|
||||
Programming this with threads is cumbersome, especially when implementing a clean shutdown.
|
||||
With async, the `select!` macro is all that is needed.
|
||||
However, async is still useful for managing concurrency!
|
||||
Specifically, the client should *simultaneously* read from stdin and from the socket.
|
||||
Programming this with threads is cumbersome, especially when implementing clean shutdown.
|
||||
With async, we can just use the `select!` macro.
|
||||
|
||||
```rust
|
||||
#![feature(async_await)]
|
||||
|
||||
use std::net::ToSocketAddrs;
|
||||
|
||||
use futures::select;
|
||||
use futures::FutureExt;
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# extern crate futures;
|
||||
use async_std::{
|
||||
io::{stdin, BufReader},
|
||||
net::{TcpStream, ToSocketAddrs},
|
||||
prelude::*,
|
||||
net::TcpStream,
|
||||
task,
|
||||
io::{stdin, BufReader},
|
||||
};
|
||||
use futures::{select, FutureExt};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
// main
|
||||
fn run() -> Result<()> {
|
||||
task::block_on(try_run("127.0.0.1:8080"))
|
||||
|
||||
fn main() -> Result<()> {
|
||||
task::block_on(try_main("127.0.0.1:8080"))
|
||||
}
|
||||
|
||||
async fn try_run(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
async fn try_main(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
let (reader, mut writer) = (&stream, &stream); // 1
|
||||
let mut lines_from_server = BufReader::new(reader).lines().fuse(); // 2
|
||||
let mut lines_from_stdin = BufReader::new(stdin()).lines().fuse(); // 2
|
||||
let reader = BufReader::new(reader);
|
||||
let mut lines_from_server = futures::StreamExt::fuse(reader.lines()); // 2
|
||||
|
||||
let stdin = BufReader::new(stdin());
|
||||
let mut lines_from_stdin = futures::StreamExt::fuse(stdin.lines()); // 2
|
||||
loop {
|
||||
select! { // 3
|
||||
line = lines_from_server.next().fuse() => match line {
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
# Tutorial: Writing a chat
|
||||
|
||||
Nothing is simpler than creating a chat server, right?
|
||||
Not quite, chat servers expose you to all the fun of asynchronous programming:
|
||||
Nothing is as simple as a chat server, right? Not quite, chat servers
|
||||
already expose you to all the fun of asynchronous programming: how
|
||||
do you handle clients connecting concurrently. How do you handle them disconnecting?
|
||||
|
||||
How will the server handle clients connecting concurrently?
|
||||
How do you distribute the messages?
|
||||
|
||||
How will it handle them disconnecting?
|
||||
In this tutorial, we will show you how to write one in `async-std`.
|
||||
|
||||
How will it distribute the messages?
|
||||
You can also find the tutorial in [our repository](https://github.com/async-rs/a-chat).
|
||||
|
||||
This tutorial explains how to write a chat server in `async-std`.
|
||||
|
||||
You can also find the tutorial in [our repository](https://github.com/async-rs/async-std/blob/master/examples/a-chat).
|
||||
|
|
|
@ -7,33 +7,21 @@ We need to:
|
|||
2. interpret the first line as a login
|
||||
3. parse the rest of the lines as a `login: message`
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# use async_std::{
|
||||
# net::{TcpListener, ToSocketAddrs},
|
||||
# prelude::*,
|
||||
# task,
|
||||
# };
|
||||
#
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
#
|
||||
use async_std::{
|
||||
io::BufReader,
|
||||
net::TcpStream,
|
||||
};
|
||||
```rust
|
||||
use async_std::net::TcpStream;
|
||||
|
||||
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
async fn server(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
let mut incoming = listener.incoming();
|
||||
while let Some(stream) = incoming.next().await {
|
||||
let stream = stream?;
|
||||
println!("Accepting from: {}", stream.peer_addr()?);
|
||||
let _handle = task::spawn(connection_loop(stream)); // 1
|
||||
let _handle = task::spawn(client(stream)); // 1
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn connection_loop(stream: TcpStream) -> Result<()> {
|
||||
async fn client(stream: TcpStream) -> Result<()> {
|
||||
let reader = BufReader::new(&stream); // 2
|
||||
let mut lines = reader.lines();
|
||||
|
||||
|
@ -50,14 +38,14 @@ async fn connection_loop(stream: TcpStream) -> Result<()> {
|
|||
Some(idx) => (&line[..idx], line[idx + 1 ..].trim()),
|
||||
};
|
||||
let dest: Vec<String> = dest.split(',').map(|name| name.trim().to_string()).collect();
|
||||
let msg: String = msg.to_string();
|
||||
let msg: String = msg.trim().to_string();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
1. We use `task::spawn` function to spawn an independent task for working with each client.
|
||||
That is, after accepting the client the `accept_loop` immediately starts waiting for the next one.
|
||||
That is, after accepting the client the `server` loop immediately starts waiting for the next one.
|
||||
This is the core benefit of event-driven architecture: we serve many clients concurrently, without spending many hardware threads.
|
||||
|
||||
2. Luckily, the "split byte stream into lines" functionality is already implemented.
|
||||
|
@ -71,48 +59,13 @@ async fn connection_loop(stream: TcpStream) -> Result<()> {
|
|||
|
||||
## Managing Errors
|
||||
|
||||
One serious problem in the above solution is that, while we correctly propagate errors in the `connection_loop`, we just drop the error on the floor afterwards!
|
||||
One serious problem in the above solution is that, while we correctly propagate errors in the `client`, we just drop the error on the floor afterwards!
|
||||
That is, `task::spawn` does not return an error immediately (it can't, it needs to run the future to completion first), only after it is joined.
|
||||
We can "fix" it by waiting for the task to be joined, like this:
|
||||
|
||||
```rust,edition2018
|
||||
# #![feature(async_closure)]
|
||||
# extern crate async_std;
|
||||
# use async_std::{
|
||||
# io::BufReader,
|
||||
# net::{TcpListener, TcpStream, ToSocketAddrs},
|
||||
# prelude::*,
|
||||
# task,
|
||||
# };
|
||||
#
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
#
|
||||
# async fn connection_loop(stream: TcpStream) -> Result<()> {
|
||||
# let reader = BufReader::new(&stream); // 2
|
||||
# let mut lines = reader.lines();
|
||||
#
|
||||
# let name = match lines.next().await { // 3
|
||||
# None => Err("peer disconnected immediately")?,
|
||||
# Some(line) => line?,
|
||||
# };
|
||||
# println!("name = {}", name);
|
||||
#
|
||||
# while let Some(line) = lines.next().await { // 4
|
||||
# let line = line?;
|
||||
# let (dest, msg) = match line.find(':') { // 5
|
||||
# None => continue,
|
||||
# Some(idx) => (&line[..idx], line[idx + 1 ..].trim()),
|
||||
# };
|
||||
# let dest: Vec<String> = dest.split(',').map(|name| name.trim().to_string()).collect();
|
||||
# let msg: String = msg.trim().to_string();
|
||||
# }
|
||||
# Ok(())
|
||||
# }
|
||||
#
|
||||
# async move |stream| {
|
||||
let handle = task::spawn(connection_loop(stream));
|
||||
```rust
|
||||
let handle = task::spawn(client(stream));
|
||||
handle.await?
|
||||
# };
|
||||
```
|
||||
|
||||
The `.await` waits until the client finishes, and `?` propagates the result.
|
||||
|
@ -125,13 +78,7 @@ That is, a flaky internet connection of one peer brings down the whole chat room
|
|||
A correct way to handle client errors in this case is log them, and continue serving other clients.
|
||||
So let's use a helper function for this:
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# use async_std::{
|
||||
# io,
|
||||
# prelude::*,
|
||||
# task,
|
||||
# };
|
||||
```rust
|
||||
fn spawn_and_log_error<F>(fut: F) -> task::JoinHandle<()>
|
||||
where
|
||||
F: Future<Output = Result<()>> + Send + 'static,
|
||||
|
|
|
@ -1,32 +1,24 @@
|
|||
## Sending Messages
|
||||
|
||||
Now it's time to implement the other half -- sending messages.
|
||||
A most obvious way to implement sending is to give each `connection_loop` access to the write half of `TcpStream` of each other clients.
|
||||
A most obvious way to implement sending is to give each `client` access to the write half of `TcpStream` of each other clients.
|
||||
That way, a client can directly `.write_all` a message to recipients.
|
||||
However, this would be wrong: if Alice sends `bob: foo`, and Charley sends `bob: bar`, Bob might actually receive `fobaor`.
|
||||
Sending a message over a socket might require several syscalls, so two concurrent `.write_all`'s might interfere with each other!
|
||||
|
||||
As a rule of thumb, only a single task should write to each `TcpStream`.
|
||||
So let's create a `connection_writer_loop` task which receives messages over a channel and writes them to the socket.
|
||||
So let's create a `client_writer` task which receives messages over a channel and writes them to the socket.
|
||||
This task would be the point of serialization of messages.
|
||||
if Alice and Charley send two messages to Bob at the same time, Bob will see the messages in the same order as they arrive in the channel.
|
||||
|
||||
```rust,edition2018
|
||||
# extern crate async_std;
|
||||
# extern crate futures;
|
||||
# use async_std::{
|
||||
# net::TcpStream,
|
||||
# prelude::*,
|
||||
# };
|
||||
```rust
|
||||
use futures::channel::mpsc; // 1
|
||||
use futures::sink::SinkExt;
|
||||
use std::sync::Arc;
|
||||
use futures::SinkExt;
|
||||
|
||||
# type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
type Sender<T> = mpsc::UnboundedSender<T>; // 2
|
||||
type Receiver<T> = mpsc::UnboundedReceiver<T>;
|
||||
|
||||
async fn connection_writer_loop(
|
||||
async fn client_writer(
|
||||
mut messages: Receiver<String>,
|
||||
stream: Arc<TcpStream>, // 3
|
||||
) -> Result<()> {
|
||||
|
@ -40,5 +32,5 @@ async fn connection_writer_loop(
|
|||
|
||||
1. We will use channels from the `futures` crate.
|
||||
2. For simplicity, we will use `unbounded` channels, and won't be discussing backpressure in this tutorial.
|
||||
3. As `connection_loop` and `connection_writer_loop` share the same `TcpStream`, we need to put it into an `Arc`.
|
||||
Note that because `client` only reads from the stream and `connection_writer_loop` only writes to the stream, we don't get a race here.
|
||||
3. As `client` and `client_writer` share the same `TcpStream`, we need to put it into an `Arc`.
|
||||
Note that because `client` only reads from the stream and `client_writer` only writes to the stream, we don't get a race here.
|
||||
|
|
|
@ -12,7 +12,7 @@ After that, the client can send messages to other clients using the following sy
|
|||
login1, login2, ... loginN: message
|
||||
```
|
||||
|
||||
Each of the specified clients then receives a `from login: message` message.
|
||||
Each of the specified clients than receives a `from login: message` message.
|
||||
|
||||
A possible session might look like this
|
||||
|
||||
|
@ -38,10 +38,10 @@ $ cargo new a-chat
|
|||
$ cd a-chat
|
||||
```
|
||||
|
||||
Add the following lines to `Cargo.toml`:
|
||||
At the moment `async-std` requires Rust nightly, so let's add a rustup override for convenience:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
futures = "0.3.0"
|
||||
async-std = "1"
|
||||
```bash
|
||||
$ rustup override add nightly
|
||||
$ rustc --version
|
||||
rustc 1.38.0-nightly (c4715198b 2019-08-05)
|
||||
```
|
||||
|
|
|
@ -1,171 +0,0 @@
|
|||
# Examples
|
||||
|
||||
This directory contains example code that makes use of `async-std`, each of which can be run from the command line.
|
||||
|
||||
##### [Hello World][hello-world]
|
||||
|
||||
Spawns a task that says hello.
|
||||
|
||||
```
|
||||
cargo run --example hello-world
|
||||
```
|
||||
|
||||
##### [Line Count][line-count]
|
||||
|
||||
Counts the number of lines in a file given as an argument.
|
||||
|
||||
```shell
|
||||
cargo run --example line-count -- ./Cargo.toml
|
||||
```
|
||||
|
||||
##### [List Dir][list-dir]
|
||||
|
||||
Lists files in a directory given as an argument.
|
||||
|
||||
```shell
|
||||
cargo run --example list-dir -- .
|
||||
```
|
||||
|
||||
##### [Logging][logging]
|
||||
|
||||
Prints the runtime's execution log on the standard output.
|
||||
|
||||
```shell
|
||||
cargo run --example logging
|
||||
```
|
||||
|
||||
##### [Print File][print-file]
|
||||
|
||||
Prints a file given as an argument to stdout.
|
||||
|
||||
```shell
|
||||
cargo run --example print-file ./Cargo.toml
|
||||
```
|
||||
|
||||
##### [Socket Timeouts][socket-timeouts]
|
||||
|
||||
Prints response of GET request made to TCP server with 5 second socket timeout
|
||||
|
||||
```shell
|
||||
cargo run --example socket-timeouts
|
||||
```
|
||||
|
||||
##### [Stdin Echo][stdin-echo]
|
||||
|
||||
Echoes lines read on stdin to stdout.
|
||||
|
||||
```shell
|
||||
cargo run --example stdin-echo
|
||||
```
|
||||
|
||||
##### [Stdin Timeout][stdin-timeout]
|
||||
|
||||
Reads a line from stdin, or exits with an error if nothing is read in 5 seconds.
|
||||
|
||||
```shell
|
||||
cargo run --example stdin-timeout
|
||||
```
|
||||
|
||||
##### [Surf Web][surf-web]
|
||||
|
||||
Sends an HTTP request to the Rust website.
|
||||
|
||||
```shell
|
||||
cargo run --example surf-web
|
||||
```
|
||||
|
||||
##### [Task Local][task-local]
|
||||
|
||||
Creates a task-local value.
|
||||
|
||||
```shell
|
||||
cargo run --example task-local
|
||||
```
|
||||
|
||||
##### [Task Name][task-name]
|
||||
|
||||
Spawns a named task that prints its name.
|
||||
|
||||
```shell
|
||||
cargo run --example task-name
|
||||
```
|
||||
|
||||
##### [TCP Client][tcp-client]
|
||||
|
||||
Connects to Localhost over TCP.
|
||||
|
||||
First, start the echo server:
|
||||
|
||||
```shell
|
||||
cargo run --example tcp-echo
|
||||
```
|
||||
|
||||
Then run the client:
|
||||
|
||||
```shell
|
||||
cargo run --example tcp-client
|
||||
```
|
||||
|
||||
##### [TCP Echo][tcp-echo]
|
||||
|
||||
TCP echo server.
|
||||
|
||||
Start the echo server:
|
||||
|
||||
```shell
|
||||
cargo run --example tcp-echo
|
||||
```
|
||||
|
||||
Make requests by running the client example:
|
||||
|
||||
```shell
|
||||
cargo run --example tcp-client
|
||||
```
|
||||
|
||||
##### [UDP Client][udp-client]
|
||||
|
||||
Connects to Localhost over UDP.
|
||||
|
||||
First, start the echo server:
|
||||
|
||||
```shell
|
||||
cargo run --example udp-echo
|
||||
```
|
||||
|
||||
Then run the client:
|
||||
|
||||
```shell
|
||||
cargo run --example udp-client
|
||||
```
|
||||
|
||||
##### [UDP Echo][udp-echo]
|
||||
|
||||
UDP echo server.
|
||||
|
||||
Start the echo server:
|
||||
|
||||
```shell
|
||||
cargo run --example udp-echo
|
||||
```
|
||||
|
||||
Make requests by running the client example:
|
||||
|
||||
```shell
|
||||
cargo run --example udp-client
|
||||
```
|
||||
|
||||
[hello-world]: https://github.com/async-rs/async-std/blob/master/examples/hello-world.rs
|
||||
[line-count]: https://github.com/async-rs/async-std/blob/master/examples/line-count.rs
|
||||
[list-dir]: https://github.com/async-rs/async-std/blob/master/examples/list-dir.rs
|
||||
[logging]: https://github.com/async-rs/async-std/blob/master/examples/logging.rs
|
||||
[print-file]: https://github.com/async-rs/async-std/blob/master/examples/print-file.rs
|
||||
[socket-timeouts]: https://github.com/async-rs/async-std/blob/master/examples/socket-timeouts.rs
|
||||
[stdin-echo]: https://github.com/async-rs/async-std/blob/master/examples/stdin-echo.rs
|
||||
[stdin-timeout]: https://github.com/async-rs/async-std/blob/master/examples/stdin-timeout.rs
|
||||
[surf-web]: https://github.com/async-rs/async-std/blob/master/examples/surf-web.rs
|
||||
[task-local]: https://github.com/async-rs/async-std/blob/master/examples/task-local.rs
|
||||
[task-name]: https://github.com/async-rs/async-std/blob/master/examples/task-name.rs
|
||||
[tcp-client]: https://github.com/async-rs/async-std/blob/master/examples/tcp-client.rs
|
||||
[tcp-echo]: https://github.com/async-rs/async-std/blob/master/examples/tcp-echo.rs
|
||||
[udp-client]: https://github.com/async-rs/async-std/blob/master/examples/udp-client.rs
|
||||
[udp-echo]: https://github.com/async-rs/async-std/blob/master/examples/udp-echo.rs
|
|
@ -1,45 +0,0 @@
|
|||
use futures::select;
|
||||
use futures::FutureExt;
|
||||
|
||||
use async_std::{
|
||||
io::{stdin, BufReader},
|
||||
net::{TcpStream, ToSocketAddrs},
|
||||
prelude::*,
|
||||
task,
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
pub(crate) fn main() -> Result<()> {
|
||||
task::block_on(try_main("127.0.0.1:8080"))
|
||||
}
|
||||
|
||||
async fn try_main(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
let (reader, mut writer) = (&stream, &stream);
|
||||
let reader = BufReader::new(reader);
|
||||
let mut lines_from_server = futures::StreamExt::fuse(reader.lines());
|
||||
|
||||
let stdin = BufReader::new(stdin());
|
||||
let mut lines_from_stdin = futures::StreamExt::fuse(stdin.lines());
|
||||
loop {
|
||||
select! {
|
||||
line = lines_from_server.next().fuse() => match line {
|
||||
Some(line) => {
|
||||
let line = line?;
|
||||
println!("{}", line);
|
||||
},
|
||||
None => break,
|
||||
},
|
||||
line = lines_from_stdin.next().fuse() => match line {
|
||||
Some(line) => {
|
||||
let line = line?;
|
||||
writer.write_all(line.as_bytes()).await?;
|
||||
writer.write_all(b"\n").await?;
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
mod client;
|
||||
mod server;
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut args = std::env::args();
|
||||
match (args.nth(1).as_ref().map(String::as_str), args.next()) {
|
||||
(Some("client"), None) => client::main(),
|
||||
(Some("server"), None) => server::main(),
|
||||
_ => Err("Usage: a-chat [client|server]".into()),
|
||||
}
|
||||
}
|
|
@ -1,184 +0,0 @@
|
|||
use std::{
|
||||
collections::hash_map::{Entry, HashMap},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use futures::{channel::mpsc, select, FutureExt, SinkExt};
|
||||
|
||||
use async_std::{
|
||||
io::BufReader,
|
||||
net::{TcpListener, TcpStream, ToSocketAddrs},
|
||||
prelude::*,
|
||||
task,
|
||||
};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
type Sender<T> = mpsc::UnboundedSender<T>;
|
||||
type Receiver<T> = mpsc::UnboundedReceiver<T>;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Void {}
|
||||
|
||||
pub(crate) fn main() -> Result<()> {
|
||||
task::block_on(accept_loop("127.0.0.1:8080"))
|
||||
}
|
||||
|
||||
async fn accept_loop(addr: impl ToSocketAddrs) -> Result<()> {
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
|
||||
let (broker_sender, broker_receiver) = mpsc::unbounded();
|
||||
let broker = task::spawn(broker_loop(broker_receiver));
|
||||
let mut incoming = listener.incoming();
|
||||
while let Some(stream) = incoming.next().await {
|
||||
let stream = stream?;
|
||||
println!("Accepting from: {}", stream.peer_addr()?);
|
||||
spawn_and_log_error(connection_loop(broker_sender.clone(), stream));
|
||||
}
|
||||
drop(broker_sender);
|
||||
broker.await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn connection_loop(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
|
||||
let stream = Arc::new(stream);
|
||||
let reader = BufReader::new(&*stream);
|
||||
let mut lines = reader.lines();
|
||||
|
||||
let name = match lines.next().await {
|
||||
None => return Err("peer disconnected immediately".into()),
|
||||
Some(line) => line?,
|
||||
};
|
||||
let (_shutdown_sender, shutdown_receiver) = mpsc::unbounded::<Void>();
|
||||
broker
|
||||
.send(Event::NewPeer {
|
||||
name: name.clone(),
|
||||
stream: Arc::clone(&stream),
|
||||
shutdown: shutdown_receiver,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
while let Some(line) = lines.next().await {
|
||||
let line = line?;
|
||||
let (dest, msg) = match line.find(':') {
|
||||
None => continue,
|
||||
Some(idx) => (&line[..idx], line[idx + 1..].trim()),
|
||||
};
|
||||
let dest: Vec<String> = dest
|
||||
.split(',')
|
||||
.map(|name| name.trim().to_string())
|
||||
.collect();
|
||||
let msg: String = msg.trim().to_string();
|
||||
|
||||
broker
|
||||
.send(Event::Message {
|
||||
from: name.clone(),
|
||||
to: dest,
|
||||
msg,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn connection_writer_loop(
|
||||
messages: &mut Receiver<String>,
|
||||
stream: Arc<TcpStream>,
|
||||
mut shutdown: Receiver<Void>,
|
||||
) -> Result<()> {
|
||||
let mut stream = &*stream;
|
||||
loop {
|
||||
select! {
|
||||
msg = messages.next().fuse() => match msg {
|
||||
Some(msg) => stream.write_all(msg.as_bytes()).await?,
|
||||
None => break,
|
||||
},
|
||||
void = shutdown.next().fuse() => match void {
|
||||
Some(void) => match void {},
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Event {
|
||||
NewPeer {
|
||||
name: String,
|
||||
stream: Arc<TcpStream>,
|
||||
shutdown: Receiver<Void>,
|
||||
},
|
||||
Message {
|
||||
from: String,
|
||||
to: Vec<String>,
|
||||
msg: String,
|
||||
},
|
||||
}
|
||||
|
||||
async fn broker_loop(mut events: Receiver<Event>) {
|
||||
let (disconnect_sender, mut disconnect_receiver) =
|
||||
mpsc::unbounded::<(String, Receiver<String>)>();
|
||||
let mut peers: HashMap<String, Sender<String>> = HashMap::new();
|
||||
|
||||
loop {
|
||||
let event = select! {
|
||||
event = events.next().fuse() => match event {
|
||||
None => break,
|
||||
Some(event) => event,
|
||||
},
|
||||
disconnect = disconnect_receiver.next().fuse() => {
|
||||
let (name, _pending_messages) = disconnect.unwrap();
|
||||
assert!(peers.remove(&name).is_some());
|
||||
continue;
|
||||
},
|
||||
};
|
||||
match event {
|
||||
Event::Message { from, to, msg } => {
|
||||
for addr in to {
|
||||
if let Some(peer) = peers.get_mut(&addr) {
|
||||
let msg = format!("from {}: {}\n", from, msg);
|
||||
peer.send(msg).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::NewPeer {
|
||||
name,
|
||||
stream,
|
||||
shutdown,
|
||||
} => match peers.entry(name.clone()) {
|
||||
Entry::Occupied(..) => (),
|
||||
Entry::Vacant(entry) => {
|
||||
let (client_sender, mut client_receiver) = mpsc::unbounded();
|
||||
entry.insert(client_sender);
|
||||
let mut disconnect_sender = disconnect_sender.clone();
|
||||
spawn_and_log_error(async move {
|
||||
let res =
|
||||
connection_writer_loop(&mut client_receiver, stream, shutdown).await;
|
||||
disconnect_sender
|
||||
.send((name, client_receiver))
|
||||
.await
|
||||
.unwrap();
|
||||
res
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
drop(peers);
|
||||
drop(disconnect_sender);
|
||||
while let Some((_name, _pending_messages)) = disconnect_receiver.next().await {}
|
||||
}
|
||||
|
||||
fn spawn_and_log_error<F>(fut: F) -> task::JoinHandle<()>
|
||||
where
|
||||
F: Future<Output = Result<()>> + Send + 'static,
|
||||
{
|
||||
task::spawn(async move {
|
||||
if let Err(e) = fut.await {
|
||||
eprintln!("{}", e)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
//! Spawns a task that says hello.
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use async_std::task;
|
||||
|
||||
async fn say_hi() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Counts the number of lines in a file given as an argument.
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use std::env::args;
|
||||
|
||||
use async_std::fs::File;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Lists files in a directory given as an argument.
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use std::env::args;
|
||||
|
||||
use async_std::fs;
|
||||
|
@ -13,9 +15,8 @@ fn main() -> io::Result<()> {
|
|||
task::block_on(async {
|
||||
let mut dir = fs::read_dir(&path).await?;
|
||||
|
||||
while let Some(res) = dir.next().await {
|
||||
let entry = res?;
|
||||
println!("{}", entry.file_name().to_string_lossy());
|
||||
while let Some(entry) = dir.next().await {
|
||||
println!("{}", entry?.file_name().to_string_lossy());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Prints the runtime's execution log on the standard output.
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use async_std::task;
|
||||
|
||||
fn main() {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Prints a file given as an argument to stdout.
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use std::env::args;
|
||||
|
||||
use async_std::fs::File;
|
||||
|
@ -7,7 +9,7 @@ use async_std::io;
|
|||
use async_std::prelude::*;
|
||||
use async_std::task;
|
||||
|
||||
const LEN: usize = 16 * 1024; // 16 Kb
|
||||
const LEN: usize = 4 * 1024 * 1024; // 4 Mb
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
let path = args().nth(1).expect("missing path argument");
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
//! Prints response of GET request made to TCP server with 5 second socket timeout
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use async_std::{io, net::TcpStream, prelude::*, task};
|
||||
|
||||
async fn get() -> io::Result<Vec<u8>> {
|
||||
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 move {
|
||||
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);
|
||||
});
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
//! Echoes lines read on stdin to stdout.
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use async_std::io;
|
||||
use async_std::prelude::*;
|
||||
use async_std::task;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Reads a line from stdin, or exits with an error if nothing is read in 5 seconds.
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use async_std::io;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
//! Sends an HTTP request to the Rust website.
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use async_std::task;
|
||||
|
||||
fn main() -> Result<(), surf::Exception> {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Creates a task-local value.
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
use async_std::prelude::*;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
//! Spawns a named task that prints its name.
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use async_std::task;
|
||||
|
||||
async fn print_name() {
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
//! $ cargo run --example tcp-client
|
||||
//! ```
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use async_std::io;
|
||||
use async_std::net::TcpStream;
|
||||
use async_std::prelude::*;
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
//! $ nc localhost 8080
|
||||
//! ```
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use async_std::io;
|
||||
use async_std::net::{TcpListener, TcpStream};
|
||||
use async_std::prelude::*;
|
||||
|
@ -14,9 +16,8 @@ use async_std::task;
|
|||
async fn process(stream: TcpStream) -> io::Result<()> {
|
||||
println!("Accepted from: {}", stream.peer_addr()?);
|
||||
|
||||
let mut reader = stream.clone();
|
||||
let mut writer = stream;
|
||||
io::copy(&mut reader, &mut writer).await?;
|
||||
let (reader, writer) = &mut (&stream, &stream);
|
||||
io::copy(reader, writer).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
//! TCP echo server, accepting connections both on both ipv4 and ipv6 sockets.
|
||||
//!
|
||||
//! To send messages, do:
|
||||
//!
|
||||
//! ```sh
|
||||
//! $ nc 127.0.0.1 8080
|
||||
//! $ nc ::1 8080
|
||||
//! ```
|
||||
|
||||
use async_std::io;
|
||||
use async_std::net::{TcpListener, TcpStream};
|
||||
use async_std::prelude::*;
|
||||
use async_std::task;
|
||||
|
||||
async fn process(stream: TcpStream) -> io::Result<()> {
|
||||
println!("Accepted from: {}", stream.peer_addr()?);
|
||||
|
||||
let mut reader = stream.clone();
|
||||
let mut writer = stream;
|
||||
io::copy(&mut reader, &mut writer).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
task::block_on(async {
|
||||
let ipv4_listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
println!("Listening on {}", ipv4_listener.local_addr()?);
|
||||
let ipv6_listener = TcpListener::bind("[::1]:8080").await?;
|
||||
println!("Listening on {}", ipv6_listener.local_addr()?);
|
||||
|
||||
let ipv4_incoming = ipv4_listener.incoming();
|
||||
let ipv6_incoming = ipv6_listener.incoming();
|
||||
|
||||
let mut incoming = ipv4_incoming.merge(ipv6_incoming);
|
||||
|
||||
while let Some(stream) = incoming.next().await {
|
||||
let stream = stream?;
|
||||
task::spawn(async {
|
||||
process(stream).await.unwrap();
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
|
@ -12,6 +12,8 @@
|
|||
//! $ cargo run --example udp-client
|
||||
//! ```
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use async_std::io;
|
||||
use async_std::net::UdpSocket;
|
||||
use async_std::task;
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
//! $ nc -u localhost 8080
|
||||
//! ```
|
||||
|
||||
#![feature(async_await)]
|
||||
|
||||
use async_std::io;
|
||||
use async_std::net::UdpSocket;
|
||||
use async_std::task;
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
use std::collections::BinaryHeap;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, IntoStream};
|
||||
|
||||
impl<T: Ord + Send> stream::Extend<T> for BinaryHeap<T> {
|
||||
fn extend<'a, S: IntoStream<Item = T> + 'a>(
|
||||
&'a mut self,
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
let stream = stream.into_stream();
|
||||
|
||||
self.reserve(stream.size_hint().0);
|
||||
|
||||
Box::pin(stream.for_each(move |item| self.push(item)))
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
use std::collections::BinaryHeap;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, FromStream, IntoStream};
|
||||
|
||||
impl<T: Ord + Send> FromStream<T> for BinaryHeap<T> {
|
||||
#[inline]
|
||||
fn from_stream<'a, S: IntoStream<Item = T> + 'a>(
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = Self> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
let stream = stream.into_stream();
|
||||
|
||||
Box::pin(async move {
|
||||
let mut out = BinaryHeap::new();
|
||||
stream::extend(&mut out, stream).await;
|
||||
out
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//! The Rust priority queue implemented with a binary heap
|
||||
|
||||
mod extend;
|
||||
mod from_stream;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use std::collections::BinaryHeap;
|
|
@ -1,19 +0,0 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, IntoStream};
|
||||
|
||||
impl<K: Ord + Send, V: Send> stream::Extend<(K, V)> for BTreeMap<K, V> {
|
||||
fn extend<'a, S: IntoStream<Item = (K, V)> + 'a>(
|
||||
&'a mut self,
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
Box::pin(stream.into_stream().for_each(move |(k, v)| {
|
||||
self.insert(k, v);
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, FromStream, IntoStream};
|
||||
|
||||
impl<K: Ord + Send, V: Send> FromStream<(K, V)> for BTreeMap<K, V> {
|
||||
#[inline]
|
||||
fn from_stream<'a, S: IntoStream<Item = (K, V)> + 'a>(
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = Self> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
let stream = stream.into_stream();
|
||||
|
||||
Box::pin(async move {
|
||||
let mut out = BTreeMap::new();
|
||||
stream::extend(&mut out, stream).await;
|
||||
out
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//! The Rust B-Tree Map
|
||||
|
||||
mod extend;
|
||||
mod from_stream;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use std::collections::BTreeMap;
|
|
@ -1,19 +0,0 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, IntoStream};
|
||||
|
||||
impl<T: Ord + Send> stream::Extend<T> for BTreeSet<T> {
|
||||
fn extend<'a, S: IntoStream<Item = T> + 'a>(
|
||||
&'a mut self,
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
Box::pin(stream.into_stream().for_each(move |item| {
|
||||
self.insert(item);
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, FromStream, IntoStream};
|
||||
|
||||
impl<T: Ord + Send> FromStream<T> for BTreeSet<T> {
|
||||
#[inline]
|
||||
fn from_stream<'a, S: IntoStream<Item = T> + 'a>(
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = Self> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
let stream = stream.into_stream();
|
||||
|
||||
Box::pin(async move {
|
||||
let mut out = BTreeSet::new();
|
||||
stream::extend(&mut out, stream).await;
|
||||
out
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//! The Rust B-Tree Set
|
||||
|
||||
mod extend;
|
||||
mod from_stream;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use std::collections::BTreeSet;
|
|
@ -1,41 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
use std::hash::{BuildHasher, Hash};
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, IntoStream};
|
||||
|
||||
impl<K, V, H> stream::Extend<(K, V)> for HashMap<K, V, H>
|
||||
where
|
||||
K: Eq + Hash + Send,
|
||||
V: Send,
|
||||
H: BuildHasher + Default + Send,
|
||||
{
|
||||
fn extend<'a, S: IntoStream<Item = (K, V)> + 'a>(
|
||||
&'a mut self,
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
let stream = stream.into_stream();
|
||||
|
||||
// The following is adapted from the hashbrown source code:
|
||||
// https://github.com/rust-lang/hashbrown/blob/d1ad4fc3aae2ade446738eea512e50b9e863dd0c/src/map.rs#L2470-L2491
|
||||
//
|
||||
// Keys may be already present or show multiple times in the stream. Reserve the entire
|
||||
// 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.
|
||||
|
||||
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);
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
use std::hash::{BuildHasher, Hash};
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, FromStream, IntoStream};
|
||||
|
||||
impl<K, V, H> FromStream<(K, V)> for HashMap<K, V, H>
|
||||
where
|
||||
K: Eq + Hash + Send,
|
||||
H: BuildHasher + Default + Send,
|
||||
V: Send,
|
||||
{
|
||||
#[inline]
|
||||
fn from_stream<'a, S: IntoStream<Item = (K, V)> + 'a>(
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = Self> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
let stream = stream.into_stream();
|
||||
|
||||
Box::pin(async move {
|
||||
let mut out = HashMap::with_hasher(Default::default());
|
||||
stream::extend(&mut out, stream).await;
|
||||
out
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//! The Rust hash map, implemented with quadratic probing and SIMD lookup.
|
||||
|
||||
mod extend;
|
||||
mod from_stream;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use std::collections::HashMap;
|
|
@ -1,43 +0,0 @@
|
|||
use std::collections::HashSet;
|
||||
use std::hash::{BuildHasher, Hash};
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, IntoStream};
|
||||
|
||||
impl<T, H> stream::Extend<T> for HashSet<T, H>
|
||||
where
|
||||
T: Eq + Hash + Send,
|
||||
H: BuildHasher + Default + Send,
|
||||
{
|
||||
fn extend<'a, S: IntoStream<Item = T> + 'a>(
|
||||
&'a mut self,
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
// The Extend impl for HashSet in the standard library delegates to the internal HashMap.
|
||||
// Thus, this impl is just a copy of the async Extend impl for HashMap in this crate.
|
||||
|
||||
let stream = stream.into_stream();
|
||||
|
||||
// The following is adapted from the hashbrown source code:
|
||||
// https://github.com/rust-lang/hashbrown/blob/d1ad4fc3aae2ade446738eea512e50b9e863dd0c/src/map.rs#L2470-L2491
|
||||
//
|
||||
// Keys may be already present or show multiple times in the stream. Reserve the entire
|
||||
// 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.
|
||||
|
||||
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);
|
||||
}))
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
use std::collections::HashSet;
|
||||
use std::hash::{BuildHasher, Hash};
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, FromStream, IntoStream};
|
||||
|
||||
impl<T, H> FromStream<T> for HashSet<T, H>
|
||||
where
|
||||
T: Eq + Hash + Send,
|
||||
H: BuildHasher + Default + Send,
|
||||
{
|
||||
#[inline]
|
||||
fn from_stream<'a, S: IntoStream<Item = T> + 'a>(
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = Self> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
let stream = stream.into_stream();
|
||||
|
||||
Box::pin(async move {
|
||||
let mut out = HashSet::with_hasher(Default::default());
|
||||
stream::extend(&mut out, stream).await;
|
||||
out
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//! The Rust hash set, implemented as a `HashMap` where the value is `()`.
|
||||
|
||||
mod extend;
|
||||
mod from_stream;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use std::collections::HashSet;
|
|
@ -1,18 +0,0 @@
|
|||
use std::collections::LinkedList;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, IntoStream};
|
||||
|
||||
impl<T: Send> stream::Extend<T> for LinkedList<T> {
|
||||
fn extend<'a, S: IntoStream<Item = T> + 'a>(
|
||||
&'a mut self,
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
let stream = stream.into_stream();
|
||||
Box::pin(stream.for_each(move |item| self.push_back(item)))
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
use std::collections::LinkedList;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, FromStream, IntoStream};
|
||||
|
||||
impl<T: Send> FromStream<T> for LinkedList<T> {
|
||||
#[inline]
|
||||
fn from_stream<'a, S: IntoStream<Item = T> + 'a>(
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = Self> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
let stream = stream.into_stream();
|
||||
|
||||
Box::pin(async move {
|
||||
let mut out = LinkedList::new();
|
||||
stream::extend(&mut out, stream).await;
|
||||
out
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//! The Rust doubly-linked list with owned nodes
|
||||
|
||||
mod extend;
|
||||
mod from_stream;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use std::collections::LinkedList;
|
|
@ -1,20 +0,0 @@
|
|||
//! The Rust standard collections
|
||||
//!
|
||||
//! This library provides efficient implementations of the most common general purpose programming
|
||||
//! data structures.
|
||||
|
||||
pub mod binary_heap;
|
||||
pub mod btree_map;
|
||||
pub mod btree_set;
|
||||
pub mod hash_map;
|
||||
pub mod hash_set;
|
||||
pub mod linked_list;
|
||||
pub mod vec_deque;
|
||||
|
||||
pub use binary_heap::BinaryHeap;
|
||||
pub use btree_map::BTreeMap;
|
||||
pub use btree_set::BTreeSet;
|
||||
pub use hash_map::HashMap;
|
||||
pub use hash_set::HashSet;
|
||||
pub use linked_list::LinkedList;
|
||||
pub use vec_deque::VecDeque;
|
|
@ -1,21 +0,0 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, IntoStream};
|
||||
|
||||
impl<T: Send> stream::Extend<T> for VecDeque<T> {
|
||||
fn extend<'a, S: IntoStream<Item = T> + 'a>(
|
||||
&'a mut self,
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
let stream = stream.into_stream();
|
||||
|
||||
self.reserve(stream.size_hint().0);
|
||||
|
||||
Box::pin(stream.for_each(move |item| self.push_back(item)))
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::stream::{self, FromStream, IntoStream};
|
||||
|
||||
impl<T: Send> FromStream<T> for VecDeque<T> {
|
||||
#[inline]
|
||||
fn from_stream<'a, S: IntoStream<Item = T> + 'a>(
|
||||
stream: S,
|
||||
) -> Pin<Box<dyn Future<Output = Self> + 'a + Send>>
|
||||
where
|
||||
<S as IntoStream>::IntoStream: Send,
|
||||
{
|
||||
let stream = stream.into_stream();
|
||||
|
||||
Box::pin(async move {
|
||||
let mut out = VecDeque::new();
|
||||
stream::extend(&mut out, stream).await;
|
||||
out
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
//! The Rust double-ended queue, implemented with a growable ring buffer.
|
||||
|
||||
mod extend;
|
||||
mod from_stream;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use std::collections::VecDeque;
|
|
@ -1,7 +1,8 @@
|
|||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::io;
|
||||
use crate::path::{Path, PathBuf};
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Returns the canonical form of a path.
|
||||
///
|
||||
|
@ -14,15 +15,15 @@ use crate::utils::Context as _;
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` does not point to an existing file or directory.
|
||||
/// * A non-final component in `path` is not a directory.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * `path` does not exist.
|
||||
/// * A non-final component in path is not a directory.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -33,10 +34,5 @@ use crate::utils::Context as _;
|
|||
/// ```
|
||||
pub async fn canonicalize<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::canonicalize(&path)
|
||||
.map(Into::into)
|
||||
.context(|| format!("could not canonicalize `{}`", path.display()))
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::canonicalize(path) }).await
|
||||
}
|
||||
|
|
|
@ -1,50 +1,43 @@
|
|||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Copies the contents and permissions of a file to a new location.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Copies the contents and permissions of one file to another.
|
||||
///
|
||||
/// On success, the total number of bytes copied is returned and equals the length of the `to` file
|
||||
/// after this operation.
|
||||
/// On success, the total number of bytes copied is returned and equals the length of the `from`
|
||||
/// file.
|
||||
///
|
||||
/// The old contents of `to` will be overwritten. If `from` and `to` both point to the same file,
|
||||
/// then the file will likely get truncated as a result of this operation.
|
||||
///
|
||||
/// If you're working with open [`File`]s and want to copy contents through those types, use the
|
||||
/// [`io::copy`] function.
|
||||
/// then the file will likely get truncated by this operation.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::copy`].
|
||||
///
|
||||
/// [`File`]: struct.File.html
|
||||
/// [`io::copy`]: ../io/fn.copy.html
|
||||
/// [`std::fs::copy`]: https://doc.rust-lang.org/std/fs/fn.copy.html
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `from` does not point to an existing file.
|
||||
/// * The current process lacks permissions to read `from` or write `to`.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * The `from` path is not a file.
|
||||
/// * The `from` file does not exist.
|
||||
/// * The current process lacks permissions to access `from` or write `to`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let num_bytes = fs::copy("a.txt", "b.txt").await?;
|
||||
/// let bytes_copied = fs::copy("a.txt", "b.txt").await?;
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub async fn copy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<u64> {
|
||||
let from = from.as_ref().to_owned();
|
||||
let to = to.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::copy(&from, &to)
|
||||
.context(|| format!("could not copy `{}` to `{}`", from.display(), to.display()))
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::copy(&from, &to) }).await
|
||||
}
|
||||
|
|
|
@ -1,43 +1,36 @@
|
|||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Creates a new directory.
|
||||
///
|
||||
/// Note that this function will only create the final directory in `path`. If you want to create
|
||||
/// all of its missing parent directories too, use the [`create_dir_all`] function instead.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Creates a new, empty directory.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::create_dir`].
|
||||
///
|
||||
/// [`create_dir_all`]: fn.create_dir_all.html
|
||||
/// [`std::fs::create_dir`]: https://doc.rust-lang.org/std/fs/fn.create_dir.html
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` already points to an existing file or directory.
|
||||
/// * A parent directory in `path` does not exist.
|
||||
/// * The current process lacks permissions to create the directory.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * `path` already exists.
|
||||
/// * A parent of the given path does not exist.
|
||||
/// * The current process lacks permissions to create directory at `path`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// fs::create_dir("./some/directory").await?;
|
||||
/// fs::create_dir("./some/dir").await?;
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub async fn create_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::create_dir(&path)
|
||||
.context(|| format!("could not create directory `{}`", path.display()))
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::create_dir(path) }).await
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Creates a new directory and all of its parents if they are missing.
|
||||
use crate::task::blocking;
|
||||
use crate::io;
|
||||
|
||||
/// Creates a new, empty directory and all of its parents if they are missing.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::create_dir_all`].
|
||||
///
|
||||
|
@ -11,28 +12,24 @@ use crate::utils::Context as _;
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` already points to an existing file or directory.
|
||||
/// * The current process lacks permissions to create the directory or its missing parents.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * The parent directories do not exists and couldn't be created.
|
||||
/// * The current process lacks permissions to create directory at `path`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// fs::create_dir_all("./some/directory").await?;
|
||||
/// fs::create_dir_all("./some/dir").await?;
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub async fn create_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::create_dir_all(&path)
|
||||
.context(|| format!("could not create directory path `{}`", path.display()))
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::create_dir_all(path) }).await
|
||||
}
|
||||
|
|
|
@ -1,31 +1,27 @@
|
|||
use std::future::Future;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
use crate::future::Future;
|
||||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// A builder for creating directories with configurable options.
|
||||
///
|
||||
/// For Unix-specific options, import the [`os::unix::fs::DirBuilderExt`] trait.
|
||||
/// A builder for creating directories in various manners.
|
||||
///
|
||||
/// This type is an async version of [`std::fs::DirBuilder`].
|
||||
///
|
||||
/// [`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, Default)]
|
||||
#[derive(Debug)]
|
||||
pub struct DirBuilder {
|
||||
/// Set to `true` if non-existent parent directories should be created.
|
||||
recursive: bool,
|
||||
|
||||
/// Unix mode for newly created directories.
|
||||
#[cfg(unix)]
|
||||
mode: Option<u32>,
|
||||
}
|
||||
|
||||
impl DirBuilder {
|
||||
/// Creates a blank set of options.
|
||||
///
|
||||
/// The [`recursive`] option is initially set to `false`.
|
||||
/// Creates a new builder with [`recursive`] set to `false`.
|
||||
///
|
||||
/// [`recursive`]: #method.recursive
|
||||
///
|
||||
|
@ -37,24 +33,25 @@ impl DirBuilder {
|
|||
/// let builder = DirBuilder::new();
|
||||
/// ```
|
||||
pub fn new() -> DirBuilder {
|
||||
#[cfg(not(unix))]
|
||||
let builder = DirBuilder { recursive: false };
|
||||
|
||||
#[cfg(unix)]
|
||||
let builder = DirBuilder {
|
||||
recursive: false,
|
||||
mode: None,
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
let builder = DirBuilder { recursive: false };
|
||||
|
||||
builder
|
||||
}
|
||||
|
||||
/// Sets the option for recursive mode.
|
||||
///
|
||||
/// When set to `true`, this option means all parent directories should be created recursively
|
||||
/// if they don't exist. Parents are created with the same permissions as the final directory.
|
||||
/// This option, when `true`, means that all parent directories should be created recursively
|
||||
/// if they don't exist. Parents are created with the same security settings and permissions as
|
||||
/// the final directory.
|
||||
///
|
||||
/// This option is initially set to `false`.
|
||||
/// This option defaults to `false`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -73,30 +70,23 @@ impl DirBuilder {
|
|||
///
|
||||
/// It is considered an error if the directory already exists unless recursive mode is enabled.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
///
|
||||
/// * `path` already points to an existing file or directory.
|
||||
/// * The current process lacks permissions to create the directory or its missing parents.
|
||||
/// * Some other I/O error occurred.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs::DirBuilder;
|
||||
///
|
||||
/// DirBuilder::new()
|
||||
/// .recursive(true)
|
||||
/// .create("./some/directory")
|
||||
/// .create("/tmp/foo/bar/baz")
|
||||
/// .await?;
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn create<P: AsRef<Path>>(&self, path: P) -> impl Future<Output = io::Result<()>> {
|
||||
let mut builder = std::fs::DirBuilder::new();
|
||||
let mut builder = fs::DirBuilder::new();
|
||||
builder.recursive(self.recursive);
|
||||
|
||||
#[cfg(unix)]
|
||||
|
@ -107,17 +97,26 @@ impl DirBuilder {
|
|||
}
|
||||
|
||||
let path = path.as_ref().to_owned();
|
||||
async move { spawn_blocking(move || builder.create(path)).await }
|
||||
async move { blocking::spawn(async move { builder.create(path) }).await }
|
||||
}
|
||||
}
|
||||
|
||||
cfg_unix! {
|
||||
use crate::os::unix::fs::DirBuilderExt;
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "docs")] {
|
||||
use crate::os::unix::fs::DirBuilderExt;
|
||||
} else if #[cfg(unix)] {
|
||||
use std::os::unix::fs::DirBuilderExt;
|
||||
}
|
||||
}
|
||||
|
||||
impl DirBuilderExt for DirBuilder {
|
||||
fn mode(&mut self, mode: u32) -> &mut Self {
|
||||
self.mode = Some(mode);
|
||||
self
|
||||
#[cfg_attr(feature = "docs", doc(cfg(unix)))]
|
||||
cfg_if! {
|
||||
if #[cfg(any(unix, feature = "docs"))] {
|
||||
impl DirBuilderExt for DirBuilder {
|
||||
fn mode(&mut self, mode: u32) -> &mut Self {
|
||||
self.mode = Some(mode);
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,67 @@
|
|||
use std::ffi::OsString;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::fs::{FileType, Metadata};
|
||||
use cfg_if::cfg_if;
|
||||
use futures::future::{self, FutureExt, TryFutureExt};
|
||||
|
||||
use crate::future::Future;
|
||||
use crate::io;
|
||||
use crate::path::PathBuf;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::task::{blocking, Poll};
|
||||
|
||||
/// An entry in a directory.
|
||||
/// An entry inside a directory.
|
||||
///
|
||||
/// A stream of entries in a directory is returned by [`read_dir`].
|
||||
/// An instance of `DirEntry` represents an entry inside a directory on the filesystem. Each entry
|
||||
/// carriers additional information like the full path or metadata.
|
||||
///
|
||||
/// This type is an async version of [`std::fs::DirEntry`].
|
||||
///
|
||||
/// [`read_dir`]: fn.read_dir.html
|
||||
/// [`std::fs::DirEntry`]: https://doc.rust-lang.org/std/fs/struct.DirEntry.html
|
||||
pub struct DirEntry(Arc<std::fs::DirEntry>);
|
||||
#[derive(Debug)]
|
||||
pub struct DirEntry {
|
||||
/// The state of the entry.
|
||||
state: Mutex<State>,
|
||||
|
||||
/// The full path to the entry.
|
||||
path: PathBuf,
|
||||
|
||||
#[cfg(unix)]
|
||||
ino: u64,
|
||||
|
||||
/// The bare name of the entry without the leading path.
|
||||
file_name: OsString,
|
||||
}
|
||||
|
||||
/// The state of an asynchronous `DirEntry`.
|
||||
///
|
||||
/// The `DirEntry` can be either idle or busy performing an asynchronous operation.
|
||||
#[derive(Debug)]
|
||||
enum State {
|
||||
Idle(Option<fs::DirEntry>),
|
||||
Busy(blocking::JoinHandle<State>),
|
||||
}
|
||||
|
||||
impl DirEntry {
|
||||
/// Creates an asynchronous `DirEntry` from a synchronous one.
|
||||
pub(crate) fn new(inner: std::fs::DirEntry) -> DirEntry {
|
||||
DirEntry(Arc::new(inner))
|
||||
/// Creates an asynchronous `DirEntry` from a synchronous handle.
|
||||
pub(crate) fn new(inner: fs::DirEntry) -> DirEntry {
|
||||
#[cfg(unix)]
|
||||
let dir_entry = DirEntry {
|
||||
path: inner.path(),
|
||||
file_name: inner.file_name(),
|
||||
ino: inner.ino(),
|
||||
state: Mutex::new(State::Idle(Some(inner))),
|
||||
};
|
||||
|
||||
#[cfg(windows)]
|
||||
let dir_entry = DirEntry {
|
||||
path: inner.path(),
|
||||
file_name: inner.file_name(),
|
||||
state: Mutex::new(State::Idle(Some(inner))),
|
||||
};
|
||||
|
||||
dir_entry
|
||||
}
|
||||
|
||||
/// Returns the full path to this entry.
|
||||
|
@ -33,6 +74,7 @@ impl DirEntry {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -40,37 +82,25 @@ impl DirEntry {
|
|||
///
|
||||
/// let mut dir = fs::read_dir(".").await?;
|
||||
///
|
||||
/// while let Some(res) = dir.next().await {
|
||||
/// let entry = res?;
|
||||
/// while let Some(entry) = dir.next().await {
|
||||
/// let entry = entry?;
|
||||
/// println!("{:?}", entry.path());
|
||||
/// }
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn path(&self) -> PathBuf {
|
||||
self.0.path().into()
|
||||
self.path.clone()
|
||||
}
|
||||
|
||||
/// Reads the metadata for this entry.
|
||||
/// Returns the metadata for this entry.
|
||||
///
|
||||
/// This function will traverse symbolic links to read the metadata.
|
||||
///
|
||||
/// If you want to read metadata without following symbolic links, use [`symlink_metadata`]
|
||||
/// instead.
|
||||
///
|
||||
/// [`symlink_metadata`]: fn.symlink_metadata.html
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
///
|
||||
/// * This entry does not point to an existing file or directory anymore.
|
||||
/// * The current process lacks permissions to read the metadata.
|
||||
/// * Some other I/O error occurred.
|
||||
/// This function will not traverse symlinks if this entry points at a symlink.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -78,37 +108,53 @@ impl DirEntry {
|
|||
///
|
||||
/// let mut dir = fs::read_dir(".").await?;
|
||||
///
|
||||
/// while let Some(res) = dir.next().await {
|
||||
/// let entry = res?;
|
||||
/// while let Some(entry) = dir.next().await {
|
||||
/// let entry = entry?;
|
||||
/// println!("{:?}", entry.metadata().await?);
|
||||
/// }
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub async fn metadata(&self) -> io::Result<Metadata> {
|
||||
let inner = self.0.clone();
|
||||
spawn_blocking(move || inner.metadata()).await
|
||||
pub async fn metadata(&self) -> io::Result<fs::Metadata> {
|
||||
future::poll_fn(|cx| {
|
||||
let state = &mut *self.state.lock().unwrap();
|
||||
|
||||
loop {
|
||||
match state {
|
||||
State::Idle(opt) => match opt.take() {
|
||||
None => return Poll::Ready(None),
|
||||
Some(inner) => {
|
||||
let (s, r) = futures::channel::oneshot::channel();
|
||||
|
||||
// Start the operation asynchronously.
|
||||
*state = State::Busy(blocking::spawn(async move {
|
||||
let res = inner.metadata();
|
||||
let _ = s.send(res);
|
||||
State::Idle(Some(inner))
|
||||
}));
|
||||
|
||||
return Poll::Ready(Some(r));
|
||||
}
|
||||
},
|
||||
// Poll the asynchronous operation the file is currently blocked on.
|
||||
State::Busy(task) => *state = futures::ready!(Pin::new(task).poll(cx)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(|opt| opt.ok_or_else(|| io_error("invalid state")))
|
||||
.await?
|
||||
.map_err(|_| io_error("blocking task failed"))
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Reads the file type for this entry.
|
||||
/// Returns the file type for this entry.
|
||||
///
|
||||
/// This function will not traverse symbolic links if this entry points at one.
|
||||
///
|
||||
/// If you want to read metadata with following symbolic links, use [`metadata`] instead.
|
||||
///
|
||||
/// [`metadata`]: #method.metadata
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
///
|
||||
/// * This entry does not point to an existing file or directory anymore.
|
||||
/// * The current process lacks permissions to read this entry's metadata.
|
||||
/// * Some other I/O error occurred.
|
||||
/// This function will not traverse symlinks if this entry points at a symlink.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -116,16 +162,43 @@ impl DirEntry {
|
|||
///
|
||||
/// let mut dir = fs::read_dir(".").await?;
|
||||
///
|
||||
/// while let Some(res) = dir.next().await {
|
||||
/// let entry = res?;
|
||||
/// while let Some(entry) = dir.next().await {
|
||||
/// let entry = entry?;
|
||||
/// println!("{:?}", entry.file_type().await?);
|
||||
/// }
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub async fn file_type(&self) -> io::Result<FileType> {
|
||||
let inner = self.0.clone();
|
||||
spawn_blocking(move || inner.file_type()).await
|
||||
pub async fn file_type(&self) -> io::Result<fs::FileType> {
|
||||
future::poll_fn(|cx| {
|
||||
let state = &mut *self.state.lock().unwrap();
|
||||
|
||||
loop {
|
||||
match state {
|
||||
State::Idle(opt) => match opt.take() {
|
||||
None => return Poll::Ready(None),
|
||||
Some(inner) => {
|
||||
let (s, r) = futures::channel::oneshot::channel();
|
||||
|
||||
// Start the operation asynchronously.
|
||||
*state = State::Busy(blocking::spawn(async move {
|
||||
let res = inner.file_type();
|
||||
let _ = s.send(res);
|
||||
State::Idle(Some(inner))
|
||||
}));
|
||||
|
||||
return Poll::Ready(Some(r));
|
||||
}
|
||||
},
|
||||
// Poll the asynchronous operation the file is currently blocked on.
|
||||
State::Busy(task) => *state = futures::ready!(Pin::new(task).poll(cx)),
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(|opt| opt.ok_or_else(|| io_error("invalid state")))
|
||||
.await?
|
||||
.map_err(|_| io_error("blocking task failed"))
|
||||
.await?
|
||||
}
|
||||
|
||||
/// Returns the bare name of this entry without the leading path.
|
||||
|
@ -133,6 +206,7 @@ impl DirEntry {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -140,36 +214,38 @@ impl DirEntry {
|
|||
///
|
||||
/// let mut dir = fs::read_dir(".").await?;
|
||||
///
|
||||
/// while let Some(res) = dir.next().await {
|
||||
/// let entry = res?;
|
||||
/// println!("{}", entry.file_name().to_string_lossy());
|
||||
/// while let Some(entry) = dir.next().await {
|
||||
/// let entry = entry?;
|
||||
/// println!("{:?}", entry.file_name());
|
||||
/// }
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn file_name(&self) -> OsString {
|
||||
self.0.file_name()
|
||||
self.file_name.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for DirEntry {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("DirEntry").field(&self.path()).finish()
|
||||
/// Creates a custom `io::Error` with an arbitrary error type.
|
||||
fn io_error(err: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::Other, err)
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "docs")] {
|
||||
use crate::os::unix::fs::DirEntryExt;
|
||||
} else if #[cfg(unix)] {
|
||||
use std::os::unix::fs::DirEntryExt;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for DirEntry {
|
||||
fn clone(&self) -> Self {
|
||||
DirEntry(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
cfg_unix! {
|
||||
use crate::os::unix::fs::DirEntryExt;
|
||||
|
||||
impl DirEntryExt for DirEntry {
|
||||
fn ino(&self) -> u64 {
|
||||
self.0.ino()
|
||||
#[cfg_attr(feature = "docs", doc(cfg(unix)))]
|
||||
cfg_if! {
|
||||
if #[cfg(any(unix, feature = "docs"))] {
|
||||
impl DirEntryExt for DirEntry {
|
||||
fn ino(&self) -> u64 {
|
||||
self.ino
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
1189
src/fs/file.rs
1189
src/fs/file.rs
File diff suppressed because it is too large
Load diff
|
@ -1,84 +0,0 @@
|
|||
cfg_not_docs! {
|
||||
pub use std::fs::FileType;
|
||||
}
|
||||
|
||||
cfg_docs! {
|
||||
/// The type of a file or directory.
|
||||
///
|
||||
/// A file type is returned by [`Metadata::file_type`].
|
||||
///
|
||||
/// Note that file types are mutually exclusive, i.e. at most one of methods [`is_dir`],
|
||||
/// [`is_file`], and [`is_symlink`] can return `true`.
|
||||
///
|
||||
/// This type is a re-export of [`std::fs::FileType`].
|
||||
///
|
||||
/// [`Metadata::file_type`]: struct.Metadata.html#method.file_type
|
||||
/// [`is_dir`]: #method.is_dir
|
||||
/// [`is_file`]: #method.is_file
|
||||
/// [`is_symlink`]: #method.is_symlink
|
||||
/// [`std::fs::FileType`]: https://doc.rust-lang.org/std/fs/struct.FileType.html
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct FileType {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl FileType {
|
||||
/// Returns `true` if this file type represents a regular directory.
|
||||
///
|
||||
/// If this file type represents a symbolic link, this method returns `false`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let file_type = fs::metadata(".").await?.file_type();
|
||||
/// println!("{:?}", file_type.is_dir());
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn is_dir(&self) -> bool {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
|
||||
/// Returns `true` if this file type represents a regular file.
|
||||
///
|
||||
/// If this file type represents a symbolic link, this method returns `false`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let file_type = fs::metadata("a.txt").await?.file_type();
|
||||
/// println!("{:?}", file_type.is_file());
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn is_file(&self) -> bool {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
|
||||
/// Returns `true` if this file type represents a symbolic link.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let file_type = fs::metadata("a.txt").await?.file_type();
|
||||
/// println!("{:?}", file_type.is_symlink());
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn is_symlink(&self) -> bool {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Creates a hard link on the filesystem.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Creates a new hard link on the filesystem.
|
||||
///
|
||||
/// The `dst` path will be a link pointing to the `src` path. Note that operating systems often
|
||||
/// require these two paths to be located on the same filesystem.
|
||||
/// The `dst` path will be a link pointing to the `src` path. Note that systems often require these
|
||||
/// two paths to both be located on the same filesystem.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::hard_link`].
|
||||
///
|
||||
|
@ -14,14 +15,14 @@ use crate::utils::Context as _;
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `src` does not point to an existing file.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * The `src` path is not a file or doesn't exist.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -33,14 +34,5 @@ use crate::utils::Context as _;
|
|||
pub async fn hard_link<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
|
||||
let from = from.as_ref().to_owned();
|
||||
let to = to.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::hard_link(&from, &to).context(|| {
|
||||
format!(
|
||||
"could not create a hard link from `{}` to `{}`",
|
||||
from.display(),
|
||||
to.display()
|
||||
)
|
||||
})
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::hard_link(&from, &to) }).await
|
||||
}
|
||||
|
|
|
@ -1,29 +1,28 @@
|
|||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use std::fs::{self, Metadata};
|
||||
use std::path::Path;
|
||||
|
||||
/// Reads metadata for a path.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Queries the metadata for a path.
|
||||
///
|
||||
/// This function will traverse symbolic links to read metadata for the target file or directory.
|
||||
/// If you want to read metadata without following symbolic links, use [`symlink_metadata`]
|
||||
/// instead.
|
||||
/// This function will traverse symbolic links to query information about the file or directory.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::metadata`].
|
||||
///
|
||||
/// [`symlink_metadata`]: fn.symlink_metadata.html
|
||||
/// [`std::fs::metadata`]: https://doc.rust-lang.org/std/fs/fn.metadata.html
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` does not point to an existing file or directory.
|
||||
/// * The current process lacks permissions to read metadata for the path.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * `path` does not exist.
|
||||
/// * The current process lacks permissions to query metadata for `path`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -34,196 +33,5 @@ use crate::task::spawn_blocking;
|
|||
/// ```
|
||||
pub async fn metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || std::fs::metadata(path)).await
|
||||
}
|
||||
|
||||
cfg_not_docs! {
|
||||
pub use std::fs::Metadata;
|
||||
}
|
||||
|
||||
cfg_docs! {
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::fs::{FileType, Permissions};
|
||||
|
||||
/// Metadata for a file or directory.
|
||||
///
|
||||
/// Metadata is returned by [`metadata`] and [`symlink_metadata`].
|
||||
///
|
||||
/// This type is a re-export of [`std::fs::Metadata`].
|
||||
///
|
||||
/// [`metadata`]: fn.metadata.html
|
||||
/// [`symlink_metadata`]: fn.symlink_metadata.html
|
||||
/// [`is_dir`]: #method.is_dir
|
||||
/// [`is_file`]: #method.is_file
|
||||
/// [`std::fs::Metadata`]: https://doc.rust-lang.org/std/fs/struct.Metadata.html
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Metadata {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
/// Returns the file type from this metadata.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let metadata = fs::metadata("a.txt").await?;
|
||||
/// println!("{:?}", metadata.file_type());
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn file_type(&self) -> FileType {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
|
||||
/// Returns `true` if this metadata is for a regular directory.
|
||||
///
|
||||
/// If this metadata is for a symbolic link, this method returns `false`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let metadata = fs::metadata(".").await?;
|
||||
/// println!("{:?}", metadata.is_dir());
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn is_dir(&self) -> bool {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
|
||||
/// Returns `true` if this metadata is for a regular file.
|
||||
///
|
||||
/// If this metadata is for a symbolic link, this method returns `false`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let metadata = fs::metadata("a.txt").await?;
|
||||
/// println!("{:?}", metadata.is_file());
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn is_file(&self) -> bool {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
|
||||
/// Returns the file size in bytes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let metadata = fs::metadata("a.txt").await?;
|
||||
/// println!("{}", metadata.len());
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn len(&self) -> u64 {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
|
||||
/// Returns the permissions from this metadata.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let metadata = fs::metadata("a.txt").await?;
|
||||
/// println!("{:?}", metadata.permissions());
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn permissions(&self) -> Permissions {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
|
||||
/// Returns the last modification time.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This data may not be available on all platforms, in which case an error will be
|
||||
/// returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let metadata = fs::metadata("a.txt").await?;
|
||||
/// println!("{:?}", metadata.modified());
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn modified(&self) -> io::Result<SystemTime> {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
|
||||
/// Returns the last access time.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This data may not be available on all platforms, in which case an error will be
|
||||
/// returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let metadata = fs::metadata("a.txt").await?;
|
||||
/// println!("{:?}", metadata.accessed());
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn accessed(&self) -> io::Result<SystemTime> {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
|
||||
/// Returns the creation time.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This data may not be available on all platforms, in which case an error will be
|
||||
/// returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let metadata = fs::metadata("a.txt").await?;
|
||||
/// println!("{:?}", metadata.created());
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn created(&self) -> io::Result<SystemTime> {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
}
|
||||
blocking::spawn(async move { fs::metadata(path) }).await
|
||||
}
|
||||
|
|
|
@ -2,20 +2,14 @@
|
|||
//!
|
||||
//! This module is an async version of [`std::fs`].
|
||||
//!
|
||||
//! [`os::unix::fs`]: ../os/unix/fs/index.html
|
||||
//! [`os::windows::fs`]: ../os/windows/fs/index.html
|
||||
//! [`std::fs`]: https://doc.rust-lang.org/std/fs/index.html
|
||||
//!
|
||||
//! # Platform-specific extensions
|
||||
//!
|
||||
//! * Unix: use the [`os::unix::fs`] module.
|
||||
//! * Windows: use the [`os::windows::fs`] module.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! Create a new file and write some bytes to it:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # #![feature(async_await)]
|
||||
//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
//! #
|
||||
//! use async_std::fs::File;
|
||||
|
@ -30,16 +24,15 @@
|
|||
pub use dir_builder::DirBuilder;
|
||||
pub use dir_entry::DirEntry;
|
||||
pub use file::File;
|
||||
pub use file_type::FileType;
|
||||
pub use metadata::Metadata;
|
||||
pub use open_options::OpenOptions;
|
||||
pub use permissions::Permissions;
|
||||
pub use read_dir::ReadDir;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use std::fs::{FileType, Metadata, Permissions};
|
||||
|
||||
pub use canonicalize::canonicalize;
|
||||
pub use copy::copy;
|
||||
pub use create_dir::create_dir;
|
||||
pub use create_dir_all::create_dir_all;
|
||||
pub use hard_link::hard_link;
|
||||
pub use metadata::metadata;
|
||||
pub use read::read;
|
||||
|
@ -57,15 +50,12 @@ pub use write::write;
|
|||
mod canonicalize;
|
||||
mod copy;
|
||||
mod create_dir;
|
||||
mod create_dir_all;
|
||||
mod dir_builder;
|
||||
mod dir_entry;
|
||||
mod file;
|
||||
mod file_type;
|
||||
mod hard_link;
|
||||
mod metadata;
|
||||
mod open_options;
|
||||
mod permissions;
|
||||
mod read;
|
||||
mod read_dir;
|
||||
mod read_link;
|
||||
|
|
|
@ -1,35 +1,38 @@
|
|||
use std::future::Future;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::fs::File;
|
||||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
/// A builder for opening files with configurable options.
|
||||
use super::File;
|
||||
use crate::future::Future;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Options and flags which for configuring how a file is opened.
|
||||
///
|
||||
/// Files can be opened in [`read`] and/or [`write`] mode.
|
||||
/// This builder exposes the ability to configure how a [`File`] is opened and what operations are
|
||||
/// permitted on the open file. The [`File::open`] and [`File::create`] methods are aliases for
|
||||
/// commonly used options with this builder.
|
||||
///
|
||||
/// The [`append`] option opens files in a special writing mode that moves the file cursor to the
|
||||
/// end of file before every write operation.
|
||||
///
|
||||
/// It is also possible to [`truncate`] the file right after opening, to [`create`] a file if it
|
||||
/// doesn't exist yet, or to always create a new file with [`create_new`].
|
||||
/// Generally speaking, when using `OpenOptions`, you'll first call [`new`], then chain calls to
|
||||
/// methods to set each option, then call [`open`], passing the path of the file you're trying to
|
||||
/// open. This will give you a [`File`] inside that you can further operate on.
|
||||
///
|
||||
/// This type is an async version of [`std::fs::OpenOptions`].
|
||||
///
|
||||
/// [`read`]: #method.read
|
||||
/// [`write`]: #method.write
|
||||
/// [`append`]: #method.append
|
||||
/// [`truncate`]: #method.truncate
|
||||
/// [`create`]: #method.create
|
||||
/// [`create_new`]: #method.create_new
|
||||
/// [`new`]: struct.OpenOptions.html#method.new
|
||||
/// [`open`]: struct.OpenOptions.html#method.open
|
||||
/// [`File`]: struct.File.html
|
||||
/// [`File::open`]: struct.File.html#method.open
|
||||
/// [`File::create`]: struct.File.html#method.create
|
||||
/// [`std::fs::OpenOptions`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Open a file for reading:
|
||||
/// Opening a file for reading:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs::OpenOptions;
|
||||
|
@ -42,9 +45,10 @@ use crate::task::spawn_blocking;
|
|||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
///
|
||||
/// Open a file for both reading and writing, and create it if it doesn't exist yet:
|
||||
/// Opening a file for both reading and writing, creating it if it doesn't exist:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs::OpenOptions;
|
||||
|
@ -59,16 +63,17 @@ use crate::task::spawn_blocking;
|
|||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct OpenOptions(std::fs::OpenOptions);
|
||||
pub struct OpenOptions(fs::OpenOptions);
|
||||
|
||||
impl OpenOptions {
|
||||
/// Creates a blank set of options.
|
||||
/// Creates a blank new set of options.
|
||||
///
|
||||
/// All options are initially set to `false`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs::OpenOptions;
|
||||
|
@ -81,16 +86,17 @@ impl OpenOptions {
|
|||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn new() -> OpenOptions {
|
||||
OpenOptions(std::fs::OpenOptions::new())
|
||||
OpenOptions(fs::OpenOptions::new())
|
||||
}
|
||||
|
||||
/// Configures the option for read mode.
|
||||
/// Sets the option for read access.
|
||||
///
|
||||
/// When set to `true`, this option means the file will be readable after opening.
|
||||
/// This option, when `true`, will indicate that the file should be readable if opened.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs::OpenOptions;
|
||||
|
@ -107,16 +113,17 @@ impl OpenOptions {
|
|||
self
|
||||
}
|
||||
|
||||
/// Configures the option for write mode.
|
||||
/// Sets the option for write access.
|
||||
///
|
||||
/// When set to `true`, this option means the file will be writable after opening.
|
||||
/// This option, when `true`, will indicate that the file should be writable if opened.
|
||||
///
|
||||
/// If the file already exists, write calls on it will overwrite the previous contents without
|
||||
/// If the file already exists, any write calls on it will overwrite its contents, without
|
||||
/// truncating it.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs::OpenOptions;
|
||||
|
@ -133,14 +140,36 @@ impl OpenOptions {
|
|||
self
|
||||
}
|
||||
|
||||
/// Configures the option for append mode.
|
||||
/// Sets the option for append mode.
|
||||
///
|
||||
/// When set to `true`, this option means the file will be writable after opening and the file
|
||||
/// cursor will be moved to the end of file before every write operaiton.
|
||||
/// This option, when `true`, means that writes will append to a file instead of overwriting
|
||||
/// previous contents. Note that setting `.write(true).append(true)` has the same effect as
|
||||
/// setting only `.append(true)`.
|
||||
///
|
||||
/// For most filesystems, the operating system guarantees that all writes are atomic: no writes
|
||||
/// get mangled because another process writes at the same time.
|
||||
///
|
||||
/// One maybe obvious note when using append mode: make sure that all data that belongs
|
||||
/// together is written to the file in one operation. This can be done by concatenating strings
|
||||
/// before writing them, or using a buffered writer (with a buffer of adequate size), and
|
||||
/// flushing when the message is complete.
|
||||
///
|
||||
/// If a file is opened with both read and append access, beware that after opening and after
|
||||
/// every write, the position for reading may be set at the end of the file. So, before
|
||||
/// writing, save the current position by seeking with a zero offset, and restore it before the
|
||||
/// next read.
|
||||
///
|
||||
/// ## Note
|
||||
///
|
||||
/// This function doesn't create the file if it doesn't exist. Use the [`create`] method to do
|
||||
/// so.
|
||||
///
|
||||
/// [`create`]: #method.create
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs::OpenOptions;
|
||||
|
@ -157,18 +186,17 @@ impl OpenOptions {
|
|||
self
|
||||
}
|
||||
|
||||
/// Configures the option for truncating the previous file.
|
||||
/// Sets the option for truncating a previous file.
|
||||
///
|
||||
/// When set to `true`, the file will be truncated to the length of 0 bytes.
|
||||
/// If a file is successfully opened with this option set, it will truncate the file to 0
|
||||
/// length if it already exists.
|
||||
///
|
||||
/// The file must be opened in [`write`] or [`append`] mode for truncation to work.
|
||||
///
|
||||
/// [`write`]: #method.write
|
||||
/// [`append`]: #method.append
|
||||
/// The file must be opened with write access for truncation to work.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs::OpenOptions;
|
||||
|
@ -186,11 +214,11 @@ impl OpenOptions {
|
|||
self
|
||||
}
|
||||
|
||||
/// Configures the option for creating a new file if it doesn't exist.
|
||||
/// Sets the option for creating a new file.
|
||||
///
|
||||
/// When set to `true`, this option means a new file will be created if it doesn't exist.
|
||||
/// This option indicates whether a new file will be created if the file does not yet exist.
|
||||
///
|
||||
/// The file must be opened in [`write`] or [`append`] mode for file creation to work.
|
||||
/// In order for the file to be created, [`write`] or [`append`] access must be used.
|
||||
///
|
||||
/// [`write`]: #method.write
|
||||
/// [`append`]: #method.append
|
||||
|
@ -198,6 +226,7 @@ impl OpenOptions {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs::OpenOptions;
|
||||
|
@ -215,19 +244,26 @@ impl OpenOptions {
|
|||
self
|
||||
}
|
||||
|
||||
/// Configures the option for creating a new file or failing if it already exists.
|
||||
/// Sets the option to always create a new file.
|
||||
///
|
||||
/// When set to `true`, this option means a new file will be created, or the open operation
|
||||
/// will fail if the file already exists.
|
||||
/// This option indicates whether a new file will be created. No file is allowed to exist at
|
||||
/// the target location, also no (dangling) symlink.
|
||||
///
|
||||
/// The file must be opened in [`write`] or [`append`] mode for file creation to work.
|
||||
/// This option is useful because it is atomic. Otherwise, between checking whether a file
|
||||
/// exists and creating a new one, the file may have been created by another process (a TOCTOU
|
||||
/// race condition / attack).
|
||||
///
|
||||
/// [`write`]: #method.write
|
||||
/// [`append`]: #method.append
|
||||
/// If `.create_new(true)` is set, [`.create()`] and [`.truncate()`] are ignored.
|
||||
///
|
||||
/// The file must be opened with write or append access in order to create a new file.
|
||||
///
|
||||
/// [`.create()`]: #method.create
|
||||
/// [`.truncate()`]: #method.truncate
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs::OpenOptions;
|
||||
|
@ -245,70 +281,78 @@ impl OpenOptions {
|
|||
self
|
||||
}
|
||||
|
||||
/// Opens a file with the configured options.
|
||||
/// Opens a file at specified path with the configured options.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// This function will return an error under a number of different circumstances. Some of these
|
||||
/// error conditions are listed here, together with their [`ErrorKind`]. The mapping to
|
||||
/// [`ErrorKind`]s is not part of the compatibility contract of the function, especially the
|
||||
/// `Other` kind might change to more specific kinds in the future.
|
||||
///
|
||||
/// * The file does not exist and neither [`create`] nor [`create_new`] were set.
|
||||
/// * The file's parent directory does not exist.
|
||||
/// * The current process lacks permissions to open the file in the configured mode.
|
||||
/// * The file already exists and [`create_new`] was set.
|
||||
/// * Invalid combination of options was used, like [`truncate`] was set but [`write`] wasn't,
|
||||
/// or none of [`read`], [`write`], and [`append`] modes was set.
|
||||
/// * An OS-level occurred, like too many files are open or the file name is too long.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * [`NotFound`]: The specified file does not exist and neither `create` or `create_new` is
|
||||
/// set.
|
||||
/// * [`NotFound`]: One of the directory components of the file path does not exist.
|
||||
/// * [`PermissionDenied`]: The user lacks permission to get the specified access rights for
|
||||
/// the file.
|
||||
/// * [`PermissionDenied`]: The user lacks permission to open one of the directory components
|
||||
/// of the specified path.
|
||||
/// * [`AlreadyExists`]: `create_new` was specified and the file already exists.
|
||||
/// * [`InvalidInput`]: Invalid combinations of open options (truncate without write access, no
|
||||
/// access mode set, etc.).
|
||||
/// * [`Other`]: One of the directory components of the specified file path was not, in fact, a
|
||||
/// directory.
|
||||
/// * [`Other`]: Filesystem-level errors: full disk, write permission requested on a read-only
|
||||
/// file system, exceeded disk quota, too many open files, too long filename, too many
|
||||
/// symbolic links in the specified path (Unix-like systems only), etc.
|
||||
///
|
||||
/// [`read`]: #method.read
|
||||
/// [`write`]: #method.write
|
||||
/// [`append`]: #method.append
|
||||
/// [`truncate`]: #method.truncate
|
||||
/// [`create`]: #method.create
|
||||
/// [`create_new`]: #method.create_new
|
||||
/// [`ErrorKind`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html
|
||||
/// [`AlreadyExists`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.AlreadyExists
|
||||
/// [`InvalidInput`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.InvalidInput
|
||||
/// [`NotFound`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.NotFound
|
||||
/// [`Other`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.Other
|
||||
/// [`PermissionDenied`]: https://doc.rust-lang.org/std/io/enum.ErrorKind.html#variant.PermissionDenied
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs::OpenOptions;
|
||||
///
|
||||
/// let file = OpenOptions::new()
|
||||
/// .read(true)
|
||||
/// .open("a.txt")
|
||||
/// .await?;
|
||||
/// let file = OpenOptions::new().open("a.txt").await?;
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn open<P: AsRef<Path>>(&self, path: P) -> impl Future<Output = io::Result<File>> {
|
||||
let path = path.as_ref().to_owned();
|
||||
let options = self.0.clone();
|
||||
async move {
|
||||
let file = spawn_blocking(move || options.open(path)).await?;
|
||||
Ok(File::new(file, true))
|
||||
}
|
||||
async move { blocking::spawn(async move { options.open(path).map(|f| f.into()) }).await }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OpenOptions {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "docs")] {
|
||||
use crate::os::unix::fs::OpenOptionsExt;
|
||||
} else if #[cfg(unix)] {
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
}
|
||||
}
|
||||
|
||||
cfg_unix! {
|
||||
use crate::os::unix::fs::OpenOptionsExt;
|
||||
#[cfg_attr(feature = "docs", doc(cfg(unix)))]
|
||||
cfg_if! {
|
||||
if #[cfg(any(unix, feature = "docs"))] {
|
||||
impl OpenOptionsExt for OpenOptions {
|
||||
fn mode(&mut self, mode: u32) -> &mut Self {
|
||||
self.0.mode(mode);
|
||||
self
|
||||
}
|
||||
|
||||
impl OpenOptionsExt for OpenOptions {
|
||||
fn mode(&mut self, mode: u32) -> &mut Self {
|
||||
self.0.mode(mode);
|
||||
self
|
||||
}
|
||||
|
||||
fn custom_flags(&mut self, flags: i32) -> &mut Self {
|
||||
self.0.custom_flags(flags);
|
||||
self
|
||||
fn custom_flags(&mut self, flags: i32) -> &mut Self {
|
||||
self.0.custom_flags(flags);
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
cfg_not_docs! {
|
||||
pub use std::fs::Permissions;
|
||||
}
|
||||
|
||||
cfg_docs! {
|
||||
/// A set of permissions on a file or directory.
|
||||
///
|
||||
/// This type is a re-export of [`std::fs::Permissions`].
|
||||
///
|
||||
/// [`std::fs::Permissions`]: https://doc.rust-lang.org/std/fs/struct.Permissions.html
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Permissions {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl Permissions {
|
||||
/// Returns the read-only flag.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let perm = fs::metadata("a.txt").await?.permissions();
|
||||
/// println!("{:?}", perm.readonly());
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn readonly(&self) -> bool {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
|
||||
/// Configures the read-only flag.
|
||||
///
|
||||
/// [`fs::set_permissions`]: fn.set_permissions.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// let mut perm = fs::metadata("a.txt").await?.permissions();
|
||||
/// perm.set_readonly(true);
|
||||
/// fs::set_permissions("a.txt", perm).await?;
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub fn set_readonly(&mut self, readonly: bool) {
|
||||
unreachable!("this impl only appears in the rendered docs")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,32 +1,30 @@
|
|||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Reads the entire contents of a file as raw bytes.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Read the entire contents of a file into a bytes vector.
|
||||
///
|
||||
/// This is a convenience function for reading entire files. It pre-allocates a buffer based on the
|
||||
/// file size when available, so it is typically faster than manually opening a file and reading
|
||||
/// from it.
|
||||
///
|
||||
/// If you want to read the contents as a string, use [`read_to_string`] instead.
|
||||
/// file size when available, so it is generally faster than manually opening a file and reading
|
||||
/// into a `Vec`.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::read`].
|
||||
///
|
||||
/// [`read_to_string`]: fn.read_to_string.html
|
||||
/// [`std::fs::read`]: https://doc.rust-lang.org/std/fs/fn.read.html
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` does not point to an existing file.
|
||||
/// * The current process lacks permissions to read the file.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * `path` does not exist.
|
||||
/// * The current process lacks permissions to read `path`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -37,8 +35,5 @@ use crate::utils::Context as _;
|
|||
/// ```
|
||||
pub async fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::read(&path).context(|| format!("could not read file `{}`", path.display()))
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::read(path) }).await
|
||||
}
|
||||
|
|
|
@ -1,60 +1,58 @@
|
|||
use std::future::Future;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use crate::fs::DirEntry;
|
||||
use super::DirEntry;
|
||||
use crate::future::Future;
|
||||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::stream::Stream;
|
||||
use crate::task::{spawn_blocking, Context, JoinHandle, Poll};
|
||||
use crate::utils::Context as _;
|
||||
use crate::task::{blocking, Context, Poll};
|
||||
|
||||
/// Returns a stream of entries in a directory.
|
||||
/// Returns a stream over the entries within a directory.
|
||||
///
|
||||
/// The stream yields items of type [`io::Result`]`<`[`DirEntry`]`>`. Note that I/O errors can
|
||||
/// occur while reading from the stream.
|
||||
/// The stream yields items of type [`io::Result`]`<`[`DirEntry`]`>`. New errors may be encountered
|
||||
/// after a stream is initially constructed.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::read_dir`].
|
||||
///
|
||||
/// [`io::Result`]: ../io/type.Result.html
|
||||
/// [`io::Result`]: https://doc.rust-lang.org/std/io/type.Result.html
|
||||
/// [`DirEntry`]: struct.DirEntry.html
|
||||
/// [`std::fs::read_dir`]: https://doc.rust-lang.org/std/fs/fn.read_dir.html
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` does not point to an existing directory.
|
||||
/// * The current process lacks permissions to read the contents of the directory.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * `path` does not exist.
|
||||
/// * `path` does not point at a directory.
|
||||
/// * The current process lacks permissions to view the contents of `path`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
/// use async_std::prelude::*;
|
||||
///
|
||||
/// let mut entries = fs::read_dir(".").await?;
|
||||
/// let mut dir = fs::read_dir(".").await?;
|
||||
///
|
||||
/// while let Some(res) = entries.next().await {
|
||||
/// let entry = res?;
|
||||
/// println!("{}", entry.file_name().to_string_lossy());
|
||||
/// while let Some(entry) = dir.next().await {
|
||||
/// let entry = entry?;
|
||||
/// println!("{:?}", entry.file_name());
|
||||
/// }
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub async fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::read_dir(&path)
|
||||
.context(|| format!("could not read directory `{}`", path.display()))
|
||||
})
|
||||
.await
|
||||
.map(ReadDir::new)
|
||||
blocking::spawn(async move { fs::read_dir(path) })
|
||||
.await
|
||||
.map(ReadDir::new)
|
||||
}
|
||||
|
||||
/// A stream of entries in a directory.
|
||||
/// A stream over entries in a directory.
|
||||
///
|
||||
/// This stream is returned by [`read_dir`] and yields items of type
|
||||
/// [`io::Result`]`<`[`DirEntry`]`>`. Each [`DirEntry`] can then retrieve information like entry's
|
||||
|
@ -63,49 +61,75 @@ pub async fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir> {
|
|||
/// This type is an async version of [`std::fs::ReadDir`].
|
||||
///
|
||||
/// [`read_dir`]: fn.read_dir.html
|
||||
/// [`io::Result`]: ../io/type.Result.html
|
||||
/// [`io::Result`]: https://doc.rust-lang.org/std/io/type.Result.html
|
||||
/// [`DirEntry`]: struct.DirEntry.html
|
||||
/// [`std::fs::ReadDir`]: https://doc.rust-lang.org/std/fs/struct.ReadDir.html
|
||||
#[derive(Debug)]
|
||||
pub struct ReadDir(State);
|
||||
pub struct ReadDir(Mutex<State>);
|
||||
|
||||
/// The state of an asynchronous `ReadDir`.
|
||||
///
|
||||
/// The `ReadDir` can be either idle or busy performing an asynchronous operation.
|
||||
#[derive(Debug)]
|
||||
enum State {
|
||||
Idle(Option<std::fs::ReadDir>),
|
||||
Busy(JoinHandle<(std::fs::ReadDir, Option<io::Result<std::fs::DirEntry>>)>),
|
||||
Idle(Option<Inner>),
|
||||
Busy(blocking::JoinHandle<State>),
|
||||
}
|
||||
|
||||
/// Inner representation of an asynchronous `DirEntry`.
|
||||
#[derive(Debug)]
|
||||
struct Inner {
|
||||
/// The blocking handle.
|
||||
read_dir: fs::ReadDir,
|
||||
|
||||
/// The next item in the stream.
|
||||
item: Option<io::Result<DirEntry>>,
|
||||
}
|
||||
|
||||
impl ReadDir {
|
||||
/// Creates an asynchronous `ReadDir` from a synchronous handle.
|
||||
pub(crate) fn new(inner: std::fs::ReadDir) -> ReadDir {
|
||||
ReadDir(State::Idle(Some(inner)))
|
||||
pub(crate) fn new(inner: fs::ReadDir) -> ReadDir {
|
||||
ReadDir(Mutex::new(State::Idle(Some(Inner {
|
||||
read_dir: inner,
|
||||
item: None,
|
||||
}))))
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for ReadDir {
|
||||
impl futures::Stream for ReadDir {
|
||||
type Item = io::Result<DirEntry>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
loop {
|
||||
match &mut self.0 {
|
||||
State::Idle(opt) => {
|
||||
let mut inner = opt.take().unwrap();
|
||||
let state = &mut *self.0.lock().unwrap();
|
||||
|
||||
// Start the operation asynchronously.
|
||||
self.0 = State::Busy(spawn_blocking(move || {
|
||||
let next = inner.next();
|
||||
(inner, next)
|
||||
}));
|
||||
loop {
|
||||
match state {
|
||||
State::Idle(opt) => {
|
||||
let inner = match opt.as_mut() {
|
||||
None => return Poll::Ready(None),
|
||||
Some(inner) => inner,
|
||||
};
|
||||
|
||||
// Check if the operation has completed.
|
||||
if let Some(res) = inner.item.take() {
|
||||
return Poll::Ready(Some(res));
|
||||
} else {
|
||||
let mut inner = opt.take().unwrap();
|
||||
|
||||
// Start the operation asynchronously.
|
||||
*state = State::Busy(blocking::spawn(async move {
|
||||
match inner.read_dir.next() {
|
||||
None => State::Idle(None),
|
||||
Some(res) => {
|
||||
inner.item = Some(res.map(DirEntry::new));
|
||||
State::Idle(Some(inner))
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
// Poll the asynchronous operation the file is currently blocked on.
|
||||
State::Busy(task) => {
|
||||
let (inner, opt) = futures_core::ready!(Pin::new(task).poll(cx));
|
||||
self.0 = State::Idle(Some(inner));
|
||||
return Poll::Ready(opt.map(|res| res.map(DirEntry::new)));
|
||||
}
|
||||
State::Busy(task) => *state = futures::ready!(Pin::new(task).poll(cx)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::io;
|
||||
use crate::path::{Path, PathBuf};
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Reads a symbolic link and returns the path it points to.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Reads a symbolic link, returning the path it points to.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::read_link`].
|
||||
///
|
||||
|
@ -11,14 +12,15 @@ use crate::utils::Context as _;
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` does not point to an existing link.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * `path` is not a symbolic link.
|
||||
/// * `path` does not exist.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -29,10 +31,5 @@ use crate::utils::Context as _;
|
|||
/// ```
|
||||
pub async fn read_link<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::read_link(&path)
|
||||
.map(Into::into)
|
||||
.context(|| format!("could not read link `{}`", path.display()))
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::read_link(path) }).await
|
||||
}
|
||||
|
|
|
@ -1,33 +1,26 @@
|
|||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Reads the entire contents of a file as a string.
|
||||
///
|
||||
/// This is a convenience function for reading entire files. It pre-allocates a string based on the
|
||||
/// file size when available, so it is typically faster than manually opening a file and reading
|
||||
/// from it.
|
||||
///
|
||||
/// If you want to read the contents as raw bytes, use [`read`] instead.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Read the entire contents of a file into a string.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::read_to_string`].
|
||||
///
|
||||
/// [`read`]: fn.read.html
|
||||
/// [`std::fs::read_to_string`]: https://doc.rust-lang.org/std/fs/fn.read_to_string.html
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` does not point to an existing file.
|
||||
/// * The current process lacks permissions to read the file.
|
||||
/// * The contents of the file cannot be read as a UTF-8 string.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * `path` is not a file.
|
||||
/// * The current process lacks permissions to read `path`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -38,9 +31,5 @@ use crate::utils::Context as _;
|
|||
/// ```
|
||||
pub async fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::read_to_string(&path)
|
||||
.context(|| format!("could not read file `{}`", path.display()))
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::read_to_string(path) }).await
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Removes an empty directory.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Removes an existing, empty directory.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::remove_dir`].
|
||||
///
|
||||
|
@ -11,28 +12,24 @@ use crate::utils::Context as _;
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` is not an existing and empty directory.
|
||||
/// * The current process lacks permissions to remove the directory.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * `path` is not an empty directory.
|
||||
/// * The current process lacks permissions to remove directory at `path`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// fs::remove_dir("./some/directory").await?;
|
||||
/// fs::remove_dir("./some/dir").await?;
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub async fn remove_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::remove_dir(&path)
|
||||
.context(|| format!("could not remove directory `{}`", path.display()))
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::remove_dir(path) }).await
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Removes a directory and all of its contents.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Removes an directory and all of its contents.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::remove_dir_all`].
|
||||
///
|
||||
|
@ -11,28 +12,24 @@ use crate::utils::Context as _;
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` is not an existing and empty directory.
|
||||
/// * The current process lacks permissions to remove the directory.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * `path` is not a directory.
|
||||
/// * The current process lacks permissions to remove directory at `path`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// fs::remove_dir_all("./some/directory").await?;
|
||||
/// fs::remove_dir_all("./some/dir").await?;
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub async fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::remove_dir_all(&path)
|
||||
.context(|| format!("could not remove directory `{}`", path.display()))
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::remove_dir_all(path) }).await
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Removes a file.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Removes a file from the filesystem.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::remove_file`].
|
||||
///
|
||||
|
@ -11,15 +12,15 @@ use crate::utils::Context as _;
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` does not point to an existing file.
|
||||
/// * The current process lacks permissions to remove the file.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * `path` is not a file.
|
||||
/// * The current process lacks permissions to remove file at `path`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -30,9 +31,5 @@ use crate::utils::Context as _;
|
|||
/// ```
|
||||
pub async fn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::remove_file(&path)
|
||||
.context(|| format!("could not remove file `{}`", path.display()))
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::remove_file(path) }).await
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Renames a file or directory to a new location.
|
||||
///
|
||||
/// If a file or directory already exists at the target location, it will be overwritten by this
|
||||
/// operation.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Renames a file or directory to a new name, replacing the original if it already exists.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::rename`].
|
||||
///
|
||||
|
@ -14,16 +12,16 @@ use crate::utils::Context as _;
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `from` does not point to an existing file or directory.
|
||||
/// * `from` does not exist.
|
||||
/// * `from` and `to` are on different filesystems.
|
||||
/// * The current process lacks permissions to do the rename operation.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * The current process lacks permissions to rename `from` to `to`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -35,14 +33,5 @@ use crate::utils::Context as _;
|
|||
pub async fn rename<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) -> io::Result<()> {
|
||||
let from = from.as_ref().to_owned();
|
||||
let to = to.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::rename(&from, &to).context(|| {
|
||||
format!(
|
||||
"could not rename `{}` to `{}`",
|
||||
from.display(),
|
||||
to.display()
|
||||
)
|
||||
})
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::rename(&from, &to) }).await
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::fs::Permissions;
|
||||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Changes the permissions of a file or directory.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Changes the permissions on a file or directory.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::set_permissions`].
|
||||
///
|
||||
|
@ -11,15 +12,15 @@ use crate::task::spawn_blocking;
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` does not point to an existing file or directory.
|
||||
/// * The current process lacks permissions to change attributes on the file or directory.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * `path` does not exist.
|
||||
/// * The current process lacks permissions to change attributes of `path`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -30,7 +31,7 @@ use crate::task::spawn_blocking;
|
|||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub async fn set_permissions<P: AsRef<Path>>(path: P, perm: Permissions) -> io::Result<()> {
|
||||
pub async fn set_permissions<P: AsRef<Path>>(path: P, perm: fs::Permissions) -> io::Result<()> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || std::fs::set_permissions(path, perm)).await
|
||||
blocking::spawn(async move { fs::set_permissions(path, perm) }).await
|
||||
}
|
||||
|
|
|
@ -1,29 +1,26 @@
|
|||
use crate::fs::Metadata;
|
||||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use std::fs::{self, Metadata};
|
||||
use std::path::Path;
|
||||
|
||||
/// Reads metadata for a path without following symbolic links.
|
||||
///
|
||||
/// If you want to follow symbolic links before reading metadata of the target file or directory,
|
||||
/// use [`metadata`] instead.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Queries the metadata for a path without following symlinks.
|
||||
///
|
||||
/// This function is an async version of [`std::fs::symlink_metadata`].
|
||||
///
|
||||
/// [`metadata`]: fn.metadata.html
|
||||
/// [`std::fs::symlink_metadata`]: https://doc.rust-lang.org/std/fs/fn.symlink_metadata.html
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * `path` does not point to an existing file or directory.
|
||||
/// * The current process lacks permissions to read metadata for the path.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * `path` does not exist.
|
||||
/// * The current process lacks permissions to query metadata for `path`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
|
@ -34,5 +31,5 @@ use crate::task::spawn_blocking;
|
|||
/// ```
|
||||
pub async fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
|
||||
let path = path.as_ref().to_owned();
|
||||
spawn_blocking(move || std::fs::symlink_metadata(path)).await
|
||||
blocking::spawn(async move { fs::symlink_metadata(path) }).await
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use crate::io;
|
||||
use crate::path::Path;
|
||||
use crate::task::spawn_blocking;
|
||||
use crate::utils::Context as _;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
/// Writes a slice of bytes as the new contents of a file.
|
||||
use crate::io;
|
||||
use crate::task::blocking;
|
||||
|
||||
/// Writes a slice of bytes as the entire contents of a file.
|
||||
///
|
||||
/// This function will create a file if it does not exist, and will entirely replace its contents
|
||||
/// if it does.
|
||||
|
@ -14,29 +15,24 @@ use crate::utils::Context as _;
|
|||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// An error will be returned in the following situations:
|
||||
/// An error will be returned in the following situations (not an exhaustive list):
|
||||
///
|
||||
/// * The file's parent directory does not exist.
|
||||
/// * The current process lacks permissions to write to the file.
|
||||
/// * Some other I/O error occurred.
|
||||
/// * The current process lacks permissions to write into `path`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #![feature(async_await)]
|
||||
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::fs;
|
||||
///
|
||||
/// fs::write("a.txt", b"Hello world!").await?;
|
||||
/// fs::write("a.txt", b"Lorem ipsum").await?;
|
||||
/// #
|
||||
/// # Ok(()) }) }
|
||||
/// ```
|
||||
pub async fn write<P: AsRef<Path>, C: AsRef<[u8]>>(path: P, contents: C) -> io::Result<()> {
|
||||
let path = path.as_ref().to_owned();
|
||||
let contents = contents.as_ref().to_owned();
|
||||
spawn_blocking(move || {
|
||||
std::fs::write(&path, contents)
|
||||
.context(|| format!("could not write to file `{}`", path.display()))
|
||||
})
|
||||
.await
|
||||
blocking::spawn(async move { fs::write(path, contents) }).await
|
||||
}
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::task::{Context, Poll};
|
||||
use crate::utils::{timer_after, Timer};
|
||||
|
||||
pin_project! {
|
||||
#[doc(hidden)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct DelayFuture<F> {
|
||||
#[pin]
|
||||
future: F,
|
||||
#[pin]
|
||||
delay: Timer,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> DelayFuture<F> {
|
||||
pub fn new(future: F, dur: Duration) -> DelayFuture<F> {
|
||||
let delay = timer_after(dur);
|
||||
|
||||
DelayFuture { future, delay }
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: Future> Future for DelayFuture<F> {
|
||||
type Output = F::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
match this.delay.poll(cx) {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(_) => match this.future.poll(cx) {
|
||||
Poll::Ready(v) => Poll::Ready(v),
|
||||
Poll::Pending => Poll::Pending,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use crate::future::IntoFuture;
|
||||
use crate::task::{ready, Context, Poll};
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct FlattenFuture<Fut1, Fut2> {
|
||||
state: State<Fut1, Fut2>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum State<Fut1, Fut2> {
|
||||
First(Fut1),
|
||||
Second(Fut2),
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl<Fut1, Fut2> FlattenFuture<Fut1, Fut2> {
|
||||
pub(crate) fn new(future: Fut1) -> FlattenFuture<Fut1, Fut2> {
|
||||
FlattenFuture {
|
||||
state: State::First(future),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Fut1> Future for FlattenFuture<Fut1, <Fut1::Output as IntoFuture>::Future>
|
||||
where
|
||||
Fut1: Future,
|
||||
Fut1::Output: IntoFuture,
|
||||
{
|
||||
type Output = <Fut1::Output as IntoFuture>::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let Self { state } = unsafe { self.get_unchecked_mut() };
|
||||
loop {
|
||||
match state {
|
||||
State::First(fut1) => {
|
||||
let fut2 = ready!(unsafe { Pin::new_unchecked(fut1) }.poll(cx)).into_future();
|
||||
*state = State::Second(fut2);
|
||||
}
|
||||
State::Second(fut2) => {
|
||||
let v = ready!(unsafe { Pin::new_unchecked(fut2) }.poll(cx));
|
||||
*state = State::Empty;
|
||||
return Poll::Ready(v);
|
||||
}
|
||||
State::Empty => panic!("polled a completed future"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
use std::pin::Pin;
|
||||
|
||||
use crate::future::MaybeDone;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::task::{Context, Poll};
|
||||
use std::future::Future;
|
||||
|
||||
pin_project! {
|
||||
#[allow(missing_docs)]
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Join<L, R>
|
||||
where
|
||||
L: Future,
|
||||
R: Future,
|
||||
{
|
||||
#[pin] left: MaybeDone<L>,
|
||||
#[pin] right: MaybeDone<R>,
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> Join<L, R>
|
||||
where
|
||||
L: Future,
|
||||
R: Future,
|
||||
{
|
||||
pub(crate) fn new(left: L, right: R) -> Self {
|
||||
Self {
|
||||
left: MaybeDone::new(left),
|
||||
right: MaybeDone::new(right),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<L, R> Future for Join<L, R>
|
||||
where
|
||||
L: Future,
|
||||
R: Future,
|
||||
{
|
||||
type Output = (L::Output, R::Output);
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
|
||||
let mut left = this.left;
|
||||
let mut right = this.right;
|
||||
|
||||
let is_left_ready = Future::poll(Pin::new(&mut left), cx).is_ready();
|
||||
if is_left_ready && right.as_ref().output().is_some() {
|
||||
return Poll::Ready((left.take().unwrap(), right.take().unwrap()));
|
||||
}
|
||||
|
||||
let is_right_ready = Future::poll(Pin::new(&mut right), cx).is_ready();
|
||||
if is_right_ready && left.as_ref().output().is_some() {
|
||||
return Poll::Ready((left.take().unwrap(), right.take().unwrap()));
|
||||
}
|
||||
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue