Skip to content

Commit 6ab154b

Browse files
bors[bot]sunjay
andauthored
Merge #266
266: Changes Extend trait in order to allow streams that yield references r=yoshuawuyts a=sunjay This is not ready to merge yet. I am mainly opening it so we can discuss a change I had to make to the `Extend` trait. cc @yoshuawuyts @stjepang (and anyone else interested) ## Before this can be merged - [x] Discuss/Approve changes to `Extend` trait - [x] Change to using `for_each` after #264 is merged - [ ] (optional) Wait until a `copied()` method is added to `StreamExt` so that the `&char` impl can be finished. - We can also just comment out or remove the impl that uses `copied` until that is added ## Changes To The Extend Trait While writing the impls of the `Extend` trait for the `String` type, I noticed that certain impls weren't possible because there is no bound on `Extend` that guarantees that the type `A` being yielded from the stream actually lives long enough. We probably didn't run into this earlier because this usually isn't a problem for owned values since the compiler doesn't have to worry about whether they will out live the stream that they come from. I ran into this because of the `Extend` impls that consume streams of references. The difference between the async `Extend` and the standard library `Extend` is that the async `Extend` returns a value that still references the input stream. That means that if `A` is any reference type, the compiler needs to be able to guarantee that `A` will be around as long as the `Future` returned from the trait method is around. To fix this, I had to add the bound shown below: ```patch pub trait Extend<A> { /// Extends a collection with the contents of a stream. fn stream_extend<'a, T: IntoStream<Item = A> + 'a>( &'a mut self, stream: T, - ) -> Pin<Box<dyn Future<Output = ()> + 'a>>; + ) -> Pin<Box<dyn Future<Output = ()> + 'a>> where A: 'a; } ``` This guarantees that each value of type `A` will last at least as long as our boxed future does. The bound had to be in a where clause on the method (and not on the declaration of `A` because the lifetime `'a` isn't in scope at the trait level. I don't think there are any negative consequences of using a where clause like this, but that's why I wanted to bring it up for discussion. In addition to this, I had to ensure that when writing the `Extend` impls for `String` I appropriately bounded the lifetime of the references from the stream. You can see this in the code below with `where 'b: 'a`. ```rust impl<'b> Extend<&'b str> for String { fn stream_extend<'a, S: IntoStream<Item = &'b str> + 'a>( &'a mut self, stream: S, ) -> Pin<Box<dyn Future<Output = ()> + 'a>> where 'b: 'a { //TODO: This can just be: stream.into_stream().for_each(move |s| self.push_str(s)) Box::pin(stream.into_stream().fold((), move |(), s| self.push_str(s))) } } ``` I should note that initially I tried to make it work with just the impl shown above, without modifying the `Extend` trait. This doesn't work because it would be a stricter bound than what is found in the trait itself. Rust does not allow stricter bounds like that because it could potentially cause unsoundness when dealing with generics. Of course, I am totally open to being completely wrong in my understanding of how to resolve this issue. I tried to solve the problem with as minimal of a change as possible. Please let me know if you have some better ideas or other suggestions. ## `FromStream` impls for String The purpose of adding these `Extend` impls is to continue my work from #129 in adding the rest of the `FromStream` impls. The `Extend` impls are used directly to add all of the `FromStream` impls for `String`. Just like with #207 and #265, this adds a new `string` module that is unstable just like the other modules added for `FromStream`. Co-authored-by: Sunjay Varma <[email protected]>
2 parents 5bd6acd + 09a15ef commit 6ab154b

File tree

5 files changed

+184
-1
lines changed

5 files changed

+184
-1
lines changed

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ cfg_if! {
6868
mod vec;
6969
mod result;
7070
mod option;
71+
mod string;
7172
}
7273
}
7374

src/stream/extend.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ pub trait Extend<A> {
3333
fn stream_extend<'a, T: IntoStream<Item = A> + 'a>(
3434
&'a mut self,
3535
stream: T,
36-
) -> Pin<Box<dyn Future<Output = ()> + 'a>>;
36+
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
37+
where
38+
A: 'a;
3739
}
3840

