Merge branch 'master' into buf-writer

This commit is contained in:
Kirill Mironov 2019-09-24 15:03:55 +03:00
commit 9509a056bd
173 changed files with 7879 additions and 3592 deletions

View file

@ -3,24 +3,66 @@ language: rust
env:
- RUSTFLAGS="-D warnings"
before_script:
- rustup component add rustfmt
# Cache the whole `~/.cargo` directory to keep `~/cargo/.crates.toml`.
cache:
directories:
- /home/travis/.cargo
# Don't cache the cargo registry because it's too big.
before_cache:
- rm -rf /home/travis/.cargo/registry
branches:
only:
- master
- staging
- trying
matrix:
fast_finish: true
include:
- rust: nightly
os: linux
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
- name: fmt
rust: nightly
os: linux
before_script: |
if ! rustup component add rustfmt; then
target=`curl https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/rustfmt`;
echo "'rustfmt' is unavailable on the toolchain 'nightly', use the toolchain 'nightly-$target' instead";
rustup toolchain install nightly-$target;
rustup default nightly-$target;
rustup component add rustfmt;
fi
script:
- cargo fmt --all -- --check
- name: docs
rust: nightly
os: linux
script:
- cargo doc --features docs unstable
- name: book
rust: nightly
os: linux
before_script:
- test -x $HOME/.cargo/bin/mdbook || ./ci/install-mdbook.sh
- cargo build # to find 'extern crate async_std' by `mdbook test`
script:
- mdbook build docs
- mdbook test -L ./target/debug/deps docs
script:
- cargo check --all --benches --bins --examples --tests
- cargo test --all
- cargo fmt --all -- --check
- if [[ -n "$BUILD_DOCS" ]]; then cargo doc --features docs; fi
- cargo check --features unstable --all --benches --bins --examples --tests
- cargo test --all --doc --features unstable

View file

@ -1,3 +1,89 @@
# Version 0.99.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]
# [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
- Initial beta release
[Unreleased]: https://github.com/async-rs/async-std/compare/v0.99.6...HEAD
[0.99.6]: https://github.com/async-rs/async-std/compare/v0.99.5...0.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

View file

@ -1,9 +1,10 @@
[package]
name = "async-std"
version = "0.99.3"
version = "0.99.6"
authors = [
"Stjepan Glavina <stjepang@gmail.com>",
"The async-std Project Developers",
"Yoshua Wuyts <yoshuawuyts@gmail.com>",
"Contributors to async-std",
]
edition = "2018"
license = "Apache-2.0/MIT"
@ -21,23 +22,36 @@ rustdoc-args = ["--cfg", "feature=\"docs\""]
[features]
docs = []
unstable = []
[dependencies]
async-macros = "1.0.0"
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"
crossbeam-deque = "0.7.1"
futures-core-preview = "0.3.0-alpha.18"
futures-io-preview = "0.3.0-alpha.18"
futures-timer = "0.4.0"
lazy_static = "1.4.0"
log = { version = "0.4.8", features = ["kv_unstable"] }
memchr = "2.2.1"
mio = "0.6.19"
mio-uds = "0.6.7"
num_cpus = "1.10.0"
num_cpus = "1.10.1"
pin-utils = "0.1.0-alpha.4"
slab = "0.4.2"
surf = "1.0.1"
kv-log-macro = "1.0.4"
[dev-dependencies]
femme = "1.1.0"
femme = "1.2.0"
surf = "1.0.2"
tempdir = "0.3.7"
# These are used by the book for examples
futures-channel-preview = "0.3.0-alpha.18"
futures-util-preview = "0.3.0-alpha.18"
[dev-dependencies.futures-preview]
version = "0.3.0-alpha.18"
features = ["std", "nightly", "async-await"]

View file

