Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Future::boxed and Stream::boxed should prevent double boxing #512

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ appveyor = { repository = "alexcrichton/futures-rs" }

[dependencies]

[build-dependencies]
regex = "0.2"

[features]
use_std = []
with-deprecated = []
Expand Down
49 changes: 49 additions & 0 deletions benches/boxed_boxed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#![feature(test)]

extern crate test;
extern crate futures;

use test::Bencher;

use futures::Async;
use futures::stream;
use futures::stream::Stream;


fn drain<S : Stream<Item=u32, Error=()>>(mut s: S) {
loop {
match s.poll() {
Ok(Async::Ready(Some(i))) => { test::black_box(i); },
Ok(Async::Ready(None)) => return,
_ => unreachable!(),
}
}
}

#[bench]
fn plain(b: &mut Bencher) {
b.iter(|| {
drain(stream::iter((0..1000).map(Ok)));
})
}

#[bench]
fn boxed(b: &mut Bencher) {
b.iter(|| {
drain(stream::iter((0..1000).map(Ok)).boxed());
})
}

#[bench]
fn boxed_boxed(b: &mut Bencher) {
b.iter(|| {
drain(stream::iter((0..1000).map(Ok)).boxed().boxed());
})
}

#[bench]
fn boxed_boxed_boxed(b: &mut Bencher) {
b.iter(|| {
drain(stream::iter((0..1000).map(Ok)).boxed().boxed().boxed());
})
}
56 changes: 56 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
extern crate regex;

use std::env;
use std::str;
use std::process;

#[allow(dead_code)]
struct RustVersion {
major: u32,
minor: u32,
patch: u32,
nightly: bool,
}

fn rust_version() -> RustVersion {
let rustc = env::var("RUSTC").expect("RUSTC variable is unset");

let command = process::Command::new(rustc)
.args(&["--version"])
.stdin(process::Stdio::null())
.stderr(process::Stdio::inherit())
.stdout(process::Stdio::piped())
.spawn()
.expect("spawn rustc");

let wait = command.wait_with_output().expect("wait for rust");
if !wait.status.success() {
panic!("rustc --version exited with non-zero code");
}

let stdout = str::from_utf8(&wait.stdout).expect("stdout is not UTF-8");

let re = regex::Regex::new(r"^rustc (\d+)\.(\d+)\.(\d+)(-nightly)?").expect("compile regex");
let captures = re.captures(stdout)
.expect(&format!("regex cannot match `rustc --version` output: {:?}", stdout));

let major: u32 = captures.get(1).expect("major").as_str().parse().unwrap();
let minor: u32 = captures.get(2).expect("minor").as_str().parse().unwrap();
let patch: u32 = captures.get(3).expect("patch").as_str().parse().unwrap();
let nightly: bool = captures.get(4).is_some();

RustVersion {
major: major,
minor: minor,
patch: patch,
nightly: nightly,
}
}

fn main() {
let version = rust_version();

if version.nightly {
println!("cargo:rustc-cfg=rust_nightly");
}
}
29 changes: 28 additions & 1 deletion src/future/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ pub trait Future {
fn boxed(self) -> BoxFuture<Self::Item, Self::Error>
where Self: Sized + Send + 'static
{
::std::boxed::Box::new(self)
Boxed::boxed(self)
}

/// Map this future's result to a different type, returning a new future of
Expand Down Expand Up @@ -1044,3 +1044,30 @@ impl<F> fmt::Debug for ExecuteError<F> {
}
}
}


#[cfg(feature = "use_std")]
trait Boxed: Future {
fn boxed(self) -> BoxFuture<Self::Item, Self::Error>;
}

#[cfg(feature = "use_std")]
impl<F> Boxed for F where F: Future + Sized + Send + 'static {
#[cfg(rust_nightly)]
default fn boxed(self) -> BoxFuture<Self::Item, Self::Error> {
::std::boxed::Box::new(self)
}

#[cfg(not(rust_nightly))]
fn boxed(self) -> BoxFuture<Self::Item, Self::Error> {
::std::boxed::Box::new(self)
}
}

#[cfg(feature = "use_std")]
#[cfg(rust_nightly)]
impl<I, E> Boxed for BoxFuture<I, E> {
fn boxed(self) -> BoxFuture<Self::Item, Self::Error> {
self
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![cfg_attr(rust_nightly, feature(specialization))]

//! Zero-cost Futures in Rust
//!
//! This library is an implementation of futures in Rust which aims to provide
Expand Down
29 changes: 28 additions & 1 deletion src/stream/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ pub trait Stream {
fn boxed(self) -> BoxStream<Self::Item, Self::Error>
where Self: Sized + Send + 'static,
{
::std::boxed::Box::new(self)
Boxed::boxed(self)
}

/// Converts this stream into a `Future`.
Expand Down Expand Up @@ -1059,3 +1059,30 @@ impl<'a, S: ?Sized + Stream> Stream for &'a mut S {
(**self).poll()
}
}


#[cfg(feature = "use_std")]
trait Boxed: Stream {
fn boxed(self) -> BoxStream<Self::Item, Self::Error>;
}

#[cfg(feature = "use_std")]
impl<F> Boxed for F where F: Stream + Sized + Send + 'static {
#[cfg(rust_nightly)]
default fn boxed(self) -> BoxStream<Self::Item, Self::Error> {
::std::boxed::Box::new(self)
}

#[cfg(not(rust_nightly))]
fn boxed(self) -> BoxStream<Self::Item, Self::Error> {
::std::boxed::Box::new(self)
}
}

#[cfg(feature = "use_std")]
#[cfg(rust_nightly)]
impl<I, E> Boxed for BoxStream<I, E> {
fn boxed(self) -> BoxStream<Self::Item, Self::Error> {
self
}
}
62 changes: 62 additions & 0 deletions tests/boxed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#![cfg(rust_nightly)]

extern crate futures;

use futures::Async;
use futures::Poll;
use futures::future::Future;
use futures::stream::Stream;


#[test]
fn future_boxed_prevents_double_boxing() {
struct MyFuture {
r: &'static str,
}

impl Future for MyFuture {
type Item = &'static str;
type Error = ();

fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
Ok(Async::Ready(self.r))
}
}

let f = MyFuture { r: "I'm ready" };
let f = f.boxed();
let ptr = f.as_ref() as *const Future<Item=_, Error=_>;
let f = f.boxed();
let f = f.boxed();
let mut f = f.boxed();
assert_eq!(f.as_ref() as *const Future<Item=_, Error=_>, ptr);
assert_eq!(Ok(Async::Ready("I'm ready")), f.poll());
}

#[test]
fn stream_boxed_prevents_double_boxing() {
struct MyStream {
i: u32,
}

impl Stream for MyStream {
type Item = u32;
type Error = ();

fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
self.i += 1;
Ok(Async::Ready(Some(self.i)))
}
}

let s = MyStream { i: 0 };
let s = s.boxed();
let ptr = s.as_ref() as *const Stream<Item=_, Error=_>;
let s = s.boxed();
let s = s.boxed();
let mut s = s.boxed();
assert_eq!(s.as_ref() as *const Stream<Item=_, Error=_>, ptr);
assert_eq!(Ok(Async::Ready(Some(1))), s.poll());
assert_eq!(Ok(Async::Ready(Some(2))), s.poll());
assert_eq!(Ok(Async::Ready(Some(3))), s.poll());
}