Skip to content

Commit

Permalink
Future::boxed and Stream::boxed should prevent double boxing
Browse files Browse the repository at this point in the history
Fixes #511
  • Loading branch information
stepancheg committed Jun 10, 2017
1 parent 9f03c50 commit c1b5845
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 2 deletions.
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
36 changes: 36 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
extern crate regex;

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

fn main() {
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+)").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();

if major > 1 || minor >= 18 {
println!("cargo:rustc-cfg=rust_at_least_1_18");
}
}
31 changes: 31 additions & 0 deletions src/boxed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#[inline(always)]
#[cfg(rust_at_least_1_18)]
pub fn transmute_or_convert<A, B, F>(a: A, put_in_box: F)
-> B where A : 'static, B : 'static, F : FnOnce(A) -> B
{
use std::any::TypeId;
use std::mem;

if TypeId::of::<A>() == TypeId::of::<B>() {
// Prevent double boxing
assert!(mem::size_of::<A>() == mem::size_of::<B>());
unsafe {
let r: B = mem::transmute_copy(&a);
mem::forget(a);
r
}
} else {
put_in_box(a)
}
}

// Using simple version on older rust, because `TypeId::of`
// requires Reflect trait before Rust 1.13
// https://github.com/rust-lang/rust/pull/37600#issuecomment-259690727
#[inline(always)]
#[cfg(not(rust_at_least_1_18))]
pub fn transmute_or_convert<A, B, F>(a: A, put_in_box: F)
-> B where A : 'static, B : 'static, F : FnOnce(A) -> B
{
put_in_box(a)
}
3 changes: 2 additions & 1 deletion src/future/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,8 @@ pub trait Future {
fn boxed(self) -> BoxFuture<Self::Item, Self::Error>
where Self: Sized + Send + 'static
{
::std::boxed::Box::new(self)
::boxed::transmute_or_convert::<Self, BoxFuture<Self::Item, Self::Error>, _>(
self, |f| ::std::boxed::Box::new(f))
}

/// Map this future's result to a different type, returning a new future of
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ pub mod sync;
#[cfg(feature = "use_std")]
pub mod unsync;

#[cfg(feature = "use_std")]
#[doc(hidden)]
pub mod boxed;


if_std! {
#[doc(hidden)]
Expand Down
3 changes: 2 additions & 1 deletion src/stream/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ pub trait Stream {
fn boxed(self) -> BoxStream<Self::Item, Self::Error>
where Self: Sized + Send + 'static,
{
::std::boxed::Box::new(self)
::boxed::transmute_or_convert::<Self, BoxStream<Self::Item, Self::Error>, _>(
self, |s| ::std::boxed::Box::new(s))
}

/// Converts this stream into a `Future`.
Expand Down
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_at_least_1_18)]

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());
}

0 comments on commit c1b5845

Please sign in to comment.