@ -1,4 +1,4 @@
# Async version of Rust's standard library
# Async version of the Rust standard library
[![Build Status](https://travis-ci.com/async-rs/async-std.svg?branch=master)](https://travis-ci.com/async-rs/async-std)
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/async-rs/async-std)
@ -20,7 +20,7 @@ are used to, but in an async version and ready for Rust's `async`/`await` syntax
## Quickstart
Add the following lines to you `Cargo.toml`:
Add the following lines to your `Cargo.toml`:
```toml
[dependencies]
@ -38,8 +38,6 @@ $ cargo add async-std
## Hello world
```rust
#![feature(async_await)]
use async_std::task;
fn main() {
@ -52,8 +50,6 @@ fn main() {
## Low-Friction Sockets with Built-In Timeouts
```rust
#![feature(async_await)]
use std::time::Duration;
use async_std::{
@ -70,9 +66,9 @@ async fn get() -> io::Result<Vec<u8>> {
let mut buf = vec![];
io::timeout(Duration::from_secs(5), async {
stream.read_to_end(&mut buf).await?
stream.read_to_end(&mut buf).await?;
Ok(buf)
})
}).await
}
fn main() {
@ -85,6 +81,25 @@ fn main() {
}
```
## Features
`async-std` is strongly commited to following semver. This means your code won't
break unless _you_ decide to upgrade.
However every now and then we come up with something that we think will work
_great_ for `async-std`, and we want to provide a sneak-peek so you can try it
out. This is what we call _"unstable"_ features. You can try out the unstable
features by enabling the `unstable` feature in your `Cargo.toml` file:
```toml
[dependencies.async-std]
version = "0.99"
features = ["unstable"]
```
Just be careful when using these features, as they may change between
versions.
## Take a look around
Clone the repo:

View file

@ -1,4 +1,4 @@
#![feature(async_await, test)]
#![feature(test)]
extern crate test;

1
bors.toml Normal file
View file

@ -0,0 +1 @@
status = ["continuous-integration/travis-ci/push"]

19
ci/install-mdbook.sh Executable file
View file

@ -0,0 +1,19 @@
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

View file

View file

@ -50,12 +50,14 @@ 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
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File.open(path)?;
```rust,edition2018
# use std::{fs::File, io, io::prelude::*};
#
fn read_file(path: &str) -> io::Result<String> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
contents
Ok(contents)
}
```
@ -64,12 +66,14 @@ 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
fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File.open(path)?;
```rust,edition2018
# use std::{fs::File, io, io::prelude::*};
#
fn read_file(path: &str) -> io::Result<String> {
let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
contents
Ok(contents)
}
```
@ -79,10 +83,11 @@ 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
```rust,edition2018
# use std::{pin::Pin, task::{Context, Poll}};
#
trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output>;
}
```
@ -105,20 +110,21 @@ 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
use async_std::fs::File;
async fn read_file(path: &str) -> Result<String, io::Error> {
let mut file = File.open(path).await?;
```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?;
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
contents
Ok(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=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>>`.)
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>>`.)
## What does `.await` do?

View file

@ -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
use async_std::fs::File;
use async_std::task;
```rust,edition2018
# extern crate async_std;
use async_std::{fs::File, io, prelude::*, task};
async fn read_file(path: &str) -> Result<String, io::Error> {
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?;
contents
Ok(contents)
}
fn main() {
@ -31,24 +31,34 @@ 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
```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)
# }
#
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
task::spawn(async { })
```rust,edition2018
# extern crate async_std;
# use async_std::task;
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`.
@ -72,7 +82,9 @@ Tasks in `async_std` are one of the core abstractions. Much like Rust's `thread`
`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
```rust,edition2018
# extern crate async_std;
# use async_std::task;
fn main() {
task::block_on(async {
// this is std::fs, which blocks
@ -91,7 +103,9 @@ 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
```rust,edition2018,should_panic
# extern crate async_std;
# use async_std::task;
fn main() {
task::block_on(async {
panic!("test");
@ -106,7 +120,10 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
While panicing a spawned task will abort:
```rust
```rust,edition2018,should_panic
# extern crate async_std;
# use async_std::task;
# use std::time::Duration;
task::spawn(async {
panic!("test");
});

View file

@ -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 introducece functionality in a backwards-compatible manner
* MINOR version when we introduce functionality in a backwards-compatible manner
* PATCH version when we make backwards-compatible bug fixes
We will provide migration documentation between major versions.

View file

@ -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
use async_std::io;
async fn echo(stream: io::TcpStream) {
```rust,edition2018
# extern crate async_std;
use async_std::{io, net::TcpStream};
async fn echo(stream: TcpStream) {
let (reader, writer) = &mut (&stream, &stream);
io::copy(reader, writer).await?;
io::copy(reader, writer).await;
}
```
```

View file

@ -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

View file

@ -2,38 +2,40 @@
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
#![feature(async_await)]
use std::net::ToSocketAddrs; // 1
```rust,edition2018
# extern crate async_std;
use async_std::{
prelude::*, // 2
task, // 3
net::TcpListener, // 4
prelude::*, // 1
task, // 2
net::{TcpListener, ToSocketAddrs}, // 3
};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; // 5
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; // 4
```
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.
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.
A single thread can run many tasks.
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.
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.
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
async fn server(addr: impl ToSocketAddrs) -> Result<()> { // 1
```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
let listener = TcpListener::bind(addr).await?; // 2
let mut incoming = listener.incoming();
while let Some(stream) = incoming.next().await { // 3
@ -43,28 +45,46 @@ async fn server(addr: impl ToSocketAddrs) -> Result<()> { // 1
}
```
1. We mark the `server` function as `async`, which allows us to use `.await` syntax inside.
1. We mark the `accept_loop` 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
let listener: std::net::TcpListener = unimplemented!();
for stream in listener.incoming() {
```rust,edition2018,should_panic
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
fn main() -> Result<()> {
let fut = server("127.0.0.1:8080");
```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");
task::block_on(fut)
}
```
@ -72,5 +92,5 @@ fn main() -> 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 handle it to the executor.
In a non-async function, a way to execute a future is to hand 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.

View file

@ -1,53 +1,59 @@
## All Together
At this point, we only need to start the broker to get a fully-functioning (in the happy case!) chat:
```rust
#![feature(async_await)]
use std::{
net::ToSocketAddrs,
sync::Arc,
collections::hash_map::{HashMap, Entry},
};
use futures::{
channel::mpsc,
SinkExt,
};
```rust,edition2018
# extern crate async_std;
# extern crate futures_channel;
# extern crate futures_util;
use async_std::{
io::BufReader,
io::{self, BufReader},
net::{TcpListener, TcpStream, ToSocketAddrs},
prelude::*,
task,
net::{TcpListener, TcpStream},
};
use futures_channel::mpsc;
use futures_util::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 main() -> Result<()> {
task::block_on(server("127.0.0.1:8080"))
// main
fn run() -> Result<()> {
task::block_on(accept_loop("127.0.0.1:8080"))
}
async fn server(addr: impl ToSocketAddrs) -> Result<()> {
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<()> {
let listener = TcpListener::bind(addr).await?;
let (broker_sender, broker_receiver) = mpsc::unbounded(); // 1
let _broker_handle = task::spawn(broker(broker_receiver));
let _broker_handle = 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(client(broker_sender.clone(), stream));
spawn_and_log_error(connection_loop(broker_sender.clone(), stream));
}
Ok(())
}
async fn client(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
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();
@ -66,7 +72,7 @@ async fn client(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.trim().to_string();
let msg: String = msg.to_string();
broker.send(Event::Message { // 4
from: name.clone(),
@ -77,7 +83,7 @@ async fn client(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
Ok(())
}
async fn client_writer(
async fn connection_writer_loop(
mut messages: Receiver<String>,
stream: Arc<TcpStream>,
) -> Result<()> {
@ -101,7 +107,7 @@ enum Event {
},
}
async fn broker(mut events: Receiver<Event>) -> Result<()> {
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 {
@ -109,7 +115,8 @@ async fn broker(mut events: Receiver<Event>) -> Result<()> {
Event::Message { from, to, msg } => {
for addr in to {
if let Some(peer) = peers.get_mut(&addr) {
peer.send(format!("from {}: {}\n", from, msg)).await?
let msg = format!("from {}: {}\n", from, msg);
peer.send(msg).await?
}
}
}
@ -119,7 +126,7 @@ async fn broker(mut events: Receiver<Event>) -> Result<()> {
Entry::Vacant(entry) => {
let (client_sender, client_receiver) = mpsc::unbounded();
entry.insert(client_sender); // 4
spawn_and_log_error(client_writer(client_receiver, stream)); // 5
spawn_and_log_error(connection_writer_loop(client_receiver, stream)); // 5
}
}
}
@ -129,8 +136,8 @@ async fn broker(mut events: Receiver<Event>) -> Result<()> {
}
```
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`.
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`.
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.

View file

@ -20,37 +20,206 @@ 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
async fn server(addr: impl ToSocketAddrs) -> Result<()> {
```rust,edition2018
# extern crate async_std;
# extern crate futures_channel;
# extern crate futures_util;
# use async_std::{
# io::{self, BufReader},
# net::{TcpListener, TcpStream, ToSocketAddrs},
# prelude::*,
# task,
# };
# use futures_channel::mpsc;
# use futures_util::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<()> {
let listener = TcpListener::bind(addr).await?;
let (broker_sender, broker_receiver) = mpsc::unbounded();
let broker = task::spawn(broker(broker_receiver));
let broker_handle = 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(client(broker_sender.clone(), stream));
spawn_and_log_error(connection_loop(broker_sender.clone(), stream));
}
drop(broker_sender); // 1
broker.await?; // 5
broker_handle.await?; // 5
Ok(())
}
```
And to the broker:
```rust
async fn broker(mut events: Receiver<Event>) -> Result<()> {
```rust,edition2018
# extern crate async_std;
# extern crate futures_channel;
# extern crate futures_util;
# use async_std::{
# io::{self, BufReader},
# net::{TcpListener, TcpStream, ToSocketAddrs},
# prelude::*,
# task,
# };
# use futures_channel::mpsc;
# use futures_util::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<()> {
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) {
peer.send(format!("from {}: {}\n", from, msg)).await?
let msg = format!("from {}: {}\n", from, msg);
peer.send(msg).await?
}
}
}
@ -60,7 +229,7 @@ async fn broker(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(client_writer(client_receiver, stream));
let handle = spawn_and_log_error(connection_writer_loop(client_receiver, stream));
writers.push(handle); // 4
}
}
@ -69,7 +238,7 @@ async fn broker(mut events: Receiver<Event>) -> Result<()> {
}
drop(peers); // 3
for writer in writers { // 4
writer.await?;
writer.await;
}
Ok(())
}

View file

@ -1,7 +1,7 @@
## Connecting Readers and Writers
So how do we make sure that messages read in `client` flow into the relevant `client_writer`?
So how do we make sure that messages read in `connection_loop` flow into the relevant `connection_writer_loop`?
We should somehow maintain an `peers: HashMap<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.
@ -10,7 +10,47 @@ We can create a dedicated broker tasks which owns the `peers` map and communicat
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
```rust,edition2018
# extern crate async_std;
# extern crate futures_channel;
# extern crate futures_util;
# use async_std::{
# net::TcpStream,
# prelude::*,
# task,
# };
# use futures_channel::mpsc;
# use futures_util::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};
#[derive(Debug)]
enum Event { // 1
NewPeer {
@ -24,7 +64,7 @@ enum Event { // 1
},
}
async fn broker(mut events: Receiver<Event>) -> Result<()> {
async fn broker_loop(mut events: Receiver<Event>) -> Result<()> {
let mut peers: HashMap<String, Sender<String>> = HashMap::new(); // 2
while let Some(event) = events.next().await {
@ -32,7 +72,8 @@ async fn broker(mut events: Receiver<Event>) -> Result<()> {
Event::Message { from, to, msg } => { // 3
for addr in to {
if let Some(peer) = peers.get_mut(&addr) {
peer.send(format!("from {}: {}\n", from, msg)).await?
let msg = format!("from {}: {}\n", from, msg);
peer.send(msg).await?
}
}
}
@ -42,7 +83,7 @@ async fn broker(mut events: Receiver<Event>) -> Result<()> {
Entry::Vacant(entry) => {
let (client_sender, client_receiver) = mpsc::unbounded();
entry.insert(client_sender); // 4
spawn_and_log_error(client_writer(client_receiver, stream)); // 5
spawn_and_log_error(connection_writer_loop(client_receiver, stream)); // 5
}
}
}

View file

@ -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,9 +15,21 @@ 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 `client`:
First, let's add a shutdown channel to the `connection_loop`:
```rust
```rust,edition2018
# extern crate async_std;
# extern crate futures_channel;
# extern crate futures_util;
# use async_std::net::TcpStream;
# use futures_channel::mpsc;
# use futures_util::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>;
#
#[derive(Debug)]
enum Void {} // 1
@ -35,17 +47,17 @@ enum Event {
},
}
async fn client(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
async fn connection_loop(mut broker: Sender<Event>, stream: Arc<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!()
}
```
@ -53,19 +65,33 @@ async fn client(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
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 `client_writer`, we now need to choose between shutdown and message channels.
In the `connection_writer_loop`, we now need to choose between shutdown and message channels.
We use the `select` macro for this purpose:
```rust
use futures::select;
use futures::FutureExt;
```rust,edition2018
# extern crate async_std;
# extern crate futures_channel;
# extern crate futures_util;
# use async_std::{net::TcpStream, prelude::*};
use futures_channel::mpsc;
use futures_util::{select, FutureExt};
# use std::sync::Arc;
async fn client_writer(
# 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
async fn connection_writer_loop(
messages: &mut Receiver<String>,
stream: Arc<TcpStream>,
mut shutdown: Receiver<Void>, // 1
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 {
@ -86,7 +112,7 @@ async fn client_writer(
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 `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.
Another problem is that between the moment we detect disconnection in `connection_writer_loop` and the moment when we actually remove the peer from the `peers` map, new messages might be pushed into the peer's channel.
To not lose these messages completely, we'll return the messages channel back to the broker.
This also allows us to establish a useful invariant that the message channel strictly outlives the peer in the `peers` map, and makes the broker itself infailable.
@ -94,27 +120,22 @@ This also allows us to establish a useful invariant that the message channel str
The final code looks like this:
```rust
#![feature(async_await)]
use std::{
net::ToSocketAddrs,
sync::Arc,
collections::hash_map::{HashMap, Entry},
};
use futures::{
channel::mpsc,
SinkExt,
FutureExt,
select,
};
```rust,edition2018
# extern crate async_std;
# extern crate futures_channel;
# extern crate futures_util;
use async_std::{
io::BufReader,
net::{TcpListener, TcpStream, ToSocketAddrs},
prelude::*,
task,
net::{TcpListener, TcpStream},
};
use futures_channel::mpsc;
use futures_util::{select, FutureExt, SinkExt};
use std::{
collections::hash_map::{Entry, HashMap},
future::Future,
sync::Arc,
};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
@ -124,27 +145,27 @@ type Receiver<T> = mpsc::UnboundedReceiver<T>;
#[derive(Debug)]
enum Void {}
fn main() -> Result<()> {
task::block_on(server("127.0.0.1:8080"))
// main
fn run() -> Result<()> {
task::block_on(accept_loop("127.0.0.1:8080"))
}
async fn server(addr: impl ToSocketAddrs) -> Result<()> {
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(broker_receiver));
let broker_handle = 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(client(broker_sender.clone(), stream));
spawn_and_log_error(connection_loop(broker_sender.clone(), stream));
}
drop(broker_sender);
broker.await;
broker_handle.await;
Ok(())
}
async fn client(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
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();
@ -179,12 +200,14 @@ async fn client(mut broker: Sender<Event>, stream: TcpStream) -> Result<()> {
Ok(())
}
async fn client_writer(
async fn connection_writer_loop(
messages: &mut Receiver<String>,
stream: Arc<TcpStream>,
mut shutdown: Receiver<Void>,
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 {
@ -214,18 +237,18 @@ enum Event {
},
}
async fn broker(mut events: Receiver<Event>) {
async fn broker_loop(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() => match event {
event = events.next().fuse() => match event {
None => break, // 2
Some(event) => event,
},
disconnect = disconnect_receiver.next() => {
disconnect = disconnect_receiver.next().fuse() => {
let (name, _pending_messages) = disconnect.unwrap(); // 3
assert!(peers.remove(&name).is_some());
continue;
@ -235,7 +258,8 @@ async fn broker(mut events: Receiver<Event>) {
Event::Message { from, to, msg } => {
for addr in to {
if let Some(peer) = peers.get_mut(&addr) {
peer.send(format!("from {}: {}\n", from, msg)).await
let msg = format!("from {}: {}\n", from, msg);
peer.send(msg).await
.unwrap() // 6
}
}
@ -248,7 +272,7 @@ async fn broker(mut events: Receiver<Event>) {
entry.insert(client_sender);
let mut disconnect_sender = disconnect_sender.clone();
spawn_and_log_error(async move {
let res = client_writer(&mut client_receiver, stream, shutdown).await;
let res = connection_writer_loop(&mut client_receiver, stream, shutdown).await;
disconnect_sender.send((name, client_receiver)).await // 4
.unwrap();
res

View file

@ -14,36 +14,29 @@ Specifically, the client should *simultaneously* read from stdin and from the so
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_util;
use async_std::{
prelude::*,
net::TcpStream,
task,
io::{stdin, BufReader},
net::{TcpStream, ToSocketAddrs},
prelude::*,
task,
};
use futures_util::{select, FutureExt};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
fn main() -> Result<()> {
task::block_on(try_main("127.0.0.1:8080"))
// main
fn run() -> Result<()> {
task::block_on(try_run("127.0.0.1:8080"))
}
async fn try_main(addr: impl ToSocketAddrs) -> Result<()> {
async fn try_run(addr: impl ToSocketAddrs) -> Result<()> {
let stream = TcpStream::connect(addr).await?;
let (reader, mut writer) = (&stream, &stream); // 1
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
let mut lines_from_server = BufReader::new(reader).lines().fuse(); // 2
let mut lines_from_stdin = BufReader::new(stdin()).lines().fuse(); // 2
loop {
select! { // 3
line = lines_from_server.next().fuse() => match line {

View file

@ -8,5 +8,4 @@ How do you distribute the messages?
In this tutorial, we will show you how to write one in `async-std`.
You can also find the tutorial in [our repository](https://github.com/async-rs/a-chat).
You can also find the tutorial in [our repository](https://github.com/async-rs/async-std/blob/master/examples/a-chat).

View file

@ -7,21 +7,29 @@ We need to:
2. interpret the first line as a login
3. parse the rest of the lines as a `login: message`
```rust
use async_std::net::TcpStream;
async fn server(addr: impl ToSocketAddrs) -> Result<()> {
```rust,edition2018
# 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 accept_loop(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(client(stream)); // 1
let _handle = task::spawn(connection_loop(stream)); // 1
}
Ok(())
}
async fn client(stream: TcpStream) -> Result<()> {
async fn connection_loop(stream: TcpStream) -> Result<()> {
let reader = BufReader::new(&stream); // 2
let mut lines = reader.lines();
@ -45,7 +53,7 @@ async fn client(stream: TcpStream) -> Result<()> {
```
1. We use `task::spawn` function to spawn an independent task for working with each client.
That is, after accepting the client the `server` loop immediately starts waiting for the next one.
That is, after accepting the client the `accept_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.
@ -59,13 +67,48 @@ async fn client(stream: TcpStream) -> Result<()> {
## Managing Errors
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!
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!
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
let handle = task::spawn(client(stream));
handle.await?
```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));
handle.await
# };
```
The `.await` waits until the client finishes, and `?` propagates the result.
@ -78,10 +121,16 @@ 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
```rust,edition2018
# extern crate async_std;
# use async_std::{
# io,
# prelude::*,
# task,
# };
fn spawn_and_log_error<F>(fut: F) -> task::JoinHandle<()>
where
F: Future<Output = Result<()>> + Send + 'static,
F: Future<Output = io::Result<()>> + Send + 'static,
{
task::spawn(async move {
if let Err(e) = fut.await {

View file

@ -1,24 +1,33 @@
## Sending Messages
Now it's time to implement the other half -- sending messages.
A most obvious way to implement sending is to give each `client` access to the write half of `TcpStream` of each other clients.
A most obvious way to implement sending is to give each `connection_loop` 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 `client_writer` task which receives messages over a channel and writes them to the socket.
So let's create a `connection_writer_loop` 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
use futures::channel::mpsc; // 1
use futures::SinkExt;
```rust,edition2018
# extern crate async_std;
# extern crate futures_channel;
# extern crate futures_util;
# use async_std::{
# net::TcpStream,
# prelude::*,
# };
use futures_channel::mpsc; // 1
use futures_util::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>; // 2
type Receiver<T> = mpsc::UnboundedReceiver<T>;
async fn client_writer(
async fn connection_writer_loop(
mut messages: Receiver<String>,
stream: Arc<TcpStream>, // 3
) -> Result<()> {
@ -32,5 +41,5 @@ async fn client_writer(
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 `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.
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.

View file

@ -45,3 +45,11 @@ $ rustup override add nightly
$ rustc --version
rustc 1.38.0-nightly (c4715198b 2019-08-05)
```
Add the following lines to `Cargo.toml`:
```toml
[dependencies]
futures-preview = { version = "0.3.0-alpha.18", features = [ "async-await", "nightly" ] }
async-std = "0.99"
```

45
examples/a-chat/client.rs Normal file
View file

@ -0,0 +1,45 @@
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(())
}

13
examples/a-chat/main.rs Normal file
View file

@ -0,0 +1,13 @@
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]")?,
}
}

184
examples/a-chat/server.rs Normal file
View file

@ -0,0 +1,184 @@
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 => Err("peer disconnected immediately")?,
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)
}
})
}

View file

@ -1,7 +1,5 @@
//! Spawns a task that says hello.
#![feature(async_await)]
use async_std::task;
async fn say_hi() {

View file

@ -1,7 +1,5 @@
//! Counts the number of lines in a file given as an argument.
#![feature(async_await)]
use std::env::args;
use async_std::fs::File;

View file

@ -1,7 +1,5 @@
//! Lists files in a directory given as an argument.
#![feature(async_await)]
use std::env::args;
use async_std::fs;
@ -15,8 +13,9 @@ fn main() -> io::Result<()> {
task::block_on(async {
let mut dir = fs::read_dir(&path).await?;
while let Some(entry) = dir.next().await {
println!("{}", entry?.file_name().to_string_lossy());
while let Some(res) = dir.next().await {
let entry = res?;
println!("{}", entry.file_name().to_string_lossy());
}
Ok(())

View file

@ -1,7 +1,5 @@
//! Prints the runtime's execution log on the standard output.
#![feature(async_await)]
use async_std::task;
fn main() {

View file

@ -1,7 +1,5 @@
//! Prints a file given as an argument to stdout.
#![feature(async_await)]
use std::env::args;
use async_std::fs::File;
@ -9,7 +7,7 @@ use async_std::io;
use async_std::prelude::*;
use async_std::task;
const LEN: usize = 4 * 1024 * 1024; // 4 Mb
const LEN: usize = 16 * 1024; // 16 Kb
fn main() -> io::Result<()> {
let path = args().nth(1).expect("missing path argument");

View file

@ -0,0 +1,26 @@
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);
});
}

View file

@ -1,7 +1,5 @@
//! Echoes lines read on stdin to stdout.
#![feature(async_await)]
use async_std::io;
use async_std::prelude::*;
use async_std::task;

View file

@ -1,7 +1,5 @@
//! 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;

View file

@ -1,7 +1,5 @@
//! Sends an HTTP request to the Rust website.
#![feature(async_await)]
use async_std::task;
fn main() -> Result<(), surf::Exception> {

View file

@ -1,7 +1,5 @@
//! Creates a task-local value.
#![feature(async_await)]
use std::cell::Cell;
use async_std::prelude::*;

View file

@ -1,7 +1,5 @@
//! Spawns a named task that prints its name.
#![feature(async_await)]
use async_std::task;
async fn print_name() {

View file

@ -12,8 +12,6 @@
//! $ cargo run --example tcp-client
//! ```
#![feature(async_await)]
use async_std::io;
use async_std::net::TcpStream;
use async_std::prelude::*;

View file

@ -6,8 +6,6 @@
//! $ nc localhost 8080
//! ```
#![feature(async_await)]
use async_std::io;
use async_std::net::{TcpListener, TcpStream};
use async_std::prelude::*;

View file

@ -12,8 +12,6 @@
//! $ cargo run --example udp-client
//! ```
#![feature(async_await)]
use async_std::io;
use async_std::net::UdpSocket;
use async_std::task;

View file

@ -6,8 +6,6 @@
//! $ nc -u localhost 8080
//! ```
#![feature(async_await)]
use async_std::io;
use async_std::net::UdpSocket;
use async_std::task;

View file

@ -1,4 +1,3 @@
use std::fs;
use std::path::{Path, PathBuf};
use crate::io;
@ -15,15 +14,15 @@ use crate::task::blocking;
///
/// # Errors
///
/// An error will be returned in the following situations (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `path` does not exist.
/// * A non-final component in path is not a directory.
/// * `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.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
@ -34,5 +33,5 @@ use crate::task::blocking;
/// ```
pub async fn canonicalize<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::canonicalize(path) }).await
blocking::spawn(async move { std::fs::canonicalize(path) }).await
}

View file

@ -1,43 +1,46 @@
use std::fs;
use std::path::Path;
use crate::io;
use crate::task::blocking;
/// Copies the contents and permissions of one file to another.
/// Copies the contents and permissions of a file to a new location.
///
/// On success, the total number of bytes copied is returned and equals the length of the `from`
/// file.
/// On success, the total number of bytes copied is returned and equals the length of the `to` file
/// after this operation.
///
/// 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 by this operation.
/// 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.
///
/// 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 (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * The `from` path is not a file.
/// * The `from` file does not exist.
/// * The current process lacks permissions to access `from` or write `to`.
/// * `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.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
///
/// let bytes_copied = fs::copy("a.txt", "b.txt").await?;
/// let num_bytes = 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();
blocking::spawn(async move { fs::copy(&from, &to) }).await
blocking::spawn(async move { std::fs::copy(&from, &to) }).await
}

View file

@ -1,36 +1,39 @@
use std::fs;
use std::path::Path;
use crate::io;
use crate::task::blocking;
/// Creates a new, empty directory.
/// 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.
///
/// 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 (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `path` already exists.
/// * A parent of the given path does not exist.
/// * The current process lacks permissions to create directory at `path`.
/// * `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.
///
/// # 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/dir").await?;
/// fs::create_dir("./some/directory").await?;
/// #
/// # Ok(()) }) }
/// ```
pub async fn create_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::create_dir(path) }).await
blocking::spawn(async move { std::fs::create_dir(path) }).await
}

View file

@ -1,10 +1,9 @@
use std::fs;
use std::path::{Path, PathBuf};
use std::path::Path;
use crate::task::blocking;
use crate::io;
use crate::task::blocking;
/// Creates a new, empty directory and all of its parents if they are missing.
/// Creates a new directory and all of its parents if they are missing.
///
/// This function is an async version of [`std::fs::create_dir_all`].
///
@ -12,24 +11,24 @@ use crate::io;
///
/// # Errors
///
/// An error will be returned in the following situations (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * The parent directories do not exists and couldn't be created.
/// * The current process lacks permissions to create directory at `path`.
/// * `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;
///
/// fs::create_dir_all("./some/dir").await?;
/// fs::create_dir_all("./some/directory").await?;
/// #
/// # Ok(()) }) }
/// ```
pub async fn create_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::create_dir_all(path) }).await
blocking::spawn(async move { std::fs::create_dir_all(path) }).await
}

View file

@ -1,4 +1,3 @@
use std::fs;
use std::path::Path;
use cfg_if::cfg_if;
@ -7,21 +6,28 @@ use crate::future::Future;
use crate::io;
use crate::task::blocking;
/// A builder for creating directories in various manners.
/// A builder for creating directories with configurable options.
///
/// For Unix-specific options, import the [`os::unix::fs::DirBuilderExt`] trait.
///
/// 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)]
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 new builder with [`recursive`] set to `false`.
/// Creates a blank set of options.
///
/// The [`recursive`] option is initially set to `false`.
///
/// [`recursive`]: #method.recursive
///
@ -33,25 +39,24 @@ 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.
///
/// 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.
/// 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 defaults to `false`.
/// This option is initially set to `false`.
///
/// # Examples
///
@ -70,23 +75,30 @@ 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("/tmp/foo/bar/baz")
/// .create("./some/directory")
/// .await?;
/// #
/// # Ok(()) }) }
/// ```
pub fn create<P: AsRef<Path>>(&self, path: P) -> impl Future<Output = io::Result<()>> {
let mut builder = fs::DirBuilder::new();
let mut builder = std::fs::DirBuilder::new();
builder.recursive(self.recursive);
#[cfg(unix)]

View file

@ -1,67 +1,28 @@
use std::ffi::OsString;
use std::fs;
use std::fmt;
use std::path::PathBuf;
use std::pin::Pin;
use std::sync::Mutex;
use std::sync::Arc;
use cfg_if::cfg_if;
use futures::future::{self, FutureExt, TryFutureExt};
use crate::future::Future;
use crate::fs::{FileType, Metadata};
use crate::io;
use crate::task::{blocking, Poll};
use crate::task::blocking;
/// An entry inside a directory.
/// An entry in a directory.
///
/// An instance of `DirEntry` represents an entry inside a directory on the filesystem. Each entry
/// carriers additional information like the full path or metadata.
/// A stream of entries in a directory is returned by [`read_dir`].
///
/// 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
#[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>),
}
pub struct DirEntry(Arc<std::fs::DirEntry>);
impl DirEntry {
/// 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
/// Creates an asynchronous `DirEntry` from a synchronous one.
pub(crate) fn new(inner: std::fs::DirEntry) -> DirEntry {
DirEntry(Arc::new(inner))
}
/// Returns the full path to this entry.
@ -74,7 +35,6 @@ impl DirEntry {
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
@ -82,25 +42,37 @@ impl DirEntry {
///
/// let mut dir = fs::read_dir(".").await?;
///
/// while let Some(entry) = dir.next().await {
/// let entry = entry?;
/// while let Some(res) = dir.next().await {
/// let entry = res?;
/// println!("{:?}", entry.path());
/// }
/// #
/// # Ok(()) }) }
/// ```
pub fn path(&self) -> PathBuf {
self.path.clone()
self.0.path()
}
/// Returns the metadata for this entry.
/// Reads the metadata for this entry.
///
/// This function will not traverse symlinks if this entry points at a symlink.
/// 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.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
@ -108,53 +80,37 @@ impl DirEntry {
///
/// let mut dir = fs::read_dir(".").await?;
///
/// while let Some(entry) = dir.next().await {
/// let entry = entry?;
/// while let Some(res) = dir.next().await {
/// let entry = res?;
/// println!("{:?}", entry.metadata().await?);
/// }
/// #
/// # Ok(()) }) }
/// ```
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?
pub async fn metadata(&self) -> io::Result<Metadata> {
let inner = self.0.clone();
blocking::spawn(async move { inner.metadata() }).await
}
/// Returns the file type for this entry.
/// Reads the file type for this entry.
///
/// This function will not traverse symlinks if this entry points at a symlink.
/// 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.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
@ -162,43 +118,16 @@ impl DirEntry {
///
/// let mut dir = fs::read_dir(".").await?;
///
/// while let Some(entry) = dir.next().await {
/// let entry = entry?;
/// while let Some(res) = dir.next().await {
/// let entry = res?;
/// println!("{:?}", entry.file_type().await?);
/// }
/// #
/// # Ok(()) }) }
/// ```
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?
pub async fn file_type(&self) -> io::Result<FileType> {
let inner = self.0.clone();
blocking::spawn(async move { inner.file_type() }).await
}
/// Returns the bare name of this entry without the leading path.
@ -206,7 +135,6 @@ impl DirEntry {
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
@ -214,21 +142,22 @@ impl DirEntry {
///
/// let mut dir = fs::read_dir(".").await?;
///
/// while let Some(entry) = dir.next().await {
/// let entry = entry?;
/// println!("{:?}", entry.file_name());
/// while let Some(res) = dir.next().await {
/// let entry = res?;
/// println!("{}", entry.file_name().to_string_lossy());
/// }
/// #
/// # Ok(()) }) }
/// ```
pub fn file_name(&self) -> OsString {
self.file_name.clone()
self.0.file_name()
}
}
/// 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)
impl fmt::Debug for DirEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("DirEntry").field(&self.path()).finish()
}
}
cfg_if! {
@ -244,7 +173,7 @@ cfg_if! {
if #[cfg(any(unix, feature = "docs"))] {
impl DirEntryExt for DirEntry {
fn ino(&self) -> u64 {
self.ino
self.0.ino()
}
}
}

File diff suppressed because it is too large Load diff

86
src/fs/file_type.rs Normal file
View file

@ -0,0 +1,86 @@
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "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 {
unimplemented!()
}
/// Returns `true` if this file type represents a regular file.
///
/// If this file type represents a symbolic link, this method returns `false`.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
///
/// let file_type = fs::metadata("a.txt").await?.file_type();
/// println!("{:?}", file_type.is_file());
/// #
/// # Ok(()) }) }
/// ```
pub fn is_file(&self) -> bool {
unimplemented!()
}
/// Returns `true` if this file type represents a symbolic link.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
///
/// let file_type = fs::metadata("a.txt").await?.file_type();
/// println!("{:?}", file_type.is_symlink());
/// #
/// # Ok(()) }) }
/// ```
pub fn is_symlink(&self) -> bool {
unimplemented!()
}
}
} else {
pub use std::fs::FileType;
}
}

View file

@ -1,13 +1,12 @@
use std::fs;
use std::path::Path;
use crate::io;
use crate::task::blocking;
/// Creates a new hard link on the filesystem.
/// Creates a hard link on the 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.
/// 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.
///
/// This function is an async version of [`std::fs::hard_link`].
///
@ -15,14 +14,14 @@ use crate::task::blocking;
///
/// # Errors
///
/// An error will be returned in the following situations (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * The `src` path is not a file or doesn't exist.
/// * `src` does not point to an existing file.
/// * 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;
@ -34,5 +33,5 @@ use crate::task::blocking;
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();
blocking::spawn(async move { fs::hard_link(&from, &to) }).await
blocking::spawn(async move { std::fs::hard_link(&from, &to) }).await
}

View file

@ -1,28 +1,32 @@
use std::fs::{self, Metadata};
use std::path::Path;
use cfg_if::cfg_if;
use crate::io;
use crate::task::blocking;
/// Queries the metadata for a path.
/// Reads metadata for a path.
///
/// This function will traverse symbolic links to query information about the file or directory.
/// 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 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 (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `path` does not exist.
/// * The current process lacks permissions to query metadata for `path`.
/// * `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.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
@ -33,5 +37,196 @@ use crate::task::blocking;
/// ```
pub async fn metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::metadata(path) }).await
blocking::spawn(async move { std::fs::metadata(path) }).await
}
cfg_if! {
if #[cfg(feature = "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 {
unimplemented!()
}
/// Returns `true` if this metadata is for a regular directory.
///
/// If this metadata is for a symbolic link, this method returns `false`.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
///
/// let metadata = fs::metadata(".").await?;
/// println!("{:?}", metadata.is_dir());
/// #
/// # Ok(()) }) }
/// ```
pub fn is_dir(&self) -> bool {
unimplemented!()
}
/// Returns `true` if this metadata is for a regular file.
///
/// If this metadata is for a symbolic link, this method returns `false`.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
///
/// let metadata = fs::metadata("a.txt").await?;
/// println!("{:?}", metadata.is_file());
/// #
/// # Ok(()) }) }
/// ```
pub fn is_file(&self) -> bool {
unimplemented!()
}
/// Returns the file size in bytes.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
///
/// let metadata = fs::metadata("a.txt").await?;
/// println!("{}", metadata.len());
/// #
/// # Ok(()) }) }
/// ```
pub fn len(&self) -> u64 {
unimplemented!()
}
/// Returns the permissions from this metadata.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
///
/// let metadata = fs::metadata("a.txt").await?;
/// println!("{:?}", metadata.permissions());
/// #
/// # Ok(()) }) }
/// ```
pub fn permissions(&self) -> Permissions {
unimplemented!()
}
/// Returns the 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> {
unimplemented!()
}
/// Returns the last access time.
///
/// # Errors
///
/// This data may not be available on all platforms, in which case an error will be
/// returned.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
///
/// let metadata = fs::metadata("a.txt").await?;
/// println!("{:?}", metadata.accessed());
/// #
/// # Ok(()) }) }
/// ```
pub fn accessed(&self) -> io::Result<SystemTime> {
unimplemented!()
}
/// Returns the creation time.
///
/// # Errors
///
/// This data may not be available on all platforms, in which case an error will be
/// returned.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
///
/// let metadata = fs::metadata("a.txt").await?;
/// println!("{:?}", metadata.created());
/// #
/// # Ok(()) }) }
/// ```
pub fn created(&self) -> io::Result<SystemTime> {
unimplemented!()
}
}
} else {
pub use std::fs::Metadata;
}
}

View file

@ -2,14 +2,18 @@
//!
//! This module is an async version of [`std::fs`].
//!
//! [`os::unix::fs`]: ../os/unix/fs/index.html
//! [`std::fs`]: https://doc.rust-lang.org/std/fs/index.html
//!
//! # Platform-specific extensions
//!
//! * Unix: use the [`os::unix::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;
@ -24,15 +28,16 @@
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;
@ -50,12 +55,15 @@ 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;

View file

@ -1,38 +1,37 @@
use std::fs;
use std::io;
use std::path::Path;
use cfg_if::cfg_if;
use super::File;
use crate::fs::File;
use crate::future::Future;
use crate::io;
use crate::task::blocking;
/// Options and flags which for configuring how a file is opened.
/// A builder for opening files with configurable options.
///
/// 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.
/// Files can be opened in [`read`] and/or [`write`] mode.
///
/// 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.
/// 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`].
///
/// This type is an async version of [`std::fs::OpenOptions`].
///
/// [`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
/// [`read`]: #method.read
/// [`write`]: #method.write
/// [`append`]: #method.append
/// [`truncate`]: #method.truncate
/// [`create`]: #method.create
/// [`create_new`]: #method.create_new
/// [`std::fs::OpenOptions`]: https://doc.rust-lang.org/std/fs/struct.OpenOptions.html
///
/// # Examples
///
/// Opening a file for reading:
/// Open 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;
@ -45,10 +44,9 @@ use crate::task::blocking;
/// # Ok(()) }) }
/// ```
///
/// Opening a file for both reading and writing, creating it if it doesn't exist:
/// Open a file for both reading and writing, and create it if it doesn't exist yet:
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::OpenOptions;
@ -63,17 +61,16 @@ use crate::task::blocking;
/// # Ok(()) }) }
/// ```
#[derive(Clone, Debug)]
pub struct OpenOptions(fs::OpenOptions);
pub struct OpenOptions(std::fs::OpenOptions);
impl OpenOptions {
/// Creates a blank new set of options.
/// Creates a blank 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;
@ -86,17 +83,16 @@ impl OpenOptions {
/// # Ok(()) }) }
/// ```
pub fn new() -> OpenOptions {
OpenOptions(fs::OpenOptions::new())
OpenOptions(std::fs::OpenOptions::new())
}
/// Sets the option for read access.
/// Configures the option for read mode.
///
/// This option, when `true`, will indicate that the file should be readable if opened.
/// When set to `true`, this option means the file will be readable after opening.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::OpenOptions;
@ -113,17 +109,16 @@ impl OpenOptions {
self
}
/// Sets the option for write access.
/// Configures the option for write mode.
///
/// This option, when `true`, will indicate that the file should be writable if opened.
/// When set to `true`, this option means the file will be writable after opening.
///
/// If the file already exists, any write calls on it will overwrite its contents, without
/// If the file already exists, write calls on it will overwrite the previous 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;
@ -140,36 +135,14 @@ impl OpenOptions {
self
}
/// Sets the option for append mode.
/// Configures the option for append mode.
///
/// 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
/// 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.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::OpenOptions;
@ -186,17 +159,18 @@ impl OpenOptions {
self
}
/// Sets the option for truncating a previous file.
/// Configures the option for truncating the previous file.
///
/// If a file is successfully opened with this option set, it will truncate the file to 0
/// length if it already exists.
/// When set to `true`, the file will be truncated to the length of 0 bytes.
///
/// The file must be opened with write access for truncation to work.
/// The file must be opened in [`write`] or [`append`] mode for truncation to work.
///
/// [`write`]: #method.write
/// [`append`]: #method.append
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::OpenOptions;
@ -214,11 +188,11 @@ impl OpenOptions {
self
}
/// Sets the option for creating a new file.
/// Configures the option for creating a new file if it doesn't exist.
///
/// This option indicates whether a new file will be created if the file does not yet exist.
/// When set to `true`, this option means a new file will be created if it doesn't exist.
///
/// In order for the file to be created, [`write`] or [`append`] access must be used.
/// The file must be opened in [`write`] or [`append`] mode for file creation to work.
///
/// [`write`]: #method.write
/// [`append`]: #method.append
@ -226,7 +200,6 @@ impl OpenOptions {
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::OpenOptions;
@ -244,26 +217,19 @@ impl OpenOptions {
self
}
/// Sets the option to always create a new file.
/// Configures the option for creating a new file or failing if it 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.
/// 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 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).
/// The file must be opened in [`write`] or [`append`] mode for file creation to work.
///
/// 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
/// [`write`]: #method.write
/// [`append`]: #method.append
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::OpenOptions;
@ -281,47 +247,39 @@ impl OpenOptions {
self
}
/// Opens a file at specified path with the configured options.
/// Opens a file with the configured options.
///
/// # Errors
///
/// 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.
/// An error will be returned in the following situations:
///
/// * [`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.
/// * 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.
///
/// [`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
/// [`read`]: #method.read
/// [`write`]: #method.write
/// [`append`]: #method.append
/// [`truncate`]: #method.truncate
/// [`create`]: #method.create
/// [`create_new`]: #method.create_new
///
/// # 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().open("a.txt").await?;
/// let file = OpenOptions::new()
/// .read(true)
/// .open("a.txt")
/// .await?;
/// #
/// # Ok(()) }) }
/// ```

58
src/fs/permissions.rs Normal file
View file

@ -0,0 +1,58 @@
use cfg_if::cfg_if;
cfg_if! {
if #[cfg(feature = "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 {
unimplemented!()
}
/// Configures the read-only flag.
///
/// [`fs::set_permissions`]: fn.set_permissions.html
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
///
/// let mut perm = fs::metadata("a.txt").await?.permissions();
/// perm.set_readonly(true);
/// fs::set_permissions("a.txt", perm).await?;
/// #
/// # Ok(()) }) }
/// ```
pub fn set_readonly(&mut self, readonly: bool) {
unimplemented!()
}
}
} else {
pub use std::fs::Permissions;
}
}

View file

@ -1,30 +1,32 @@
use std::fs;
use std::path::Path;
use crate::io;
use crate::task::blocking;
/// Read the entire contents of a file into a bytes vector.
/// Reads the entire contents of a file as raw bytes.
///
/// This is a convenience function for reading entire files. It pre-allocates a buffer based on the
/// file size when available, so it is generally faster than manually opening a file and reading
/// into a `Vec`.
/// 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.
///
/// 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 (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `path` does not exist.
/// * The current process lacks permissions to read `path`.
/// * `path` does not point to an existing file.
/// * The current process lacks permissions to read the file.
/// * 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;
@ -35,5 +37,5 @@ use crate::task::blocking;
/// ```
pub async fn read<P: AsRef<Path>>(path: P) -> io::Result<Vec<u8>> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::read(path) }).await
blocking::spawn(async move { std::fs::read(path) }).await
}

View file

@ -1,58 +1,56 @@
use std::fs;
use std::path::Path;
use std::pin::Pin;
use std::sync::Mutex;
use super::DirEntry;
use crate::fs::DirEntry;
use crate::future::Future;
use crate::io;
use crate::stream::Stream;
use crate::task::{blocking, Context, Poll};
/// Returns a stream over the entries within a directory.
/// Returns a stream of entries in a directory.
///
/// The stream yields items of type [`io::Result`]`<`[`DirEntry`]`>`. New errors may be encountered
/// after a stream is initially constructed.
/// The stream yields items of type [`io::Result`]`<`[`DirEntry`]`>`. Note that I/O errors can
/// occur while reading from the stream.
///
/// This function is an async version of [`std::fs::read_dir`].
///
/// [`io::Result`]: https://doc.rust-lang.org/std/io/type.Result.html
/// [`io::Result`]: ../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 (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `path` does not exist.
/// * `path` does not point at a directory.
/// * The current process lacks permissions to view the contents of `path`.
/// * `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.
///
/// # 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 dir = fs::read_dir(".").await?;
/// let mut entries = fs::read_dir(".").await?;
///
/// while let Some(entry) = dir.next().await {
/// let entry = entry?;
/// println!("{:?}", entry.file_name());
/// while let Some(res) = entries.next().await {
/// let entry = res?;
/// println!("{}", entry.file_name().to_string_lossy());
/// }
/// #
/// # Ok(()) }) }
/// ```
pub async fn read_dir<P: AsRef<Path>>(path: P) -> io::Result<ReadDir> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::read_dir(path) })
blocking::spawn(async move { std::fs::read_dir(path) })
.await
.map(ReadDir::new)
}
/// A stream over entries in a directory.
/// A stream of 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
@ -61,75 +59,49 @@ 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`]: https://doc.rust-lang.org/std/io/type.Result.html
/// [`io::Result`]: ../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(Mutex<State>);
pub struct ReadDir(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<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>>,
Idle(Option<std::fs::ReadDir>),
Busy(blocking::JoinHandle<(std::fs::ReadDir, Option<io::Result<std::fs::DirEntry>>)>),
}
impl ReadDir {
/// Creates an asynchronous `ReadDir` from a synchronous handle.
pub(crate) fn new(inner: fs::ReadDir) -> ReadDir {
ReadDir(Mutex::new(State::Idle(Some(Inner {
read_dir: inner,
item: None,
}))))
pub(crate) fn new(inner: std::fs::ReadDir) -> ReadDir {
ReadDir(State::Idle(Some(inner)))
}
}
impl futures::Stream for ReadDir {
impl Stream for ReadDir {
type Item = io::Result<DirEntry>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let state = &mut *self.0.lock().unwrap();
loop {
match state {
match &mut self.0 {
State::Idle(opt) => {
let inner = match opt.as_mut() {
None => return Poll::Ready(None),
Some(inner) => inner,
};
let mut inner = opt.take().unwrap();
// 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))
}
}
}));
}
// Start the operation asynchronously.
self.0 = State::Busy(blocking::spawn(async move {
let next = inner.next();
(inner, next)
}));
}
// Poll the asynchronous operation the file is currently blocked on.
State::Busy(task) => *state = futures::ready!(Pin::new(task).poll(cx)),
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)));
}
}
}
}

View file

@ -1,10 +1,9 @@
use std::fs;
use std::path::{Path, PathBuf};
use crate::io;
use crate::task::blocking;
/// Reads a symbolic link, returning the path it points to.
/// Reads a symbolic link and returns the path it points to.
///
/// This function is an async version of [`std::fs::read_link`].
///
@ -12,15 +11,14 @@ use crate::task::blocking;
///
/// # Errors
///
/// An error will be returned in the following situations (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `path` is not a symbolic link.
/// * `path` does not exist.
/// * `path` does not point to an existing link.
/// * 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;
@ -31,5 +29,5 @@ use crate::task::blocking;
/// ```
pub async fn read_link<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::read_link(path) }).await
blocking::spawn(async move { std::fs::read_link(path) }).await
}