3941
impl Extend<()> for () {

src/string/extend.rs

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
use std::borrow::Cow;
2+
use std::pin::Pin;
3+
4+
use crate::prelude::*;
5+
use crate::stream::{Extend, IntoStream};
6+
7+
impl Extend<char> for String {
8+
fn stream_extend<'a, S: IntoStream<Item = char> + 'a>(
9+
&'a mut self,
10+
stream: S,
11+
) -> Pin<Box<dyn Future<Output = ()> + 'a>> {
12+
let stream = stream.into_stream();
13+
//TODO: Add this back in when size_hint is added to Stream/StreamExt
14+
// let (lower_bound, _) = stream.size_hint();
15+
// self.reserve(lower_bound);
16+
17+
Box::pin(stream.for_each(move |c| self.push(c)))
18+
}
19+
}
20+
21+
impl<'b> Extend<&'b char> for String {
22+
fn stream_extend<'a, S: IntoStream<Item = &'b char> + 'a>(
23+
&'a mut self,
24+
//TODO: Remove the underscore when uncommenting the body of this impl
25+
_stream: S,
26+
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
27+
where
28+
'b: 'a,
29+
{
30+
//TODO: This can be uncommented when `copied` is added to Stream/StreamExt
31+
//Box::pin(stream.into_stream().copied())
32+
unimplemented!()
33+
}
34+
}
35+
36+
impl<'b> Extend<&'b str> for String {
37+
fn stream_extend<'a, S: IntoStream<Item = &'b str> + 'a>(
38+
&'a mut self,
39+
stream: S,
40+
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
41+
where
42+
'b: 'a,
43+
{
44+
Box::pin(stream.into_stream().for_each(move |s| self.push_str(s)))
45+
}
46+
}
47+
48+
impl Extend<String> for String {
49+
fn stream_extend<'a, S: IntoStream<Item = String> + 'a>(
50+
&'a mut self,
51+
stream: S,
52+
) -> Pin<Box<dyn Future<Output = ()> + 'a>> {
53+
Box::pin(stream.into_stream().for_each(move |s| self.push_str(&s)))
54+
}
55+
}
56+
57+
impl<'b> Extend<Cow<'b, str>> for String {
58+
fn stream_extend<'a, S: IntoStream<Item = Cow<'b, str>> + 'a>(
59+
&'a mut self,
60+
stream: S,
61+
) -> Pin<Box<dyn Future<Output = ()> + 'a>>
62+
where
63+
'b: 'a,
64+
{
65+
Box::pin(stream.into_stream().for_each(move |s| self.push_str(&s)))
66+
}
67+
}

src/string/from_stream.rs

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
use std::borrow::Cow;
2+
use std::pin::Pin;
3+
4+
use crate::stream::{Extend, FromStream, IntoStream};
5+
6+
impl FromStream<char> for String {
7+
#[inline]
8+
fn from_stream<'a, S: IntoStream<Item = char>>(
9+
stream: S,
10+
) -> Pin<Box<dyn core::future::Future<Output = Self> + 'a>>
11+
where
12+
<S as IntoStream>::IntoStream: 'a,
13+
{
14+
let stream = stream.into_stream();
15+
16+
Box::pin(async move {
17+
pin_utils::pin_mut!(stream);
18+
19+
let mut out = String::new();
20+
out.stream_extend(stream).await;
21+
out
22+
})
23+
}
24+
}
25+
26+
impl<'b> FromStream<&'b char> for String {
27+
#[inline]
28+
fn from_stream<'a, S: IntoStream<Item = &'b char>>(
29+
stream: S,
30+
) -> Pin<Box<dyn core::future::Future<Output = Self> + 'a>>
31+
where
32+
<S as IntoStream>::IntoStream: 'a,
33+
{
34+
let stream = stream.into_stream();
35+
36+
Box::pin(async move {
37+
pin_utils::pin_mut!(stream);
38+
39+
let mut out = String::new();
40+
out.stream_extend(stream).await;
41+
out
42+
})
43+
}
44+
}
45+
46+
impl<'b> FromStream<&'b str> for String {
47+
#[inline]
48+
fn from_stream<'a, S: IntoStream<Item = &'b str>>(
49+
stream: S,
50+
) -> Pin<Box<dyn core::future::Future<Output = Self> + 'a>>
51+
where
52+
<S as IntoStream>::IntoStream: 'a,
53+
{
54+
let stream = stream.into_stream();
55+
56+
Box::pin(async move {
57+
pin_utils::pin_mut!(stream);
58+
59+
let mut out = String::new();
60+
out.stream_extend(stream).await;
61+
out
62+
})
63+
}
64+
}
65+
66+
impl FromStream<String> for String {
67+
#[inline]
68+
fn from_stream<'a, S: IntoStream<Item = String>>(
69+
stream: S,
70+
) -> Pin<Box<dyn core::future::Future<Output = Self> + 'a>>
71+
where
72+
<S as IntoStream>::IntoStream: 'a,
73+
{
74+
let stream = stream.into_stream();
75+
76+
Box::pin(async move {
77+
pin_utils::pin_mut!(stream);
78+
79+
let mut out = String::new();
80+
out.stream_extend(stream).await;
81+
out
82+
})
83+
}
84+
}
85+
86+
impl<'b> FromStream<Cow<'b, str>> for String {
87+
#[inline]
88+
fn from_stream<'a, S: IntoStream<Item = Cow<'b, str>>>(
89+
stream: S,
90+
) -> Pin<Box<dyn core::future::Future<Output = Self> + 'a>>
91+
where
92+
<S as IntoStream>::IntoStream: 'a,
93+
{
94+
let stream = stream.into_stream();
95+
96+
Box::pin(async move {
97+
pin_utils::pin_mut!(stream);
98+
99+
let mut out = String::new();
100+
out.stream_extend(stream).await;
101+
out
102+
})
103+
}
104+
}

src/string/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//! The Rust core string library
2+
//!
3+
//! This library provides a UTF-8 encoded, growable string.
4+
5+
mod extend;
6+
mod from_stream;
7+
8+
#[doc(inline)]
9+
pub use std::string::String;

0 commit comments

Comments
 (0)