View file

@ -1,26 +1,33 @@
use std::fs;
use std::path::Path;
use crate::io;
use crate::task::blocking;
/// Read the entire contents of a file into a string.
/// 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.
///
/// 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 (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `path` is not a file.
/// * The current process lacks permissions to read `path`.
/// * `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.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
@ -31,5 +38,5 @@ use crate::task::blocking;
/// ```
pub async fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::read_to_string(path) }).await
blocking::spawn(async move { std::fs::read_to_string(path) }).await
}

View file

@ -1,10 +1,9 @@
use std::fs;
use std::path::Path;
use crate::io;
use crate::task::blocking;
/// Removes an existing, empty directory.
/// Removes an empty directory.
///
/// This function is an async version of [`std::fs::remove_dir`].
///
@ -12,24 +11,24 @@ use crate::task::blocking;
///
/// # Errors
///
/// An error will be returned in the following situations (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `path` is not an empty directory.
/// * The current process lacks permissions to remove directory at `path`.
/// * `path` is not an existing and empty directory.
/// * The current process lacks permissions to remove the directory.
/// * 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;
///
/// fs::remove_dir("./some/dir").await?;
/// fs::remove_dir("./some/directory").await?;
/// #
/// # Ok(()) }) }
/// ```
pub async fn remove_dir<P: AsRef<Path>>(path: P) -> io::Result<()> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::remove_dir(path) }).await
blocking::spawn(async move { std::fs::remove_dir(path) }).await
}

View file

@ -1,10 +1,9 @@
use std::fs;
use std::path::Path;
use crate::io;
use crate::task::blocking;
/// Removes an directory and all of its contents.
/// Removes a directory and all of its contents.
///
/// This function is an async version of [`std::fs::remove_dir_all`].
///
@ -12,24 +11,24 @@ use crate::task::blocking;
///
/// # Errors
///
/// An error will be returned in the following situations (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `path` is not a directory.
/// * The current process lacks permissions to remove directory at `path`.
/// * `path` is not an existing and empty directory.
/// * The current process lacks permissions to remove the directory.
/// * 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;
///
/// fs::remove_dir_all("./some/dir").await?;
/// fs::remove_dir_all("./some/directory").await?;
/// #
/// # Ok(()) }) }
/// ```
pub async fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::remove_dir_all(path) }).await
blocking::spawn(async move { std::fs::remove_dir_all(path) }).await
}

View file

@ -1,10 +1,9 @@
use std::fs;
use std::path::Path;
use crate::io;
use crate::task::blocking;
/// Removes a file from the filesystem.
/// Removes a file.
///
/// This function is an async version of [`std::fs::remove_file`].
///
@ -12,15 +11,15 @@ use crate::task::blocking;
///
/// # Errors
///
/// An error will be returned in the following situations (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `path` is not a file.
/// * The current process lacks permissions to remove file at `path`.
/// * `path` does not point to an existing file.
/// * The current process lacks permissions to remove the file.
/// * 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;
@ -31,5 +30,5 @@ use crate::task::blocking;
/// ```
pub async fn remove_file<P: AsRef<Path>>(path: P) -> io::Result<()> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::remove_file(path) }).await
blocking::spawn(async move { std::fs::remove_file(path) }).await
}

View file

@ -1,10 +1,12 @@
use std::fs;
use std::path::Path;
use crate::io;
use crate::task::blocking;
/// Renames a file or directory to a new name, replacing the original if it already exists.
/// 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.
///
/// This function is an async version of [`std::fs::rename`].
///
@ -12,16 +14,16 @@ use crate::task::blocking;
///
/// # Errors
///
/// An error will be returned in the following situations (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `from` does not exist.
/// * `from` does not point to an existing file or directory.
/// * `from` and `to` are on different filesystems.
/// * The current process lacks permissions to rename `from` to `to`.
/// * The current process lacks permissions to do the rename operation.
/// * 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;
@ -33,5 +35,5 @@ use crate::task::blocking;
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();
blocking::spawn(async move { fs::rename(&from, &to) }).await
blocking::spawn(async move { std::fs::rename(&from, &to) }).await
}

View file

@ -1,10 +1,10 @@
use std::fs;
use std::path::Path;
use crate::fs::Permissions;
use crate::io;
use crate::task::blocking;
/// Changes the permissions on a file or directory.
/// Changes the permissions of a file or directory.
///
/// This function is an async version of [`std::fs::set_permissions`].
///
@ -12,15 +12,15 @@ use crate::task::blocking;
///
/// # Errors
///
/// An error will be returned in the following situations (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `path` does not exist.
/// * The current process lacks permissions to change attributes of `path`.
/// * `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.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
@ -31,7 +31,7 @@ use crate::task::blocking;
/// #
/// # Ok(()) }) }
/// ```
pub async fn set_permissions<P: AsRef<Path>>(path: P, perm: fs::Permissions) -> io::Result<()> {
pub async fn set_permissions<P: AsRef<Path>>(path: P, perm: Permissions) -> io::Result<()> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::set_permissions(path, perm) }).await
blocking::spawn(async move { std::fs::set_permissions(path, perm) }).await
}

View file

@ -1,26 +1,30 @@
use std::fs::{self, Metadata};
use std::path::Path;
use crate::fs::Metadata;
use crate::io;
use crate::task::blocking;
/// Queries the metadata for a path without following symlinks.
/// 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.
///
/// 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 (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * `path` does not exist.
/// * The current process lacks permissions to query metadata for `path`.
/// * `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.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs;
@ -31,5 +35,5 @@ use crate::task::blocking;
/// ```
pub async fn symlink_metadata<P: AsRef<Path>>(path: P) -> io::Result<Metadata> {
let path = path.as_ref().to_owned();
blocking::spawn(async move { fs::symlink_metadata(path) }).await
blocking::spawn(async move { std::fs::symlink_metadata(path) }).await
}

View file

@ -1,10 +1,9 @@
use std::fs;
use std::path::Path;
use crate::io;
use crate::task::blocking;
/// Writes a slice of bytes as the entire contents of a file.
/// Writes a slice of bytes as the new contents of a file.
///
/// This function will create a file if it does not exist, and will entirely replace its contents
/// if it does.
@ -15,24 +14,25 @@ use crate::task::blocking;
///
/// # Errors
///
/// An error will be returned in the following situations (not an exhaustive list):
/// An error will be returned in the following situations:
///
/// * The current process lacks permissions to write into `path`.
/// * The file's parent directory does not exist.
/// * The current process lacks permissions to write to the file.
/// * 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;
///
/// fs::write("a.txt", b"Lorem ipsum").await?;
/// fs::write("a.txt", b"Hello world!").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();
blocking::spawn(async move { fs::write(path, contents) }).await
blocking::spawn(async move { std::fs::write(path, contents) }).await
}

View file

@ -3,8 +3,19 @@
#[doc(inline)]
pub use std::future::Future;
use cfg_if::cfg_if;
pub use pending::pending;
pub use poll_fn::poll_fn;
pub use ready::ready;
mod pending;
mod poll_fn;
mod ready;
cfg_if! {
if #[cfg(any(feature = "unstable", feature = "docs"))] {
mod timeout;
pub use timeout::{timeout, TimeoutError};
}
}

View file

@ -1,17 +1,23 @@
use std::marker::PhantomData;
use std::pin::Pin;
use crate::future::Future;
use crate::task::{Context, Poll};
/// Never resolves to a value.
///
/// # Examples
///
/// ```
/// # #![feature(async_await)]
/// # fn main() { async_std::task::block_on(async {
/// #
/// use std::time::Duration;
///
/// use async_std::future::pending;
/// use async_std::future;
/// use async_std::io;
///
/// let dur = Duration::from_secs(1);
/// let fut = pending();
/// let fut = future::pending();
///
/// let res: io::Result<()> = io::timeout(dur, fut).await;
/// assert!(res.is_err());
@ -19,5 +25,20 @@
/// # }) }
/// ```
pub async fn pending<T>() -> T {
futures::future::pending::<T>().await
let fut = Pending {
_marker: PhantomData,
};
fut.await
}
struct Pending<T> {
_marker: PhantomData<T>,
}
impl<T> Future for Pending<T> {
type Output = T;
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<T> {
Poll::Pending
}
}

49
src/future/poll_fn.rs Normal file
View file

@ -0,0 +1,49 @@
use std::pin::Pin;
use crate::future::Future;
use crate::task::{Context, Poll};
/// Creates a new future wrapping around a function returning [`Poll`].
///
/// Polling the returned future delegates to the wrapped function.
///
/// # Examples
///
/// ```
/// # fn main() { async_std::task::block_on(async {
/// #
/// use async_std::future;
/// use async_std::task::{Context, Poll};
///
/// fn poll_greeting(_: &mut Context<'_>) -> Poll<String> {
/// Poll::Ready("hello world".to_string())
/// }
///
/// assert_eq!(future::poll_fn(poll_greeting).await, "hello world");
/// #
/// # }) }
/// ```
pub async fn poll_fn<F, T>(f: F) -> T
where
F: FnMut(&mut Context<'_>) -> Poll<T>,
{
let fut = PollFn { f };
fut.await
}
struct PollFn<F> {
f: F,
}
impl<F> Unpin for PollFn<F> {}
impl<T, F> Future for PollFn<F>
where
F: FnMut(&mut Context<'_>) -> Poll<T>,
{
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
(&mut self.f)(cx)
}
}

View file

@ -7,7 +7,6 @@
/// # Examples
///
/// ```
/// # #![feature(async_await)]
/// # fn main() { async_std::task::block_on(async {
/// #
/// use async_std::future;

85
src/future/timeout.rs Normal file
View file

@ -0,0 +1,85 @@
use std::error::Error;
use std::fmt;
use std::pin::Pin;
use std::time::Duration;
use futures_timer::Delay;
use crate::future::Future;
use crate::task::{Context, Poll};
/// Awaits a future or times out after a duration of time.
///
/// If you want to await an I/O future consider using
/// [`io::timeout`](../io/fn.timeout.html) instead.
///
/// # Examples
///
/// ```
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use std::time::Duration;
///
/// use async_std::future;
///
/// let never = future::pending::<()>();
/// let dur = Duration::from_millis(5);
/// assert!(future::timeout(dur, never).await.is_err());
/// #
/// # Ok(()) }) }
/// ```
#[cfg_attr(feature = "docs", doc(cfg(unstable)))]
#[cfg(any(feature = "unstable", feature = "docs"))]
pub async fn timeout<F, T>(dur: Duration, f: F) -> Result<T, TimeoutError>
where
F: Future<Output = T>,
{
let f = TimeoutFuture {
future: f,
delay: Delay::new(dur),
};
f.await
}
/// A future that times out after a duration of time.
#[doc(hidden)]
#[allow(missing_debug_implementations)]
struct TimeoutFuture<F> {
future: F,
delay: Delay,
}
impl<F> TimeoutFuture<F> {
pin_utils::unsafe_pinned!(future: F);
pin_utils::unsafe_pinned!(delay: Delay);
}
impl<F: Future> Future for TimeoutFuture<F> {
type Output = Result<F::Output, TimeoutError>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.as_mut().future().poll(cx) {
Poll::Ready(v) => Poll::Ready(Ok(v)),
Poll::Pending => match self.delay().poll(cx) {
Poll::Ready(_) => Poll::Ready(Err(TimeoutError { _private: () })),
Poll::Pending => Poll::Pending,
},
}
}
}
/// An error returned when a future times out.
#[cfg_attr(feature = "docs", doc(cfg(unstable)))]
#[cfg(any(feature = "unstable", feature = "docs"))]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TimeoutError {
_private: (),
}
impl Error for TimeoutError {}
impl fmt::Display for TimeoutError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"future has timed out".fmt(f)
}
}

View file

@ -1,326 +0,0 @@
use std::mem;
use std::pin::Pin;
use std::str;
use cfg_if::cfg_if;
use futures::io::AsyncBufRead;
use crate::future::Future;
use crate::io;
use crate::task::{Context, Poll};
cfg_if! {
if #[cfg(feature = "docs")] {
#[doc(hidden)]
pub struct ImplFuture<'a, T>(std::marker::PhantomData<&'a T>);
macro_rules! ret {
($a:lifetime, $f:tt, $o:ty) => (ImplFuture<$a, $o>);
}
} else {
macro_rules! ret {
($a:lifetime, $f:tt, $o:ty) => ($f<$a, Self>);
}
}
}
/// Allows reading from a buffered byte stream.
///
/// This trait is an async version of [`std::io::BufRead`].
///
/// While it is currently not possible to implement this trait directly, it gets implemented
/// automatically for all types that implement [`futures::io::AsyncBufRead`].
///
/// [`std::io::BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html
/// [`futures::io::AsyncBufRead`]:
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncBufRead.html
pub trait BufRead {
/// Reads all bytes into `buf` until the delimiter `byte` or EOF is reached.
///
/// This function will read bytes from the underlying stream until the delimiter or EOF is
/// found. Once found, all bytes up to, and including, the delimiter (if found) will be
/// appended to `buf`.
///
/// If successful, this function will return the total number of bytes read.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::io::BufReader;
/// use async_std::prelude::*;
///
/// let mut file = BufReader::new(File::open("a.txt").await?);
///
/// let mut buf = vec![0; 1024];
/// let n = file.read_until(b'\n', &mut buf).await?;
/// #
/// # Ok(()) }) }
/// ```
fn read_until<'a>(
&'a mut self,
byte: u8,
buf: &'a mut Vec<u8>,
) -> ret!('a, ReadUntilFuture, io::Result<usize>)
where
Self: Unpin,
{
ReadUntilFuture {
reader: self,
byte,
buf,
read: 0,
}
}
/// Reads all bytes and appends them into `buf` until a newline (the 0xA byte) is reached.
///
/// This function will read bytes from the underlying stream until the newline delimiter (the
/// 0xA byte) or EOF is found. Once found, all bytes up to, and including, the delimiter (if
/// found) will be appended to `buf`.
///
/// If successful, this function will return the total number of bytes read.
///
/// If this function returns `Ok(0)`, the stream has reached EOF.
///
/// # Errors
///
/// This function has the same error semantics as [`read_until`] and will also return an error
/// if the read bytes are not valid UTF-8. If an I/O error is encountered then `buf` may
/// contain some bytes already read in the event that all data read so far was valid UTF-8.
///
/// [`read_until`]: #method.read_until
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::io::BufReader;
/// use async_std::prelude::*;
///
/// let mut file = BufReader::new(File::open("a.txt").await?);
///
/// let mut buf = String::new();
/// file.read_line(&mut buf).await?;
/// #
/// # Ok(()) }) }
/// ```
fn read_line<'a>(
&'a mut self,
buf: &'a mut String,
) -> ret!('a, ReadLineFuture, io::Result<usize>)
where
Self: Unpin,
{
ReadLineFuture {
reader: self,
bytes: unsafe { mem::replace(buf.as_mut_vec(), Vec::new()) },
buf,
read: 0,
}
}
/// Returns a stream over the lines of this byte stream.
///
/// The stream returned from this function will yield instances of
/// [`io::Result`]`<`[`String`]`>`. Each string returned will *not* have a newline byte (the
/// 0xA byte) or CRLF (0xD, 0xA bytes) at the end.
///
/// [`io::Result`]: type.Result.html
/// [`String`]: https://doc.rust-lang.org/std/string/struct.String.html
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::io::BufReader;
/// use async_std::prelude::*;
///
/// let file = File::open("a.txt").await?;
/// let mut lines = BufReader::new(file).lines();
/// let mut count = 0;
///
/// for line in lines.next().await {
/// line?;
/// count += 1;
/// }
/// #
/// # Ok(()) }) }
/// ```
fn lines(self) -> Lines<Self>
where
Self: Unpin + Sized,
{
Lines {
reader: self,
buf: String::new(),
bytes: Vec::new(),
read: 0,
}
}
}
impl<T: AsyncBufRead + Unpin + ?Sized> BufRead for T {}
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadUntilFuture<'a, T: Unpin + ?Sized> {
reader: &'a mut T,
byte: u8,
buf: &'a mut Vec<u8>,
read: usize,
}
impl<T: AsyncBufRead + Unpin + ?Sized> Future for ReadUntilFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self {
reader,
byte,
buf,
read,
} = &mut *self;
read_until_internal(Pin::new(reader), cx, *byte, buf, read)
}
}
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadLineFuture<'a, T: Unpin + ?Sized> {
reader: &'a mut T,
buf: &'a mut String,
bytes: Vec<u8>,
read: usize,
}
impl<T: AsyncBufRead + Unpin + ?Sized> Future for ReadLineFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self {
reader,
buf,
bytes,
read,
} = &mut *self;
let reader = Pin::new(reader);
let ret = futures::ready!(read_until_internal(reader, cx, b'\n', bytes, read));
if str::from_utf8(&bytes).is_err() {
Poll::Ready(ret.and_then(|_| {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"stream did not contain valid UTF-8",
))
}))
} else {
debug_assert!(buf.is_empty());
debug_assert_eq!(*read, 0);
// Safety: `bytes` is a valid UTF-8 because `str::from_utf8` returned `Ok`.
mem::swap(unsafe { buf.as_mut_vec() }, bytes);
Poll::Ready(ret)
}
}
}
/// A stream of lines in a byte stream.
///
/// This stream is created by the [`lines`] method on types that implement [`BufRead`].
///
/// This type is an async version of [`std::io::Lines`].
///
/// [`lines`]: trait.BufRead.html#method.lines
/// [`BufRead`]: trait.BufRead.html
/// [`std::io::Lines`]: https://doc.rust-lang.org/nightly/std/io/struct.Lines.html
#[derive(Debug)]
pub struct Lines<R> {
reader: R,
buf: String,
bytes: Vec<u8>,
read: usize,
}
impl<R: AsyncBufRead> futures::Stream for Lines<R> {
type Item = io::Result<String>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let Self {
reader,
buf,
bytes,
read,
} = unsafe { self.get_unchecked_mut() };
let reader = unsafe { Pin::new_unchecked(reader) };
let n = futures::ready!(read_line_internal(reader, cx, buf, bytes, read))?;
if n == 0 && buf.is_empty() {
return Poll::Ready(None);
}
if buf.ends_with('\n') {
buf.pop();
if buf.ends_with('\r') {
buf.pop();
}
}
Poll::Ready(Some(Ok(mem::replace(buf, String::new()))))
}
}
pub fn read_line_internal<R: AsyncBufRead + ?Sized>(
reader: Pin<&mut R>,
cx: &mut Context<'_>,
buf: &mut String,
bytes: &mut Vec<u8>,
read: &mut usize,
) -> Poll<io::Result<usize>> {
let ret = futures::ready!(read_until_internal(reader, cx, b'\n', bytes, read));
if str::from_utf8(&bytes).is_err() {
Poll::Ready(ret.and_then(|_| {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"stream did not contain valid UTF-8",
))
}))
} else {
debug_assert!(buf.is_empty());
debug_assert_eq!(*read, 0);
// Safety: `bytes` is a valid UTF-8 because `str::from_utf8` returned `Ok`.
mem::swap(unsafe { buf.as_mut_vec() }, bytes);
Poll::Ready(ret)
}
}
pub fn read_until_internal<R: AsyncBufRead + ?Sized>(
mut reader: Pin<&mut R>,
cx: &mut Context<'_>,
byte: u8,
buf: &mut Vec<u8>,
read: &mut usize,
) -> Poll<io::Result<usize>> {
loop {
let (done, used) = {
let available = futures::ready!(reader.as_mut().poll_fill_buf(cx))?;
if let Some(i) = memchr::memchr(byte, available) {
buf.extend_from_slice(&available[..=i]);
(true, i + 1)
} else {
buf.extend_from_slice(available);
(false, available.len())
}
};
reader.as_mut().consume(used);
*read += used;
if done || used == 0 {
return Poll::Ready(Ok(mem::replace(read, 0)));
}
}
}

74
src/io/buf_read/lines.rs Normal file
View file

@ -0,0 +1,74 @@
use std::mem;
use std::pin::Pin;
use std::str;
use super::read_until_internal;
use crate::io::{self, BufRead};
use crate::stream::Stream;
use crate::task::{Context, Poll};
/// A stream of lines in a byte stream.
///
/// This stream is created by the [`lines`] method on types that implement [`BufRead`].
///
/// This type is an async version of [`std::io::Lines`].
///
/// [`lines`]: trait.BufRead.html#method.lines
/// [`BufRead`]: trait.BufRead.html
/// [`std::io::Lines`]: https://doc.rust-lang.org/std/io/struct.Lines.html
#[derive(Debug)]
pub struct Lines<R> {
pub(crate) reader: R,
pub(crate) buf: String,
pub(crate) bytes: Vec<u8>,
pub(crate) read: usize,
}
impl<R: BufRead> Stream for Lines<R> {
type Item = io::Result<String>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let Self {
reader,
buf,
bytes,
read,
} = unsafe { self.get_unchecked_mut() };
let reader = unsafe { Pin::new_unchecked(reader) };
let n = futures_core::ready!(read_line_internal(reader, cx, buf, bytes, read))?;
if n == 0 && buf.is_empty() {
return Poll::Ready(None);
}
if buf.ends_with('\n') {
buf.pop();
if buf.ends_with('\r') {
buf.pop();
}
}
Poll::Ready(Some(Ok(mem::replace(buf, String::new()))))
}
}
pub fn read_line_internal<R: BufRead + ?Sized>(
reader: Pin<&mut R>,
cx: &mut Context<'_>,
buf: &mut String,
bytes: &mut Vec<u8>,
read: &mut usize,
) -> Poll<io::Result<usize>> {
let ret = futures_core::ready!(read_until_internal(reader, cx, b'\n', bytes, read));
if str::from_utf8(&bytes).is_err() {
Poll::Ready(ret.and_then(|_| {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"stream did not contain valid UTF-8",
))
}))
} else {
debug_assert!(buf.is_empty());
debug_assert_eq!(*read, 0);
// Safety: `bytes` is a valid UTF-8 because `str::from_utf8` returned `Ok`.
mem::swap(unsafe { buf.as_mut_vec() }, bytes);
Poll::Ready(ret)
}
}

328
src/io/buf_read/mod.rs Normal file
View file

@ -0,0 +1,328 @@
mod lines;
mod read_line;
mod read_until;
pub use lines::Lines;
use read_line::ReadLineFuture;
use read_until::ReadUntilFuture;
use std::mem;
use std::pin::Pin;
use cfg_if::cfg_if;
use crate::io;
use crate::task::{Context, Poll};
cfg_if! {
if #[cfg(feature = "docs")] {
use std::ops::{Deref, DerefMut};
#[doc(hidden)]
pub struct ImplFuture<'a, T>(std::marker::PhantomData<&'a T>);
/// Allows reading from a buffered byte stream.
///
/// This trait is a re-export of [`futures::io::AsyncBufRead`] and is an async version of
/// [`std::io::BufRead`].
///
/// The [provided methods] do not really exist in the trait itself, but they become
/// available when the prelude is imported:
///
/// ```
/// # #[allow(unused_imports)]
/// use async_std::prelude::*;
/// ```
///
/// [`std::io::BufRead`]: https://doc.rust-lang.org/std/io/trait.BufRead.html
/// [`futures::io::AsyncBufRead`]:
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncBufRead.html
/// [provided methods]: #provided-methods
pub trait BufRead {
/// Returns the contents of the internal buffer, filling it with more data from the
/// inner reader if it is empty.
///
/// This function is a lower-level call. It needs to be paired with the [`consume`]
/// method to function properly. When calling this method, none of the contents will be
/// "read" in the sense that later calling `read` may return the same contents. As
/// such, [`consume`] must be called with the number of bytes that are consumed from
/// this buffer to ensure that the bytes are never returned twice.
///
/// [`consume`]: #tymethod.consume
///
/// An empty buffer returned indicates that the stream has reached EOF.
// TODO: write a proper doctest with `consume`
fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>>;
/// Tells this buffer that `amt` bytes have been consumed from the buffer, so they
/// should no longer be returned in calls to `read`.
fn consume(self: Pin<&mut Self>, amt: usize);
/// Reads all bytes into `buf` until the delimiter `byte` or EOF is reached.
///
/// This function will read bytes from the underlying stream until the delimiter or EOF
/// is found. Once found, all bytes up to, and including, the delimiter (if found) will
/// be appended to `buf`.
///
/// If successful, this function will return the total number of bytes read.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::io::BufReader;
/// use async_std::prelude::*;
///
/// let mut file = BufReader::new(File::open("a.txt").await?);
///
/// let mut buf = Vec::with_capacity(1024);
/// let n = file.read_until(b'\n', &mut buf).await?;
/// #
/// # Ok(()) }) }
/// ```
///
/// Multiple successful calls to `read_until` append all bytes up to and including to
/// `buf`:
/// ```
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::io::BufReader;
/// use async_std::prelude::*;
///
/// let from: &[u8] = b"append\nexample\n";
/// let mut reader = BufReader::new(from);
/// let mut buf = vec![];
///
/// let mut size = reader.read_until(b'\n', &mut buf).await?;
/// assert_eq!(size, 7);
/// assert_eq!(buf, b"append\n");
///
/// size += reader.read_until(b'\n', &mut buf).await?;
/// assert_eq!(size, from.len());
///
/// assert_eq!(buf, from);
/// #
/// # Ok(()) }) }
/// ```
fn read_until<'a>(
&'a mut self,
byte: u8,
buf: &'a mut Vec<u8>,
) -> ImplFuture<'a, io::Result<usize>>
where
Self: Unpin,
{
unreachable!()
}
/// Reads all bytes and appends them into `buf` until a newline (the 0xA byte) is
/// reached.
///
/// This function will read bytes from the underlying stream until the newline
/// delimiter (the 0xA byte) or EOF is found. Once found, all bytes up to, and
/// including, the delimiter (if found) will be appended to `buf`.
///
/// If successful, this function will return the total number of bytes read.
///
/// If this function returns `Ok(0)`, the stream has reached EOF.
///
/// # Errors
///
/// This function has the same error semantics as [`read_until`] and will also return
/// an error if the read bytes are not valid UTF-8. If an I/O error is encountered then
/// `buf` may contain some bytes already read in the event that all data read so far
/// was valid UTF-8.
///
/// [`read_until`]: #method.read_until
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::io::BufReader;
/// use async_std::prelude::*;
///
/// let mut file = BufReader::new(File::open("a.txt").await?);
///
/// let mut buf = String::new();
/// file.read_line(&mut buf).await?;
/// #
/// # Ok(()) }) }
/// ```
fn read_line<'a>(
&'a mut self,
buf: &'a mut String,
) -> ImplFuture<'a, io::Result<usize>>
where
Self: Unpin,
{
unreachable!()
}
/// Returns a stream over the lines of this byte stream.
///
/// The stream returned from this function will yield instances of
/// [`io::Result`]`<`[`String`]`>`. Each string returned will *not* have a newline byte
/// (the 0xA byte) or CRLF (0xD, 0xA bytes) at the end.
///
/// [`io::Result`]: type.Result.html
/// [`String`]: https://doc.rust-lang.org/std/string/struct.String.html
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::io::BufReader;
/// use async_std::prelude::*;
///
/// let file = File::open("a.txt").await?;
/// let mut lines = BufReader::new(file).lines();
/// let mut count = 0;
///
/// while let Some(line) = lines.next().await {
/// line?;
/// count += 1;
/// }
/// #
/// # Ok(()) }) }
/// ```
fn lines(self) -> Lines<Self>
where
Self: Unpin + Sized,
{
unreachable!()
}
}
impl<T: BufRead + Unpin + ?Sized> BufRead for Box<T> {
fn poll_fill_buf(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<&[u8]>> {
unreachable!()
}
fn consume(self: Pin<&mut Self>, amt: usize) {
unreachable!()
}
}
impl<T: BufRead + Unpin + ?Sized> BufRead for &mut T {
fn poll_fill_buf(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<&[u8]>> {
unreachable!()
}
fn consume(self: Pin<&mut Self>, amt: usize) {
unreachable!()
}
}
impl<P> BufRead for Pin<P>
where
P: DerefMut + Unpin,
<P as Deref>::Target: BufRead,
{
fn poll_fill_buf(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<&[u8]>> {
unreachable!()
}
fn consume(self: Pin<&mut Self>, amt: usize) {
unreachable!()
}
}
impl BufRead for &[u8] {
fn poll_fill_buf(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<io::Result<&[u8]>> {
unreachable!()
}
fn consume(self: Pin<&mut Self>, amt: usize) {
unreachable!()
}
}
} else {
pub use futures_io::AsyncBufRead as BufRead;
}
}
#[doc(hidden)]
pub trait BufReadExt: futures_io::AsyncBufRead {
fn read_until<'a>(&'a mut self, byte: u8, buf: &'a mut Vec<u8>) -> ReadUntilFuture<'a, Self>
where
Self: Unpin,
{
ReadUntilFuture {
reader: self,
byte,
buf,
read: 0,
}
}
fn read_line<'a>(&'a mut self, buf: &'a mut String) -> ReadLineFuture<'a, Self>
where
Self: Unpin,
{
ReadLineFuture {
reader: self,
bytes: unsafe { mem::replace(buf.as_mut_vec(), Vec::new()) },
buf,
read: 0,
}
}
fn lines(self) -> Lines<Self>
where
Self: Unpin + Sized,
{
Lines {
reader: self,
buf: String::new(),
bytes: Vec::new(),
read: 0,
}
}
}
impl<T: futures_io::AsyncBufRead + ?Sized> BufReadExt for T {}
pub fn read_until_internal<R: BufReadExt + ?Sized>(
mut reader: Pin<&mut R>,
cx: &mut Context<'_>,
byte: u8,
buf: &mut Vec<u8>,
read: &mut usize,
) -> Poll<io::Result<usize>> {
loop {
let (done, used) = {
let available = futures_core::ready!(reader.as_mut().poll_fill_buf(cx))?;
if let Some(i) = memchr::memchr(byte, available) {
buf.extend_from_slice(&available[..=i]);
(true, i + 1)
} else {
buf.extend_from_slice(available);
(false, available.len())
}
};
reader.as_mut().consume(used);
*read += used;
if done || used == 0 {
return Poll::Ready(Ok(mem::replace(read, 0)));
}
}
}

View file

@ -0,0 +1,47 @@
use std::mem;
use std::pin::Pin;
use std::str;
use super::read_until_internal;
use crate::future::Future;
use crate::io::{self, BufRead};
use crate::task::{Context, Poll};
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadLineFuture<'a, T: Unpin + ?Sized> {
pub(crate) reader: &'a mut T,
pub(crate) buf: &'a mut String,
pub(crate) bytes: Vec<u8>,
pub(crate) read: usize,
}
impl<T: BufRead + Unpin + ?Sized> Future for ReadLineFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self {
reader,
buf,
bytes,
read,
} = &mut *self;
let reader = Pin::new(reader);
let ret = futures_core::ready!(read_until_internal(reader, cx, b'\n', bytes, read));
if str::from_utf8(&bytes).is_err() {
Poll::Ready(ret.and_then(|_| {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"stream did not contain valid UTF-8",
))
}))
} else {
debug_assert!(buf.is_empty());
debug_assert_eq!(*read, 0);
// Safety: `bytes` is a valid UTF-8 because `str::from_utf8` returned `Ok`.
mem::swap(unsafe { buf.as_mut_vec() }, bytes);
Poll::Ready(ret)
}
}
}

View file

@ -0,0 +1,29 @@
use std::pin::Pin;
use super::read_until_internal;
use crate::future::Future;
use crate::io::{self, BufRead};
use crate::task::{Context, Poll};
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadUntilFuture<'a, T: Unpin + ?Sized> {
pub(crate) reader: &'a mut T,
pub(crate) byte: u8,
pub(crate) buf: &'a mut Vec<u8>,
pub(crate) read: usize,
}
impl<T: BufRead + Unpin + ?Sized> Future for ReadUntilFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self {
reader,
byte,
buf,
read,
} = &mut *self;
read_until_internal(Pin::new(reader), cx, *byte, buf, read)
}
}

View file

@ -2,9 +2,7 @@ use std::io::{IoSliceMut, Read as _};
use std::pin::Pin;
use std::{cmp, fmt};
use futures::io::{AsyncBufRead, AsyncRead, AsyncSeek, Initializer};
use crate::io::{self, SeekFrom};
use crate::io::{self, BufRead, Read, Seek, SeekFrom};
use crate::task::{Context, Poll};
const DEFAULT_CAPACITY: usize = 8 * 1024;
@ -31,17 +29,16 @@ const DEFAULT_CAPACITY: usize = 8 * 1024;
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::io::BufReader;
/// use async_std::prelude::*;
///
/// let mut f = BufReader::new(File::open("a.txt").await?);
/// let mut file = BufReader::new(File::open("a.txt").await?);
///
/// let mut line = String::new();
/// f.read_line(&mut line).await?;
/// file.read_line(&mut line).await?;
/// #
/// # Ok(()) }) }
/// ```
@ -52,7 +49,7 @@ pub struct BufReader<R> {
cap: usize,
}
impl<R: AsyncRead> BufReader<R> {
impl<R: io::Read> BufReader<R> {
/// Creates a buffered reader with default buffer capacity.
///
/// The default capacity is currently 8 KB, but may change in the future.
@ -60,7 +57,6 @@ impl<R: AsyncRead> BufReader<R> {
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
@ -79,7 +75,6 @@ impl<R: AsyncRead> BufReader<R> {
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
@ -90,17 +85,11 @@ impl<R: AsyncRead> BufReader<R> {
/// # Ok(()) }) }
/// ```
pub fn with_capacity(capacity: usize, inner: R) -> BufReader<R> {
unsafe {
let mut buffer = Vec::with_capacity(capacity);
buffer.set_len(capacity);
inner.initializer().initialize(&mut buffer);
BufReader {
inner,
buf: buffer.into_boxed_slice(),
pos: 0,
cap: 0,
}
BufReader {
inner,
buf: vec![0; capacity].into_boxed_slice(),
pos: 0,
cap: 0,
}
}
}
@ -117,7 +106,6 @@ impl<R> BufReader<R> {
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
@ -139,14 +127,13 @@ impl<R> BufReader<R> {
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::io::BufReader;
///
/// let mut f = BufReader::new(File::open("a.txt").await?);
/// let inner = f.get_mut();
/// let mut file = BufReader::new(File::open("a.txt").await?);
/// let inner = file.get_mut();
/// #
/// # Ok(()) }) }
/// ```
@ -161,7 +148,6 @@ impl<R> BufReader<R> {
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
@ -183,7 +169,6 @@ impl<R> BufReader<R> {
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
@ -206,7 +191,7 @@ impl<R> BufReader<R> {
}
}
impl<R: AsyncRead> AsyncRead for BufReader<R> {
impl<R: Read> Read for BufReader<R> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
@ -216,11 +201,11 @@ impl<R: AsyncRead> AsyncRead for BufReader<R> {
// (larger than our internal buffer), bypass our internal buffer
// entirely.
if self.pos == self.cap && buf.len() >= self.buf.len() {
let res = futures::ready!(self.as_mut().inner().poll_read(cx, buf));
let res = futures_core::ready!(self.as_mut().inner().poll_read(cx, buf));
self.discard_buffer();
return Poll::Ready(res);
}
let mut rem = futures::ready!(self.as_mut().poll_fill_buf(cx))?;
let mut rem = futures_core::ready!(self.as_mut().poll_fill_buf(cx))?;
let nread = rem.read(buf)?;
self.consume(nread);
Poll::Ready(Ok(nread))
@ -233,23 +218,18 @@ impl<R: AsyncRead> AsyncRead for BufReader<R> {
) -> Poll<io::Result<usize>> {
let total_len = bufs.iter().map(|b| b.len()).sum::<usize>();
if self.pos == self.cap && total_len >= self.buf.len() {
let res = futures::ready!(self.as_mut().inner().poll_read_vectored(cx, bufs));
let res = futures_core::ready!(self.as_mut().inner().poll_read_vectored(cx, bufs));
self.discard_buffer();
return Poll::Ready(res);
}
let mut rem = futures::ready!(self.as_mut().poll_fill_buf(cx))?;
let mut rem = futures_core::ready!(self.as_mut().poll_fill_buf(cx))?;
let nread = rem.read_vectored(bufs)?;
self.consume(nread);
Poll::Ready(Ok(nread))
}
// we can't skip unconditionally because of the large buffer case in read.
unsafe fn initializer(&self) -> Initializer {
self.inner.initializer()
}
}
impl<R: AsyncRead> AsyncBufRead for BufReader<R> {
impl<R: Read> BufRead for BufReader<R> {
fn poll_fill_buf<'a>(
self: Pin<&'a mut Self>,
cx: &mut Context<'_>,
@ -268,7 +248,7 @@ impl<R: AsyncRead> AsyncBufRead for BufReader<R> {
// to tell the compiler that the pos..cap slice is always valid.
if *pos >= *cap {
debug_assert!(*pos == *cap);
*cap = futures::ready!(inner.as_mut().poll_read(cx, buf))?;
*cap = futures_core::ready!(inner.as_mut().poll_read(cx, buf))?;
*pos = 0;
}
Poll::Ready(Ok(&buf[*pos..*cap]))
@ -279,7 +259,7 @@ impl<R: AsyncRead> AsyncBufRead for BufReader<R> {
}
}
impl<R: AsyncRead + fmt::Debug> fmt::Debug for BufReader<R> {
impl<R: io::Read + fmt::Debug> fmt::Debug for BufReader<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BufReader")
.field("reader", &self.inner)
@ -291,7 +271,7 @@ impl<R: AsyncRead + fmt::Debug> fmt::Debug for BufReader<R> {
}
}
impl<R: AsyncSeek> AsyncSeek for BufReader<R> {
impl<R: Seek> Seek for BufReader<R> {
/// Seeks to an offset, in bytes, in the underlying reader.
///
/// The position used for seeking with `SeekFrom::Current(_)` is the position the underlying
@ -323,25 +303,26 @@ impl<R: AsyncSeek> AsyncSeek for BufReader<R> {
// support seeking by i64::min_value() so we need to handle underflow when subtracting
// remainder.
if let Some(offset) = n.checked_sub(remainder) {
result = futures::ready!(
result = futures_core::ready!(
self.as_mut()
.inner()
.poll_seek(cx, SeekFrom::Current(offset))
)?;
} else {
// seek backwards by our remainder, and then by the offset
futures::ready!(
futures_core::ready!(
self.as_mut()
.inner()
.poll_seek(cx, SeekFrom::Current(-remainder))
)?;
self.as_mut().discard_buffer();
result =
futures::ready!(self.as_mut().inner().poll_seek(cx, SeekFrom::Current(n)))?;
result = futures_core::ready!(
self.as_mut().inner().poll_seek(cx, SeekFrom::Current(n))
)?;
}
} else {
// Seeking with Start/End doesn't care about our buffer length.
result = futures::ready!(self.as_mut().inner().poll_seek(cx, pos))?;
result = futures_core::ready!(self.as_mut().inner().poll_seek(cx, pos))?;
}
self.discard_buffer();
Poll::Ready(Ok(result))

View file

@ -1,6 +1,8 @@
use futures::io::{AsyncRead, AsyncReadExt, AsyncWrite};
use std::pin::Pin;
use crate::io;
use crate::future::Future;
use crate::io::{self, BufRead, BufReader, Read, Write};
use crate::task::{Context, Poll};
/// Copies the entire contents of a reader into a writer.
///
@ -14,7 +16,7 @@ use crate::io;
/// If youre wanting to copy the contents of one file to another and youre
/// working with filesystem paths, see the [`fs::copy`] function.
///
/// This function is an async version of [`std::fs::write`].
/// This function is an async version of [`std::io::copy`].
///
/// [`std::io::copy`]: https://doc.rust-lang.org/std/io/fn.copy.html
/// [`fs::copy`]: ../fs/fn.copy.html
@ -28,7 +30,6 @@ use crate::io;
/// # Examples
///
/// ```
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::io;
@ -42,9 +43,58 @@ use crate::io;
/// ```
pub async fn copy<R, W>(reader: &mut R, writer: &mut W) -> io::Result<u64>
where
R: AsyncRead + Unpin + ?Sized,
W: AsyncWrite + Unpin + ?Sized,
R: Read + Unpin + ?Sized,
W: Write + Unpin + ?Sized,
{
let bytes_read = reader.copy_into(writer).await?;
Ok(bytes_read)
pub struct CopyFuture<'a, R, W: ?Sized> {
reader: R,
writer: &'a mut W,
amt: u64,
}
impl<R, W: Unpin + ?Sized> CopyFuture<'_, R, W> {
fn project(self: Pin<&mut Self>) -> (Pin<&mut R>, Pin<&mut W>, &mut u64) {
unsafe {
let this = self.get_unchecked_mut();
(
Pin::new_unchecked(&mut this.reader),
Pin::new(&mut *this.writer),
&mut this.amt,
)
}
}
}
impl<R, W> Future for CopyFuture<'_, R, W>
where
R: BufRead,
W: Write + Unpin + ?Sized,
{
type Output = io::Result<u64>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let (mut reader, mut writer, amt) = self.project();
loop {
let buffer = futures_core::ready!(reader.as_mut().poll_fill_buf(cx))?;
if buffer.is_empty() {
futures_core::ready!(writer.as_mut().poll_flush(cx))?;
return Poll::Ready(Ok(*amt));
}
let i = futures_core::ready!(writer.as_mut().poll_write(cx, buffer))?;
if i == 0 {
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
}
*amt += i as u64;
reader.as_mut().consume(i);
}
}
}
let future = CopyFuture {
reader: BufReader::new(reader),
writer,
amt: 0,
};
future.await
}

262
src/io/cursor.rs Normal file
View file

@ -0,0 +1,262 @@
use std::pin::Pin;
use crate::io::{self, BufRead, IoSlice, IoSliceMut, Read, Seek, SeekFrom, Write};
use crate::task::{Context, Poll};
/// A `Cursor` wraps an in-memory buffer and provides it with a
/// [`Seek`] implementation.
///
/// `Cursor`s are used with in-memory buffers, anything implementing
/// `AsRef<[u8]>`, to allow them to implement [`Read`] and/or [`Write`],
/// allowing these buffers to be used anywhere you might use a reader or writer
/// that does actual I/O.
///
/// The standard library implements some I/O traits on various types which
/// are commonly used as a buffer, like `Cursor<`[`Vec`]`<u8>>` and
/// `Cursor<`[`&[u8]`][bytes]`>`.
///
/// [`Seek`]: trait.Seek.html
/// [`Read`]: trait.Read.html
/// [`Write`]: trait.Write.html
/// [`Vec`]: https://doc.rust-lang.org/std/vec/struct.Vec.html
/// [bytes]: https://doc.rust-lang.org/std/primitive.slice.html
/// [`File`]: struct.File.html
#[cfg_attr(feature = "docs", doc(cfg(unstable)))]
#[derive(Clone, Debug, Default)]
pub struct Cursor<T> {
inner: std::io::Cursor<T>,
}
impl<T> Cursor<T> {
/// Creates a new cursor wrapping the provided underlying in-memory buffer.
///
/// Cursor initial position is `0` even if underlying buffer (e.g., `Vec`)
/// is not empty. So writing to cursor starts with overwriting `Vec`
/// content, not with appending to it.
///
/// # Examples
///
/// ```
/// use async_std::io::Cursor;
///
/// let buff = Cursor::new(Vec::new());
/// # fn force_inference(_: &Cursor<Vec<u8>>) {}
/// # force_inference(&buff);
/// ```
pub fn new(inner: T) -> Cursor<T> {
Cursor {
inner: std::io::Cursor::new(inner),
}
}
/// Consumes this cursor, returning the underlying value.
///
/// # Examples
///
/// ```
/// use async_std::io::Cursor;
///
/// let buff = Cursor::new(Vec::new());
/// # fn force_inference(_: &Cursor<Vec<u8>>) {}
/// # force_inference(&buff);
///
/// let vec = buff.into_inner();
/// ```
pub fn into_inner(self) -> T {
self.inner.into_inner()
}
/// Gets a reference to the underlying value in this cursor.
///
/// # Examples
///
/// ```
/// use async_std::io::Cursor;
///
/// let buff = Cursor::new(Vec::new());
/// # fn force_inference(_: &Cursor<Vec<u8>>) {}
/// # force_inference(&buff);
///
/// let reference = buff.get_ref();
/// ```
pub fn get_ref(&self) -> &T {
self.inner.get_ref()
}
/// Gets a mutable reference to the underlying value in this cursor.
///
/// Care should be taken to avoid modifying the internal I/O state of the
/// underlying value as it may corrupt this cursor's position.
///
/// # Examples
///
/// ```
/// use async_std::io::Cursor;
///
/// let mut buff = Cursor::new(Vec::new());
/// # fn force_inference(_: &Cursor<Vec<u8>>) {}
/// # force_inference(&buff);
///
/// let reference = buff.get_mut();
/// ```
pub fn get_mut(&mut self) -> &mut T {
self.inner.get_mut()
}
/// Returns the current position of this cursor.
///
/// # Examples
///
/// ```
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::io::Cursor;
/// use async_std::io::prelude::*;
/// use async_std::io::SeekFrom;
///
/// let mut buff = Cursor::new(vec![1, 2, 3, 4, 5]);
///
/// assert_eq!(buff.position(), 0);
///
/// buff.seek(SeekFrom::Current(2)).await?;
/// assert_eq!(buff.position(), 2);
///
/// buff.seek(SeekFrom::Current(-1)).await?;
/// assert_eq!(buff.position(), 1);
/// #
/// # Ok(()) }) }
/// ```
pub fn position(&self) -> u64 {
self.inner.position()
}
/// Sets the position of this cursor.
///
/// # Examples
///
/// ```
/// use async_std::io::Cursor;
///
/// let mut buff = Cursor::new(vec![1, 2, 3, 4, 5]);
///
/// assert_eq!(buff.position(), 0);
///
/// buff.set_position(2);
/// assert_eq!(buff.position(), 2);
///
/// buff.set_position(4);
/// assert_eq!(buff.position(), 4);
/// ```
pub fn set_position(&mut self, pos: u64) {
self.inner.set_position(pos)
}
}
impl<T> Seek for Cursor<T>
where
T: AsRef<[u8]> + Unpin,
{
fn poll_seek(
mut self: Pin<&mut Self>,
_: &mut Context<'_>,
pos: SeekFrom,
) -> Poll<io::Result<u64>> {
Poll::Ready(std::io::Seek::seek(&mut self.inner, pos))
}
}
impl<T> Read for Cursor<T>
where
T: AsRef<[u8]> + Unpin,
{
fn poll_read(
mut self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
Poll::Ready(std::io::Read::read(&mut self.inner, buf))
}
fn poll_read_vectored(
mut self: Pin<&mut Self>,
_: &mut Context<'_>,
bufs: &mut [IoSliceMut<'_>],
) -> Poll<io::Result<usize>> {
Poll::Ready(std::io::Read::read_vectored(&mut self.inner, bufs))
}
}
impl<T> BufRead for Cursor<T>
where
T: AsRef<[u8]> + Unpin,
{
fn poll_fill_buf(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
Poll::Ready(std::io::BufRead::fill_buf(&mut self.get_mut().inner))
}
fn consume(mut self: Pin<&mut Self>, amt: usize) {
std::io::BufRead::consume(&mut self.inner, amt)
}
}
impl Write for Cursor<&mut [u8]> {
fn poll_write(
mut self: Pin<&mut Self>,
_: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Poll::Ready(std::io::Write::write(&mut self.inner, buf))
}
fn poll_write_vectored(
mut self: Pin<&mut Self>,
_: &mut Context<'_>,
bufs: &[IoSlice<'_>],
) -> Poll<io::Result<usize>> {
Poll::Ready(std::io::Write::write_vectored(&mut self.inner, bufs))
}
fn poll_flush(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(std::io::Write::flush(&mut self.inner))
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.poll_flush(cx)
}
}
impl Write for Cursor<&mut Vec<u8>> {
fn poll_write(
mut self: Pin<&mut Self>,
_: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Poll::Ready(std::io::Write::write(&mut self.inner, buf))
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.poll_flush(cx)
}
fn poll_flush(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(std::io::Write::flush(&mut self.inner))
}
}
impl Write for Cursor<Vec<u8>> {
fn poll_write(
mut self: Pin<&mut Self>,
_: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
Poll::Ready(std::io::Write::write(&mut self.inner, buf))
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.poll_flush(cx)
}
fn poll_flush(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(std::io::Write::flush(&mut self.inner))
}
}

View file

@ -1,9 +1,7 @@
use std::fmt;
use std::pin::Pin;
use futures::io::{AsyncBufRead, AsyncRead, Initializer};
use crate::io;
use crate::io::{self, BufRead, Read};
use crate::task::{Context, Poll};
/// Creates a reader that contains no data.
@ -11,7 +9,6 @@ use crate::task::{Context, Poll};
/// # Examples
///
/// ```rust
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::io;
@ -26,7 +23,7 @@ use crate::task::{Context, Poll};
/// # Ok(()) }) }
/// ```
pub fn empty() -> Empty {
Empty { _priv: () }
Empty { _private: () }
}
/// A reader that contains no data.
@ -35,7 +32,7 @@ pub fn empty() -> Empty {
///
/// [`sink`]: fn.sink.html
pub struct Empty {
_priv: (),
_private: (),
}
impl fmt::Debug for Empty {
@ -44,7 +41,7 @@ impl fmt::Debug for Empty {
}
}
impl AsyncRead for Empty {
impl Read for Empty {
#[inline]
fn poll_read(
self: Pin<&mut Self>,
@ -53,14 +50,9 @@ impl AsyncRead for Empty {
) -> Poll<io::Result<usize>> {
Poll::Ready(Ok(0))
}
#[inline]
unsafe fn initializer(&self) -> Initializer {
Initializer::nop()
}
}
impl AsyncBufRead for Empty {
impl BufRead for Empty {
#[inline]
fn poll_fill_buf<'a>(
self: Pin<&'a mut Self>,

View file

@ -9,7 +9,6 @@
//! Read a line from the standard input:
//!
//! ```no_run
//! # #![feature(async_await)]
//! # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
//! #
//! use async_std::io;
@ -22,14 +21,16 @@
//! ```
#[doc(inline)]
pub use std::io::{Error, ErrorKind, Result, SeekFrom};
pub use std::io::{Error, ErrorKind, IoSlice, IoSliceMut, Result, SeekFrom};
pub use buf_read::{BufRead, Lines};
pub use buf_reader::BufReader;
pub use buf_writer::{BufWriter, LineWriter};
pub use copy::copy;
pub use cursor::Cursor;
pub use empty::{empty, Empty};
pub use read::Read;
pub use repeat::{repeat, Repeat};
pub use seek::Seek;
pub use sink::{sink, Sink};
pub use stderr::{stderr, Stderr};
@ -38,16 +39,21 @@ pub use stdout::{stdout, Stdout};
pub use timeout::timeout;
pub use write::Write;
mod buf_read;
pub mod prelude;
pub(crate) mod buf_read;
pub(crate) mod read;
pub(crate) mod seek;
pub(crate) mod write;
mod buf_reader;
mod buf_writer;
mod copy;
mod cursor;
mod empty;
mod read;
mod seek;
mod repeat;
mod sink;
mod stderr;
mod stdin;
mod stdout;
mod timeout;
mod write;

27
src/io/prelude.rs Normal file
View file

@ -0,0 +1,27 @@
//! The async I/O Prelude
//!
//! The purpose of this module is to alleviate imports of many common I/O traits
//! by adding a glob import to the top of I/O heavy modules:
//!
//! ```
//! # #![allow(unused_imports)]
//! use async_std::io::prelude::*;
//! ```
#[doc(no_inline)]
pub use crate::io::BufRead;
#[doc(no_inline)]
pub use crate::io::Read;
#[doc(no_inline)]
pub use crate::io::Seek;
#[doc(no_inline)]
pub use crate::io::Write;
#[doc(hidden)]
pub use crate::io::buf_read::BufReadExt as _;
#[doc(hidden)]
pub use crate::io::read::ReadExt as _;
#[doc(hidden)]
pub use crate::io::seek::SeekExt as _;
#[doc(hidden)]
pub use crate::io::write::WriteExt as _;

View file

@ -1,398 +0,0 @@
use std::io::IoSliceMut;
use std::mem;
use std::pin::Pin;
use std::str;
use cfg_if::cfg_if;
use futures::io::AsyncRead;
use crate::future::Future;
use crate::io;
use crate::task::{Context, Poll};
cfg_if! {
if #[cfg(feature = "docs")] {
#[doc(hidden)]
pub struct ImplFuture<'a, T>(std::marker::PhantomData<&'a T>);
macro_rules! ret {
($a:lifetime, $f:tt, $o:ty) => (ImplFuture<$a, $o>);
}
} else {
macro_rules! ret {
($a:lifetime, $f:tt, $o:ty) => ($f<$a, Self>);
}
}
}
/// Allows reading from a byte stream.
///
/// This trait is an async version of [`std::io::Read`].
///
/// While it is currently not possible to implement this trait directly, it gets implemented
/// automatically for all types that implement [`futures::io::AsyncRead`].
///
/// [`std::io::Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
/// [`futures::io::AsyncRead`]:
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncRead.html
pub trait Read {
/// Reads some bytes from the byte stream.
///
/// Returns the number of bytes read from the start of the buffer.
///
/// If the return value is `Ok(n)`, then it must be guaranteed that `0 <= n <= buf.len()`. A
/// nonzero `n` value indicates that the buffer has been filled in with `n` bytes of data. If
/// `n` is `0`, then it can indicate one of two scenarios:
///
/// 1. This reader has reached its "end of file" and will likely no longer be able to produce
/// bytes. Note that this does not mean that the reader will always no longer be able to
/// produce bytes.
/// 2. The buffer specified was 0 bytes in length.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut f = File::open("a.txt").await?;
///
/// let mut buf = vec![0; 1024];
/// let n = f.read(&mut buf).await?;
/// #
/// # Ok(()) }) }
/// ```
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> ret!('a, ReadFuture, io::Result<usize>)
where
Self: Unpin;
/// Like [`read`], except that it reads into a slice of buffers.
///
/// Data is copied to fill each buffer in order, with the final buffer written to possibly
/// being only partially filled. This method must behave as a single call to [`read`] with the
/// buffers concatenated would.
///
/// The default implementation calls [`read`] with either the first nonempty buffer provided,
/// or an empty one if none exists.
///
/// [`read`]: #tymethod.read
fn read_vectored<'a>(
&'a mut self,
bufs: &'a mut [IoSliceMut<'a>],
) -> ret!('a, ReadVectoredFuture, io::Result<usize>)
where
Self: Unpin,
{
ReadVectoredFuture { reader: self, bufs }
}
/// Reads all bytes from the byte stream.
///
/// All bytes read from this stream will be appended to the specified buffer `buf`. This
/// function will continuously call [`read`] to append more data to `buf` until [`read`]
/// returns either `Ok(0)` or an error.
///
/// If successful, this function will return the total number of bytes read.
///
/// [`read`]: #tymethod.read
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut f = File::open("a.txt").await?;
///
/// let mut buf = Vec::new();
/// f.read_to_end(&mut buf).await?;
/// #
/// # Ok(()) }) }
/// ```
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> ret!('a, ReadToEndFuture, io::Result<usize>)
where
Self: Unpin,
{
let start_len = buf.len();
ReadToEndFuture {
reader: self,
buf,
start_len,
}
}
/// Reads all bytes from the byte stream and appends them into a string.
///
/// If successful, this function will return the number of bytes read.
///
/// If the data in this stream is not valid UTF-8 then an error will be returned and `buf` will
/// be left unmodified.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut f = File::open("a.txt").await?;
///
/// let mut buf = String::new();
/// f.read_to_string(&mut buf).await?;
/// #
/// # Ok(()) }) }
/// ```
fn read_to_string<'a>(
&'a mut self,
buf: &'a mut String,
) -> ret!('a, ReadToStringFuture, io::Result<usize>)
where
Self: Unpin,
{
let start_len = buf.len();
ReadToStringFuture {
reader: self,
bytes: unsafe { mem::replace(buf.as_mut_vec(), Vec::new()) },
buf,
start_len,
}
}
/// Reads the exact number of bytes required to fill `buf`.
///
/// This function reads as many bytes as necessary to completely fill the specified buffer
/// `buf`.
///
/// No guarantees are provided about the contents of `buf` when this function is called,
/// implementations cannot rely on any property of the contents of `buf` being true. It is
/// recommended that implementations only write data to `buf` instead of reading its contents.
///
/// If this function encounters an "end of file" before completely filling the buffer, it
/// returns an error of the kind [`ErrorKind::UnexpectedEof`]. The contents of `buf` are
/// unspecified in this case.
///
/// If any other read error is encountered then this function immediately returns. The contents
/// of `buf` are unspecified in this case.
///
/// If this function returns an error, it is unspecified how many bytes it has read, but it
/// will never read more than would be necessary to completely fill the buffer.
///
/// [`ErrorKind::UnexpectedEof`]: enum.ErrorKind.html#variant.UnexpectedEof
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut f = File::open("a.txt").await?;
///
/// let mut buf = vec![0; 10];
/// f.read_exact(&mut buf).await?;
/// #
/// # Ok(()) }) }
/// ```
fn read_exact<'a>(&'a mut self, buf: &'a mut [u8]) -> ret!('a, ReadExactFuture, io::Result<()>)
where
Self: Unpin,
{
ReadExactFuture { reader: self, buf }
}
}
impl<T: AsyncRead + Unpin + ?Sized> Read for T {
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> ret!('a, ReadFuture, io::Result<usize>) {
ReadFuture { reader: self, buf }
}
}
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadFuture<'a, T: Unpin + ?Sized> {
reader: &'a mut T,
buf: &'a mut [u8],
}
impl<T: AsyncRead + Unpin + ?Sized> Future for ReadFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self { reader, buf } = &mut *self;
Pin::new(reader).poll_read(cx, buf)
}
}
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadVectoredFuture<'a, T: Unpin + ?Sized> {
reader: &'a mut T,
bufs: &'a mut [IoSliceMut<'a>],
}
impl<T: AsyncRead + Unpin + ?Sized> Future for ReadVectoredFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self { reader, bufs } = &mut *self;
Pin::new(reader).poll_read_vectored(cx, bufs)
}
}
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadToEndFuture<'a, T: Unpin + ?Sized> {
reader: &'a mut T,
buf: &'a mut Vec<u8>,
start_len: usize,
}
impl<T: AsyncRead + Unpin + ?Sized> Future for ReadToEndFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self {
reader,
buf,
start_len,
} = &mut *self;
read_to_end_internal(Pin::new(reader), cx, buf, *start_len)
}
}
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadToStringFuture<'a, T: Unpin + ?Sized> {
reader: &'a mut T,
buf: &'a mut String,
bytes: Vec<u8>,
start_len: usize,
}
impl<T: AsyncRead + Unpin + ?Sized> Future for ReadToStringFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self {
reader,
buf,
bytes,
start_len,
} = &mut *self;
let reader = Pin::new(reader);
let ret = futures::ready!(read_to_end_internal(reader, cx, bytes, *start_len));
if str::from_utf8(&bytes).is_err() {
Poll::Ready(ret.and_then(|_| {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"stream did not contain valid UTF-8",
))
}))
} else {
debug_assert!(buf.is_empty());
// Safety: `bytes` is a valid UTF-8 because `str::from_utf8` returned `Ok`.
mem::swap(unsafe { buf.as_mut_vec() }, bytes);
Poll::Ready(ret)
}
}
}
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadExactFuture<'a, T: Unpin + ?Sized> {
reader: &'a mut T,
buf: &'a mut [u8],
}
impl<T: AsyncRead + Unpin + ?Sized> Future for ReadExactFuture<'_, T> {
type Output = io::Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self { reader, buf } = &mut *self;
while !buf.is_empty() {
let n = futures::ready!(Pin::new(&mut *reader).poll_read(cx, buf))?;
let (_, rest) = mem::replace(buf, &mut []).split_at_mut(n);
*buf = rest;
if n == 0 {
return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into()));
}
}
Poll::Ready(Ok(()))
}
}
// This uses an adaptive system to extend the vector when it fills. We want to
// avoid paying to allocate and zero a huge chunk of memory if the reader only
// has 4 bytes while still making large reads if the reader does have a ton
// of data to return. Simply tacking on an extra DEFAULT_BUF_SIZE space every
// time is 4,500 times (!) slower than this if the reader has a very small
// amount of data to return.
//
// Because we're extending the buffer with uninitialized data for trusted
// readers, we need to make sure to truncate that if any of this panics.
pub fn read_to_end_internal<R: AsyncRead + ?Sized>(
mut rd: Pin<&mut R>,
cx: &mut Context<'_>,
buf: &mut Vec<u8>,
start_len: usize,
) -> Poll<io::Result<usize>> {
struct Guard<'a> {
buf: &'a mut Vec<u8>,
len: usize,
}
impl Drop for Guard<'_> {
fn drop(&mut self) {
unsafe {
self.buf.set_len(self.len);
}
}
}
let mut g = Guard {
len: buf.len(),
buf,
};
let ret;
loop {
if g.len == g.buf.len() {
unsafe {
g.buf.reserve(32);
let capacity = g.buf.capacity();
g.buf.set_len(capacity);
rd.initializer().initialize(&mut g.buf[g.len..]);
}
}
match futures::ready!(rd.as_mut().poll_read(cx, &mut g.buf[g.len..])) {
Ok(0) => {
ret = Poll::Ready(Ok(g.len - start_len));
break;
}
Ok(n) => g.len += n,
Err(e) => {
ret = Poll::Ready(Err(e));
break;
}
}
}
ret
}

333
src/io/read/mod.rs Normal file
View file

@ -0,0 +1,333 @@
mod read;
mod read_exact;
mod read_to_end;
mod read_to_string;
mod read_vectored;
use read::ReadFuture;
use read_exact::ReadExactFuture;
use read_to_end::{read_to_end_internal, ReadToEndFuture};
use read_to_string::ReadToStringFuture;
use read_vectored::ReadVectoredFuture;
use std::mem;
use cfg_if::cfg_if;
use crate::io::IoSliceMut;
cfg_if! {
if #[cfg(feature = "docs")] {
use std::pin::Pin;
use std::ops::{Deref, DerefMut};
use crate::io;
use crate::task::{Context, Poll};
#[doc(hidden)]
pub struct ImplFuture<'a, T>(std::marker::PhantomData<&'a T>);
/// Allows reading from a byte stream.
///
/// This trait is a re-export of [`futures::io::AsyncRead`] and is an async version of
/// [`std::io::Read`].
///
/// Methods other than [`poll_read`] and [`poll_read_vectored`] do not really exist in the
/// trait itself, but they become available when the prelude is imported:
///
/// ```
/// # #[allow(unused_imports)]
/// use async_std::prelude::*;
/// ```
///
/// [`std::io::Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
/// [`futures::io::AsyncRead`]:
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncRead.html
/// [`poll_read`]: #tymethod.poll_read
/// [`poll_read_vectored`]: #method.poll_read_vectored
pub trait Read {
/// Attempt to read from the `AsyncRead` into `buf`.
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>>;
/// Attempt to read from the `AsyncRead` into `bufs` using vectored IO operations.
fn poll_read_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &mut [IoSliceMut<'_>],
) -> Poll<io::Result<usize>> {
unreachable!()
}
/// Reads some bytes from the byte stream.
///
/// Returns the number of bytes read from the start of the buffer.
///
/// If the return value is `Ok(n)`, then it must be guaranteed that
/// `0 <= n <= buf.len()`. A nonzero `n` value indicates that the buffer has been
/// filled in with `n` bytes of data. If `n` is `0`, then it can indicate one of two
/// scenarios:
///
/// 1. This reader has reached its "end of file" and will likely no longer be able to
/// produce bytes. Note that this does not mean that the reader will always no
/// longer be able to produce bytes.
/// 2. The buffer specified was 0 bytes in length.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut file = File::open("a.txt").await?;
///
/// let mut buf = vec![0; 1024];
/// let n = file.read(&mut buf).await?;
/// #
/// # Ok(()) }) }
/// ```
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> ImplFuture<'a, io::Result<usize>>
where
Self: Unpin
{
unreachable!()
}
/// Like [`read`], except that it reads into a slice of buffers.
///
/// Data is copied to fill each buffer in order, with the final buffer written to
/// possibly being only partially filled. This method must behave as a single call to
/// [`read`] with the buffers concatenated would.
///
/// The default implementation calls [`read`] with either the first nonempty buffer
/// provided, or an empty one if none exists.
///
/// [`read`]: #tymethod.read
fn read_vectored<'a>(
&'a mut self,
bufs: &'a mut [IoSliceMut<'a>],
) -> ImplFuture<'a, io::Result<usize>>
where
Self: Unpin,
{
unreachable!()
}
/// Reads all bytes from the byte stream.
///
/// All bytes read from this stream will be appended to the specified buffer `buf`.
/// This function will continuously call [`read`] to append more data to `buf` until
/// [`read`] returns either `Ok(0)` or an error.
///
/// If successful, this function will return the total number of bytes read.
///
/// [`read`]: #tymethod.read
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut file = File::open("a.txt").await?;
///
/// let mut buf = Vec::new();
/// file.read_to_end(&mut buf).await?;
/// #
/// # Ok(()) }) }
/// ```
fn read_to_end<'a>(
&'a mut self,
buf: &'a mut Vec<u8>,
) -> ImplFuture<'a, io::Result<usize>>
where
Self: Unpin,
{
unreachable!()
}
/// Reads all bytes from the byte stream and appends them into a string.
///
/// If successful, this function will return the number of bytes read.
///
/// If the data in this stream is not valid UTF-8 then an error will be returned and
/// `buf` will be left unmodified.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut file = File::open("a.txt").await?;
///
/// let mut buf = String::new();
/// file.read_to_string(&mut buf).await?;
/// #
/// # Ok(()) }) }
/// ```
fn read_to_string<'a>(
&'a mut self,
buf: &'a mut String,
) -> ImplFuture<'a, io::Result<usize>>
where
Self: Unpin,
{
unreachable!()
}
/// Reads the exact number of bytes required to fill `buf`.
///
/// This function reads as many bytes as necessary to completely fill the specified
/// buffer `buf`.
///
/// No guarantees are provided about the contents of `buf` when this function is
/// called, implementations cannot rely on any property of the contents of `buf` being
/// true. It is recommended that implementations only write data to `buf` instead of
/// reading its contents.
///
/// If this function encounters an "end of file" before completely filling the buffer,
/// it returns an error of the kind [`ErrorKind::UnexpectedEof`]. The contents of
/// `buf` are unspecified in this case.
///
/// If any other read error is encountered then this function immediately returns. The
/// contents of `buf` are unspecified in this case.
///
/// If this function returns an error, it is unspecified how many bytes it has read,
/// but it will never read more than would be necessary to completely fill the buffer.
///
/// [`ErrorKind::UnexpectedEof`]: enum.ErrorKind.html#variant.UnexpectedEof
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut file = File::open("a.txt").await?;
///
/// let mut buf = vec![0; 10];
/// file.read_exact(&mut buf).await?;
/// #
/// # Ok(()) }) }
/// ```
fn read_exact<'a>(&'a mut self, buf: &'a mut [u8]) -> ImplFuture<'a, io::Result<()>>
where
Self: Unpin,
{
unreachable!()
}
}
impl<T: Read + Unpin + ?Sized> Read for Box<T> {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
unreachable!()
}
}
impl<T: Read + Unpin + ?Sized> Read for &mut T {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
unreachable!()
}
}
impl<P> Read for Pin<P>
where
P: DerefMut + Unpin,
<P as Deref>::Target: Read,
{
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
unreachable!()
}
}
impl Read for &[u8] {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
unreachable!()
}
}
} else {
pub use futures_io::AsyncRead as Read;
}
}
#[doc(hidden)]
pub trait ReadExt: futures_io::AsyncRead {
fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> ReadFuture<'a, Self>
where
Self: Unpin,
{
ReadFuture { reader: self, buf }
}
fn read_vectored<'a>(
&'a mut self,
bufs: &'a mut [IoSliceMut<'a>],
) -> ReadVectoredFuture<'a, Self>
where
Self: Unpin,
{
ReadVectoredFuture { reader: self, bufs }
}
fn read_to_end<'a>(&'a mut self, buf: &'a mut Vec<u8>) -> ReadToEndFuture<'a, Self>
where
Self: Unpin,
{
let start_len = buf.len();
ReadToEndFuture {
reader: self,
buf,
start_len,
}
}
fn read_to_string<'a>(&'a mut self, buf: &'a mut String) -> ReadToStringFuture<'a, Self>
where
Self: Unpin,
{
let start_len = buf.len();
ReadToStringFuture {
reader: self,
bytes: unsafe { mem::replace(buf.as_mut_vec(), Vec::new()) },
buf,
start_len,
}
}
fn read_exact<'a>(&'a mut self, buf: &'a mut [u8]) -> ReadExactFuture<'a, Self>
where
Self: Unpin,
{
ReadExactFuture { reader: self, buf }
}
}
impl<T: futures_io::AsyncRead + ?Sized> ReadExt for T {}

21
src/io/read/read.rs Normal file
View file

@ -0,0 +1,21 @@
use std::pin::Pin;
use crate::future::Future;
use crate::io::{self, Read};
use crate::task::{Context, Poll};
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadFuture<'a, T: Unpin + ?Sized> {
pub(crate) reader: &'a mut T,
pub(crate) buf: &'a mut [u8],
}
impl<T: Read + Unpin + ?Sized> Future for ReadFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self { reader, buf } = &mut *self;
Pin::new(reader).poll_read(cx, buf)
}
}

33
src/io/read/read_exact.rs Normal file
View file

@ -0,0 +1,33 @@
use std::mem;
use std::pin::Pin;
use crate::future::Future;
use crate::io::{self, Read};
use crate::task::{Context, Poll};
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadExactFuture<'a, T: Unpin + ?Sized> {
pub(crate) reader: &'a mut T,
pub(crate) buf: &'a mut [u8],
}
impl<T: Read + Unpin + ?Sized> Future for ReadExactFuture<'_, T> {
type Output = io::Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self { reader, buf } = &mut *self;
while !buf.is_empty() {
let n = futures_core::ready!(Pin::new(&mut *reader).poll_read(cx, buf))?;
let (_, rest) = mem::replace(buf, &mut []).split_at_mut(n);
*buf = rest;
if n == 0 {
return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into()));
}
}
Poll::Ready(Ok(()))
}
}

View file

@ -0,0 +1,85 @@
use std::pin::Pin;
use crate::future::Future;
use crate::io::{self, Read};
use crate::task::{Context, Poll};
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadToEndFuture<'a, T: Unpin + ?Sized> {
pub(crate) reader: &'a mut T,
pub(crate) buf: &'a mut Vec<u8>,
pub(crate) start_len: usize,
}
impl<T: Read + Unpin + ?Sized> Future for ReadToEndFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self {
reader,
buf,
start_len,
} = &mut *self;
read_to_end_internal(Pin::new(reader), cx, buf, *start_len)
}
}
// This uses an adaptive system to extend the vector when it fills. We want to
// avoid paying to allocate and zero a huge chunk of memory if the reader only
// has 4 bytes while still making large reads if the reader does have a ton
// of data to return. Simply tacking on an extra DEFAULT_BUF_SIZE space every
// time is 4,500 times (!) slower than this if the reader has a very small
// amount of data to return.
//
// Because we're extending the buffer with uninitialized data for trusted
// readers, we need to make sure to truncate that if any of this panics.
pub fn read_to_end_internal<R: Read + ?Sized>(
mut rd: Pin<&mut R>,
cx: &mut Context<'_>,
buf: &mut Vec<u8>,
start_len: usize,
) -> Poll<io::Result<usize>> {
struct Guard<'a> {
buf: &'a mut Vec<u8>,
len: usize,
}
impl Drop for Guard<'_> {
fn drop(&mut self) {
unsafe {
self.buf.set_len(self.len);
}
}
}
let mut g = Guard {
len: buf.len(),
buf,
};
let ret;
loop {
if g.len == g.buf.len() {
unsafe {
g.buf.reserve(32);
let capacity = g.buf.capacity();
g.buf.set_len(capacity);
rd.initializer().initialize(&mut g.buf[g.len..]);
}
}
match futures_core::ready!(rd.as_mut().poll_read(cx, &mut g.buf[g.len..])) {
Ok(0) => {
ret = Poll::Ready(Ok(g.len - start_len));
break;
}
Ok(n) => g.len += n,
Err(e) => {
ret = Poll::Ready(Err(e));
break;
}
}
}
ret
}

View file

@ -0,0 +1,46 @@
use std::mem;
use std::pin::Pin;
use std::str;
use super::read_to_end_internal;
use crate::future::Future;
use crate::io::{self, Read};
use crate::task::{Context, Poll};
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadToStringFuture<'a, T: Unpin + ?Sized> {
pub(crate) reader: &'a mut T,
pub(crate) buf: &'a mut String,
pub(crate) bytes: Vec<u8>,
pub(crate) start_len: usize,
}
impl<T: Read + Unpin + ?Sized> Future for ReadToStringFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self {
reader,
buf,
bytes,
start_len,
} = &mut *self;
let reader = Pin::new(reader);
let ret = futures_core::ready!(read_to_end_internal(reader, cx, bytes, *start_len));
if str::from_utf8(&bytes).is_err() {
Poll::Ready(ret.and_then(|_| {
Err(io::Error::new(
io::ErrorKind::InvalidData,
"stream did not contain valid UTF-8",
))
}))
} else {
debug_assert!(buf.is_empty());
// Safety: `bytes` is a valid UTF-8 because `str::from_utf8` returned `Ok`.
mem::swap(unsafe { buf.as_mut_vec() }, bytes);
Poll::Ready(ret)
}
}
}

View file

@ -0,0 +1,21 @@
use std::pin::Pin;
use crate::future::Future;
use crate::io::{self, IoSliceMut, Read};
use crate::task::{Context, Poll};
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct ReadVectoredFuture<'a, T: Unpin + ?Sized> {
pub(crate) reader: &'a mut T,
pub(crate) bufs: &'a mut [IoSliceMut<'a>],
}
impl<T: Read + Unpin + ?Sized> Future for ReadVectoredFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self { reader, bufs } = &mut *self;
Pin::new(reader).poll_read_vectored(cx, bufs)
}
}

57
src/io/repeat.rs Normal file
View file

@ -0,0 +1,57 @@
use std::fmt;
use std::pin::Pin;
use crate::io::{self, Read};
use crate::task::{Context, Poll};
/// Creates an instance of a reader that infinitely repeats one byte.
///
/// All reads from this reader will succeed by filling the specified buffer with the given byte.
///
/// ## Examples
///
/// ```rust
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::io;
/// use async_std::prelude::*;
///
/// let mut buffer = [0; 3];
/// io::repeat(0b101).read_exact(&mut buffer).await?;
///
/// assert_eq!(buffer, [0b101, 0b101, 0b101]);
/// #
/// # Ok(()) }) }
/// ```
pub fn repeat(byte: u8) -> Repeat {
Repeat { byte }
}
/// A reader which yields one byte over and over and over and over and over and...
///
/// This reader is constructed by the [`repeat`] function.
///
/// [`repeat`]: fn.repeat.html
pub struct Repeat {
byte: u8,
}
impl fmt::Debug for Repeat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.pad("Empty { .. }")
}
}
impl Read for Repeat {
#[inline]
fn poll_read(
self: Pin<&mut Self>,
_: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
for b in &mut *buf {
*b = self.byte;
}
Poll::Ready(Ok(buf.len()))
}
}

View file

@ -1,7 +1,6 @@
use std::pin::Pin;
use cfg_if::cfg_if;
use futures::io::AsyncSeek;
use crate::future::Future;
use crate::io::{self, SeekFrom};
@ -9,64 +8,116 @@ use crate::task::{Context, Poll};
cfg_if! {
if #[cfg(feature = "docs")] {
#[doc(hidden)]
pub struct ImplFuture<'a, T>(std::marker::PhantomData<&'a T>);
use std::ops::{Deref, DerefMut};
macro_rules! ret {
($a:lifetime, $f:tt, $o:ty) => (ImplFuture<$a, $o>);
#[doc(hidden)]
pub struct ImplFuture<T>(std::marker::PhantomData<T>);
/// Allows seeking through a byte stream.
///
/// This trait is a re-export of [`futures::io::AsyncSeek`] and is an async version of
/// [`std::io::Seek`].
///
/// The [provided methods] do not really exist in the trait itself, but they become
/// available when the prelude is imported:
///
/// ```
/// # #[allow(unused_imports)]
/// use async_std::prelude::*;
/// ```
///
/// [`std::io::Seek`]: https://doc.rust-lang.org/std/io/trait.Seek.html
/// [`futures::io::AsyncSeek`]:
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncSeek.html
/// [provided methods]: #provided-methods
pub trait Seek {
/// Attempt to seek to an offset, in bytes, in a stream.
fn poll_seek(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
pos: SeekFrom,
) -> Poll<io::Result<u64>>;
/// Seeks to a new position in a byte stream.
///
/// Returns the new position in the byte stream.
///
/// A seek beyond the end of stream is allowed, but behavior is defined by the
/// implementation.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::io::SeekFrom;
/// use async_std::prelude::*;
///
/// let mut file = File::open("a.txt").await?;
///
/// let file_len = file.seek(SeekFrom::End(0)).await?;
/// #
/// # Ok(()) }) }
/// ```
fn seek(&mut self, pos: SeekFrom) -> ImplFuture<io::Result<u64>>
where
Self: Unpin
{
unreachable!()
}
}
impl<T: Seek + Unpin + ?Sized> Seek for Box<T> {
fn poll_seek(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
pos: SeekFrom,
) -> Poll<io::Result<u64>> {
unreachable!()
}
}
impl<T: Seek + Unpin + ?Sized> Seek for &mut T {
fn poll_seek(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
pos: SeekFrom,
) -> Poll<io::Result<u64>> {
unreachable!()
}
}
impl<P> Seek for Pin<P>
where
P: DerefMut + Unpin,
<P as Deref>::Target: Seek,
{
fn poll_seek(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
pos: SeekFrom,
) -> Poll<io::Result<u64>> {
unreachable!()
}
}
} else {
macro_rules! ret {
($a:lifetime, $f:tt, $o:ty) => ($f<$a, Self>);
}
pub use futures_io::AsyncSeek as Seek;
}
}
/// Allows seeking through a byte stream.
///
/// This trait is an async version of [`std::io::Seek`].
///
/// While it is currently not possible to implement this trait directly, it gets implemented
/// automatically for all types that implement [`futures::io::AsyncSeek`].
///
/// [`std::io::Seek`]: https://doc.rust-lang.org/std/io/trait.Seek.html
/// [`futures::io::AsyncSeek`]:
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncSeek.html
pub trait Seek {
/// Seeks to a new position in a byte stream.
///
/// Returns the new position in the byte stream.
///
/// A seek beyond the end of stream is allowed, but behavior is defined by the
/// implementation.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::io::SeekFrom;
/// use async_std::prelude::*;
///
/// let mut f = File::open("a.txt").await?;
///
/// let file_len = f.seek(SeekFrom::End(0)).await?;
/// #
/// # Ok(()) }) }
/// ```
fn seek(&mut self, pos: SeekFrom) -> ret!('_, SeekFuture, io::Result<u64>)
#[doc(hidden)]
pub trait SeekExt: futures_io::AsyncSeek {
fn seek(&mut self, pos: SeekFrom) -> SeekFuture<'_, Self>
where
Self: Unpin;
}
impl<T: AsyncSeek + Unpin + ?Sized> Seek for T {
fn seek(&mut self, pos: SeekFrom) -> ret!('_, SeekFuture, io::Result<u64>) {
Self: Unpin,
{
SeekFuture { seeker: self, pos }
}
}
impl<T: futures_io::AsyncSeek + ?Sized> SeekExt for T {}
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct SeekFuture<'a, T: Unpin + ?Sized> {
@ -74,7 +125,7 @@ pub struct SeekFuture<'a, T: Unpin + ?Sized> {
pos: SeekFrom,
}
impl<T: AsyncSeek + Unpin + ?Sized> Future for SeekFuture<'_, T> {
impl<T: SeekExt + Unpin + ?Sized> Future for SeekFuture<'_, T> {
type Output = io::Result<u64>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {

View file

@ -1,9 +1,7 @@
use std::fmt;
use std::pin::Pin;
use futures::io::AsyncWrite;
use crate::io;
use crate::io::{self, Write};
use crate::task::{Context, Poll};
/// Creates a writer that consumes and drops all data.
@ -11,7 +9,6 @@ use crate::task::{Context, Poll};
/// # Examples
///
/// ```rust
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::io;
@ -23,7 +20,7 @@ use crate::task::{Context, Poll};
/// # Ok(()) }) }
/// ```
pub fn sink() -> Sink {
Sink { _priv: () }
Sink { _private: () }
}
/// A writer that consumes and drops all data.
@ -32,7 +29,7 @@ pub fn sink() -> Sink {
///
/// [`sink`]: fn.sink.html
pub struct Sink {
_priv: (),
_private: (),
}
impl fmt::Debug for Sink {
@ -41,7 +38,7 @@ impl fmt::Debug for Sink {
}
}
impl AsyncWrite for Sink {
impl Write for Sink {
#[inline]
fn poll_write(
self: Pin<&mut Self>,

View file

@ -1,11 +1,10 @@
use std::io;
use std::pin::Pin;
use std::sync::Mutex;
use cfg_if::cfg_if;
use futures::io::AsyncWrite;
use crate::future::Future;
use crate::io::{self, Write};
use crate::task::{blocking, Context, Poll};
/// Constructs a new handle to the standard error of the current process.
@ -17,7 +16,6 @@ use crate::task::{blocking, Context, Poll};
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::io;
@ -30,7 +28,7 @@ use crate::task::{blocking, Context, Poll};
/// ```
pub fn stderr() -> Stderr {
Stderr(Mutex::new(State::Idle(Some(Inner {
stderr: io::stderr(),
stderr: std::io::stderr(),
buf: Vec::new(),
last_op: None,
}))))
@ -65,7 +63,7 @@ enum State {
#[derive(Debug)]
struct Inner {
/// The blocking stderr handle.
stderr: io::Stderr,
stderr: std::io::Stderr,
/// The write buffer.
buf: Vec<u8>,
@ -81,7 +79,7 @@ enum Operation {
Flush(io::Result<()>),
}
impl AsyncWrite for Stderr {
impl Write for Stderr {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
@ -119,14 +117,14 @@ impl AsyncWrite for Stderr {
// Start the operation asynchronously.
*state = State::Busy(blocking::spawn(async move {
let res = io::Write::write(&mut inner.stderr, &mut inner.buf);
let res = std::io::Write::write(&mut inner.stderr, &mut inner.buf);
inner.last_op = Some(Operation::Write(res));
State::Idle(Some(inner))
}));
}
}
// Poll the asynchronous operation the stderr is currently blocked on.
State::Busy(task) => *state = futures::ready!(Pin::new(task).poll(cx)),
State::Busy(task) => *state = futures_core::ready!(Pin::new(task).poll(cx)),
}
}
}
@ -147,14 +145,14 @@ impl AsyncWrite for Stderr {
// Start the operation asynchronously.
*state = State::Busy(blocking::spawn(async move {
let res = io::Write::flush(&mut inner.stderr);
let res = std::io::Write::flush(&mut inner.stderr);
inner.last_op = Some(Operation::Flush(res));
State::Idle(Some(inner))
}));
}
}
// Poll the asynchronous operation the stderr is currently blocked on.
State::Busy(task) => *state = futures::ready!(Pin::new(task).poll(cx)),
State::Busy(task) => *state = futures_core::ready!(Pin::new(task).poll(cx)),
}
}
}
@ -180,7 +178,7 @@ cfg_if! {
if #[cfg(any(unix, feature = "docs"))] {
impl AsRawFd for Stderr {
fn as_raw_fd(&self) -> RawFd {
io::stderr().as_raw_fd()
std::io::stderr().as_raw_fd()
}
}
}
@ -191,7 +189,7 @@ cfg_if! {
if #[cfg(any(windows, feature = "docs"))] {
impl AsRawHandle for Stderr {
fn as_raw_handle(&self) -> RawHandle {
io::stderr().as_raw_handle()
std::io::stderr().as_raw_handle()
}
}
}

View file

@ -1,12 +1,10 @@
use std::io;
use std::pin::Pin;
use std::sync::Mutex;
use cfg_if::cfg_if;
use futures::future;
use futures::io::{AsyncRead, Initializer};
use crate::future::Future;
use crate::future::{self, Future};
use crate::io::{self, Read};
use crate::task::{blocking, Context, Poll};
/// Constructs a new handle to the standard input of the current process.
@ -18,7 +16,6 @@ use crate::task::{blocking, Context, Poll};
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::io;
@ -31,7 +28,7 @@ use crate::task::{blocking, Context, Poll};
/// ```
pub fn stdin() -> Stdin {
Stdin(Mutex::new(State::Idle(Some(Inner {
stdin: io::stdin(),
stdin: std::io::stdin(),
line: String::new(),
buf: Vec::new(),
last_op: None,
@ -67,7 +64,7 @@ enum State {
#[derive(Debug)]
struct Inner {
/// The blocking stdin handle.
stdin: io::Stdin,
stdin: std::io::Stdin,
/// The line buffer.
line: String,
@ -92,7 +89,6 @@ impl Stdin {
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::io;
@ -132,7 +128,7 @@ impl Stdin {
}
}
// Poll the asynchronous operation the stdin is currently blocked on.
State::Busy(task) => *state = futures::ready!(Pin::new(task).poll(cx)),
State::Busy(task) => *state = futures_core::ready!(Pin::new(task).poll(cx)),
}
}
})
@ -140,7 +136,7 @@ impl Stdin {
}
}
impl AsyncRead for Stdin {
impl Read for Stdin {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
@ -177,22 +173,17 @@ impl AsyncRead for Stdin {
// Start the operation asynchronously.
*state = State::Busy(blocking::spawn(async move {
let res = io::Read::read(&mut inner.stdin, &mut inner.buf);
let res = std::io::Read::read(&mut inner.stdin, &mut inner.buf);
inner.last_op = Some(Operation::Read(res));
State::Idle(Some(inner))
}));
}
}
// Poll the asynchronous operation the stdin is currently blocked on.
State::Busy(task) => *state = futures::ready!(Pin::new(task).poll(cx)),
State::Busy(task) => *state = futures_core::ready!(Pin::new(task).poll(cx)),
}
}
}
#[inline]
unsafe fn initializer(&self) -> Initializer {
Initializer::nop()
}
}
cfg_if! {
@ -211,7 +202,7 @@ cfg_if! {
if #[cfg(any(unix, feature = "docs"))] {
impl AsRawFd for Stdin {
fn as_raw_fd(&self) -> RawFd {
io::stdin().as_raw_fd()
std::io::stdin().as_raw_fd()
}
}
}
@ -222,7 +213,7 @@ cfg_if! {
if #[cfg(any(windows, feature = "docs"))] {
impl AsRawHandle for Stdin {
fn as_raw_handle(&self) -> RawHandle {
io::stdin().as_raw_handle()
std::io::stdin().as_raw_handle()
}
}
}

View file

@ -1,11 +1,10 @@
use std::io;
use std::pin::Pin;
use std::sync::Mutex;
use cfg_if::cfg_if;
use futures::io::AsyncWrite;
use crate::future::Future;
use crate::io::{self, Write};
use crate::task::{blocking, Context, Poll};
/// Constructs a new handle to the standard output of the current process.
@ -17,7 +16,6 @@ use crate::task::{blocking, Context, Poll};
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::io;
@ -30,7 +28,7 @@ use crate::task::{blocking, Context, Poll};
/// ```
pub fn stdout() -> Stdout {
Stdout(Mutex::new(State::Idle(Some(Inner {
stdout: io::stdout(),
stdout: std::io::stdout(),
buf: Vec::new(),
last_op: None,
}))))
@ -65,7 +63,7 @@ enum State {
#[derive(Debug)]
struct Inner {
/// The blocking stdout handle.
stdout: io::Stdout,
stdout: std::io::Stdout,
/// The write buffer.
buf: Vec<u8>,
@ -81,7 +79,7 @@ enum Operation {
Flush(io::Result<()>),
}
impl AsyncWrite for Stdout {
impl Write for Stdout {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
@ -119,14 +117,14 @@ impl AsyncWrite for Stdout {
// Start the operation asynchronously.
*state = State::Busy(blocking::spawn(async move {
let res = io::Write::write(&mut inner.stdout, &mut inner.buf);
let res = std::io::Write::write(&mut inner.stdout, &mut inner.buf);
inner.last_op = Some(Operation::Write(res));
State::Idle(Some(inner))
}));
}
}
// Poll the asynchronous operation the stdout is currently blocked on.
State::Busy(task) => *state = futures::ready!(Pin::new(task).poll(cx)),
State::Busy(task) => *state = futures_core::ready!(Pin::new(task).poll(cx)),
}
}
}
@ -147,14 +145,14 @@ impl AsyncWrite for Stdout {
// Start the operation asynchronously.
*state = State::Busy(blocking::spawn(async move {
let res = io::Write::flush(&mut inner.stdout);
let res = std::io::Write::flush(&mut inner.stdout);
inner.last_op = Some(Operation::Flush(res));
State::Idle(Some(inner))
}));
}
}
// Poll the asynchronous operation the stdout is currently blocked on.
State::Busy(task) => *state = futures::ready!(Pin::new(task).poll(cx)),
State::Busy(task) => *state = futures_core::ready!(Pin::new(task).poll(cx)),
}
}
}
@ -180,7 +178,7 @@ cfg_if! {
if #[cfg(any(unix, feature = "docs"))] {
impl AsRawFd for Stdout {
fn as_raw_fd(&self) -> RawFd {
io::stdout().as_raw_fd()
std::io::stdout().as_raw_fd()
}
}
}
@ -191,7 +189,7 @@ cfg_if! {
if #[cfg(any(windows, feature = "docs"))] {
impl AsRawHandle for Stdout {
fn as_raw_handle(&self) -> RawHandle {
io::stdout().as_raw_handle()
std::io::stdout().as_raw_handle()
}
}
}

View file

@ -1,19 +1,18 @@
use std::pin::Pin;
use std::time::Duration;
use futures_timer::Delay;
use pin_utils::unsafe_pinned;
use futures_timer::TryFutureExt;
use crate::future::Future;
use crate::io;
use crate::task::{Context, Poll};
/// Awaits an I/O future or times out after a duration of time.
///
/// If you want to await a non I/O future consider using
/// [`future::timeout`](../future/fn.timeout.html) instead.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use std::time::Duration;
@ -34,39 +33,5 @@ pub async fn timeout<F, T>(dur: Duration, f: F) -> io::Result<T>
where
F: Future<Output = io::Result<T>>,
{
let f = TimeoutFuture {
future: f,
delay: Delay::new(dur),
};
f.await
}
struct TimeoutFuture<F> {
future: F,
delay: Delay,
}
impl<F> TimeoutFuture<F> {
unsafe_pinned!(future: F);
unsafe_pinned!(delay: Delay);
}
impl<F, T> Future for TimeoutFuture<F>
where
F: Future<Output = io::Result<T>>,
{
type Output = F::Output;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.as_mut().future().poll(cx) {
Poll::Ready(v) => Poll::Ready(v),
Poll::Pending => match self.delay().poll(cx) {
Poll::Ready(_) => Poll::Ready(Err(io::Error::new(
io::ErrorKind::TimedOut,
"I/O operation has timed out",
))),
Poll::Pending => Poll::Pending,
},
}
}
f.timeout(dur).await
}

View file

@ -1,218 +0,0 @@
use std::io::IoSlice;
use std::mem;
use std::pin::Pin;
use cfg_if::cfg_if;
use futures::io::AsyncWrite;
use crate::future::Future;
use crate::io;
use crate::task::{Context, Poll};
cfg_if! {
if #[cfg(feature = "docs")] {
#[doc(hidden)]
pub struct ImplFuture<'a, T>(std::marker::PhantomData<&'a T>);
macro_rules! ret {
($a:lifetime, $f:tt, $o:ty) => (ImplFuture<$a, $o>);
}
} else {
macro_rules! ret {
($a:lifetime, $f:tt, $o:ty) => ($f<$a, Self>);
}
}
}
/// Allows writing to a byte stream.
///
/// This trait is an async version of [`std::io::Write`].
///
/// While it is currently not possible to implement this trait directly, it gets implemented
/// automatically for all types that implement [`futures::io::AsyncWrite`].
///
/// [`std::io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
/// [`futures::io::AsyncWrite`]:
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncWrite.html
pub trait Write {
/// Writes some bytes into the byte stream.
///
/// Returns the number of bytes written from the start of the buffer.
///
/// If the return value is `Ok(n)` then it must be guaranteed that `0 <= n <= buf.len()`. A
/// return value of `0` typically means that the underlying object is no longer able to accept
/// bytes and will likely not be able to in the future as well, or that the buffer provided is
/// empty.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut f = File::create("a.txt").await?;
///
/// let n = f.write(b"hello world").await?;
/// #
/// # Ok(()) }) }
/// ```
fn write<'a>(&'a mut self, buf: &'a [u8]) -> ret!('a, WriteFuture, io::Result<usize>)
where
Self: Unpin;
/// Flushes the stream to ensure that all buffered contents reach their destination.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut f = File::create("a.txt").await?;
///
/// f.write_all(b"hello world").await?;
/// f.flush().await?;
/// #
/// # Ok(()) }) }
/// ```
fn flush(&mut self) -> ret!('_, FlushFuture, io::Result<()>)
where
Self: Unpin;
/// Like [`write`], except that it writes from a slice of buffers.
///
/// Data is copied from each buffer in order, with the final buffer read from possibly being
/// only partially consumed. This method must behave as a call to [`write`] with the buffers
/// concatenated would.
///
/// The default implementation calls [`write`] with either the first nonempty buffer provided,
/// or an empty one if none exists.
///
/// [`write`]: #tymethod.write
fn write_vectored<'a>(
&'a mut self,
bufs: &'a [IoSlice<'a>],
) -> ret!('a, WriteVectoredFuture, io::Result<usize>)
where
Self: Unpin,
{
WriteVectoredFuture { writer: self, bufs }
}
/// Writes an entire buffer into the byte stream.
///
/// This method will continuously call [`write`] until there is no more data to be written or
/// an error is returned. This method will not return until the entire buffer has been
/// successfully written or such an error occurs.
///
/// # Examples
///
/// ```no_run
/// # #![feature(async_await)]
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut f = File::create("a.txt").await?;
///
/// f.write_all(b"hello world").await?;
/// #
/// # Ok(()) }) }
/// ```
fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> ret!('a, WriteAllFuture, io::Result<()>)
where
Self: Unpin,
{
WriteAllFuture { writer: self, buf }
}
}
impl<T: AsyncWrite + Unpin + ?Sized> Write for T {
fn write<'a>(&'a mut self, buf: &'a [u8]) -> ret!('a, WriteFuture, io::Result<usize>) {
WriteFuture { writer: self, buf }
}
fn flush(&mut self) -> ret!('_, FlushFuture, io::Result<()>) {
FlushFuture { writer: self }
}
}
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct WriteFuture<'a, T: Unpin + ?Sized> {
writer: &'a mut T,
buf: &'a [u8],
}
impl<T: AsyncWrite + Unpin + ?Sized> Future for WriteFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let buf = self.buf;
Pin::new(&mut *self.writer).poll_write(cx, buf)
}
}
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct FlushFuture<'a, T: Unpin + ?Sized> {
writer: &'a mut T,
}
impl<T: AsyncWrite + Unpin + ?Sized> Future for FlushFuture<'_, T> {
type Output = io::Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut *self.writer).poll_flush(cx)
}
}
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct WriteVectoredFuture<'a, T: Unpin + ?Sized> {
writer: &'a mut T,
bufs: &'a [IoSlice<'a>],
}
impl<T: AsyncWrite + Unpin + ?Sized> Future for WriteVectoredFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let bufs = self.bufs;
Pin::new(&mut *self.writer).poll_write_vectored(cx, bufs)
}
}
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct WriteAllFuture<'a, T: Unpin + ?Sized> {
writer: &'a mut T,
buf: &'a [u8],
}
impl<T: AsyncWrite + Unpin + ?Sized> Future for WriteAllFuture<'_, T> {
type Output = io::Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self { writer, buf } = &mut *self;
while !buf.is_empty() {
let n = futures::ready!(Pin::new(&mut **writer).poll_write(cx, buf))?;
let (_, rest) = mem::replace(buf, &[]).split_at(n);
*buf = rest;
if n == 0 {
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
}
}
Poll::Ready(Ok(()))
}
}

19
src/io/write/flush.rs Normal file
View file

@ -0,0 +1,19 @@
use std::pin::Pin;
use crate::future::Future;
use crate::io::{self, Write};
use crate::task::{Context, Poll};
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct FlushFuture<'a, T: Unpin + ?Sized> {
pub(crate) writer: &'a mut T,
}
impl<T: Write + Unpin + ?Sized> Future for FlushFuture<'_, T> {
type Output = io::Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
Pin::new(&mut *self.writer).poll_flush(cx)
}
}

289
src/io/write/mod.rs Normal file
View file

@ -0,0 +1,289 @@
mod flush;
mod write;
mod write_all;
mod write_vectored;
use flush::FlushFuture;
use write::WriteFuture;
use write_all::WriteAllFuture;
use write_vectored::WriteVectoredFuture;
use cfg_if::cfg_if;
use crate::io::IoSlice;
cfg_if! {
if #[cfg(feature = "docs")] {
use std::pin::Pin;
use std::ops::{Deref, DerefMut};
use crate::io;
use crate::task::{Context, Poll};
#[doc(hidden)]
pub struct ImplFuture<'a, T>(std::marker::PhantomData<&'a T>);
/// Allows writing to a byte stream.
///
/// This trait is a re-export of [`futures::io::AsyncWrite`] and is an async version of
/// [`std::io::Write`].
///
/// Methods other than [`poll_write`], [`poll_write_vectored`], [`poll_flush`], and
/// [`poll_close`] do not really exist in the trait itself, but they become available when
/// the prelude is imported:
///
/// ```
/// # #[allow(unused_imports)]
/// use async_std::prelude::*;
/// ```
///
/// [`std::io::Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
/// [`futures::io::AsyncWrite`]:
/// https://docs.rs/futures-preview/0.3.0-alpha.17/futures/io/trait.AsyncWrite.html
/// [`poll_write`]: #tymethod.poll_write
/// [`poll_write_vectored`]: #method.poll_write_vectored
/// [`poll_flush`]: #tymethod.poll_flush
/// [`poll_close`]: #tymethod.poll_close
pub trait Write {
/// Attempt to write bytes from `buf` into the object.
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>>;
/// Attempt to write bytes from `bufs` into the object using vectored
/// IO operations.
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
bufs: &[IoSlice<'_>]
) -> Poll<io::Result<usize>> {
unreachable!()
}
/// Attempt to flush the object, ensuring that any buffered data reach
/// their destination.
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>>;
/// Attempt to close the object.
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>>;
/// Writes some bytes into the byte stream.
///
/// Returns the number of bytes written from the start of the buffer.
///
/// If the return value is `Ok(n)` then it must be guaranteed that
/// `0 <= n <= buf.len()`. A return value of `0` typically means that the underlying
/// object is no longer able to accept bytes and will likely not be able to in the
/// future as well, or that the buffer provided is empty.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut file = File::create("a.txt").await?;
///
/// let n = file.write(b"hello world").await?;
/// #
/// # Ok(()) }) }
/// ```
fn write<'a>(&'a mut self, buf: &'a [u8]) -> ImplFuture<'a, io::Result<usize>>
where
Self: Unpin,
{
unreachable!()
}
/// Flushes the stream to ensure that all buffered contents reach their destination.
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut file = File::create("a.txt").await?;
///
/// file.write_all(b"hello world").await?;
/// file.flush().await?;
/// #
/// # Ok(()) }) }
/// ```
fn flush(&mut self) -> ImplFuture<'_, io::Result<()>>
where
Self: Unpin,
{
unreachable!()
}
/// Like [`write`], except that it writes from a slice of buffers.
///
/// Data is copied from each buffer in order, with the final buffer read from possibly
/// being only partially consumed. This method must behave as a call to [`write`] with
/// the buffers concatenated would.
///
/// The default implementation calls [`write`] with either the first nonempty buffer
/// provided, or an empty one if none exists.
///
/// [`write`]: #tymethod.write
fn write_vectored<'a>(
&'a mut self,
bufs: &'a [IoSlice<'a>],
) -> ImplFuture<'a, io::Result<usize>>
where
Self: Unpin,
{
unreachable!()
}
/// Writes an entire buffer into the byte stream.
///
/// This method will continuously call [`write`] until there is no more data to be
/// written or an error is returned. This method will not return until the entire
/// buffer has been successfully written or such an error occurs.
///
/// [`write`]: #tymethod.write
///
/// # Examples
///
/// ```no_run
/// # fn main() -> std::io::Result<()> { async_std::task::block_on(async {
/// #
/// use async_std::fs::File;
/// use async_std::prelude::*;
///
/// let mut file = File::create("a.txt").await?;
///
/// file.write_all(b"hello world").await?;
/// #
/// # Ok(()) }) }
/// ```
///
/// [`write`]: #tymethod.write
fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> ImplFuture<'a, io::Result<()>>
where
Self: Unpin,
{
unreachable!()
}
}
impl<T: Write + Unpin + ?Sized> Write for Box<T> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
unreachable!()
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
unreachable!()
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
unreachable!()
}
}
impl<T: Write + Unpin + ?Sized> Write for &mut T {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
unreachable!()
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
unreachable!()
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
unreachable!()
}
}
impl<P> Write for Pin<P>
where
P: DerefMut + Unpin,
<P as Deref>::Target: Write,
{
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
unreachable!()
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
unreachable!()
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
unreachable!()
}
}
impl Write for Vec<u8> {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
unreachable!()
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
unreachable!()
}
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
unreachable!()
}
}
} else {
pub use futures_io::AsyncWrite as Write;
}
}
#[doc(hidden)]
pub trait WriteExt: Write {
fn write<'a>(&'a mut self, buf: &'a [u8]) -> WriteFuture<'a, Self>
where
Self: Unpin,
{
WriteFuture { writer: self, buf }
}
fn flush(&mut self) -> FlushFuture<'_, Self>
where
Self: Unpin,
{
FlushFuture { writer: self }
}
fn write_vectored<'a>(&'a mut self, bufs: &'a [IoSlice<'a>]) -> WriteVectoredFuture<'a, Self>
where
Self: Unpin,
{
WriteVectoredFuture { writer: self, bufs }
}
fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> WriteAllFuture<'a, Self>
where
Self: Unpin,
{
WriteAllFuture { writer: self, buf }
}
}
impl<T: Write + ?Sized> WriteExt for T {}

21
src/io/write/write.rs Normal file
View file

@ -0,0 +1,21 @@
use std::pin::Pin;
use crate::future::Future;
use crate::io::{self, Write};
use crate::task::{Context, Poll};
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct WriteFuture<'a, T: Unpin + ?Sized> {
pub(crate) writer: &'a mut T,
pub(crate) buf: &'a [u8],
}
impl<T: Write + Unpin + ?Sized> Future for WriteFuture<'_, T> {
type Output = io::Result<usize>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let buf = self.buf;
Pin::new(&mut *self.writer).poll_write(cx, buf)
}
}

33
src/io/write/write_all.rs Normal file
View file

@ -0,0 +1,33 @@
use std::mem;
use std::pin::Pin;
use crate::future::Future;
use crate::io::{self, Write};
use crate::task::{Context, Poll};
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub struct WriteAllFuture<'a, T: Unpin + ?Sized> {
pub(crate) writer: &'a mut T,
pub(crate) buf: &'a [u8],
}
impl<T: Write + Unpin + ?Sized> Future for WriteAllFuture<'_, T> {
type Output = io::Result<()>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let Self { writer, buf } = &mut *self;
while !buf.is_empty() {
let n = futures_core::ready!(Pin::new(&mut **writer).poll_write(cx, buf))?;
let (_, rest) = mem::replace(buf, &[]).split_at(n);
*buf = rest;
if n == 0 {
return Poll::Ready(Err(io::ErrorKind::WriteZero.into()));
}
}
Poll::Ready(Ok(()))
}
}

Some files were not shown because too many files have changed in this diff Show more