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

add back bufferless read_to_string/read_to_end methods #970

Closed
wants to merge 3 commits into from

Conversation

cburgdorf
Copy link

The Read trait lost it's read_to_string and read_to_end methods that didn't require to pass a buffer. That comes as a small convenience loss. @steveklabnik suggested to write a PR to bring them back.

Rendered view

@cburgdorf cburgdorf changed the title add RFC 841 to add back bufferless read_to_string/read_to_end methods add back bufferless read_to_string/read_to_end methods Mar 12, 2015
@cburgdorf cburgdorf force-pushed the read_to_string branch 2 times, most recently from 629b0b4 to a17468d Compare March 12, 2015 23:38

But we'd like to be able to just write

Or this:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a missing example before this?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's just a superfluous Or this: ;) I updated the PR


`fn read_to_end(&mut self) -> Result<Vec<u8>>`

`fn read_to_string(&mut self) -> Result<String>`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't these conflict with the existing read_to_end and read_to_string methods?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. What's the state of method overloading in Rust? I've been basically away from Rust for the last couple of months which means I have to start over. If there's no such thing as method overloading I'd need to give them other names.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can provide multiple methods of the same name from differing traits, and use UFCS (ie ReadExt::read_to_end(&mut rdr, Vec::new())) to differentiate. But that would not improve the convenience you mentioned in this RFC. You'd need a new name.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

read_into_vec and read_into_string, possibly

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@reem I like those, updated the PR

@netvl
Copy link

netvl commented Mar 13, 2015

👍. There should be a simple way to do simple things.

@cburgdorf
Copy link
Author

Also created a PR against the source rust-lang/rust#23335

@alexcrichton
Copy link
Member

There were two concerns which motivated moving to taking a buffer instead of returning it, the first of which you've discussed here (performance), but perhaps the more important one is the loss of information. The APIs for read_into_{vec,string} can read data but then discard it if an error occurs. This form of "lossy API" we've tried to avoid having show up as much as possible, so you may want to also discuss this factor in the Motivation section to indicate why these functions are worth it.

@cburgdorf
Copy link
Author

Sorry @alexcrichton I may not see the forrest for the trees.

How is this read_into_string implementation handling the error situation differently than the read_to_string API which it internally uses?

    fn read_into_string(&mut self, buf: &mut String) -> Result<String> {
        let mut buf = String::new();
        let res = self.read_to_string(&mut buf);
        res.map(|_| buf)
    }

@seanmonstar
Copy link
Contributor

With read_to_string, even if there's an error, all previously read data has
still been pushed onto the String. With the proposed read_into_string, the
String is lost if an error occurs, even if there was success before the
error. That data is likely lost to the void.

On Sun, Mar 15, 2015, 3:13 PM Christoph Burgdorf [email protected]
wrote:

Sorry @alexcrichton https://github.com/alexcrichton I may not see the
forrest for the trees.

How is this read_into_string implementation handling the error situation
differently than the read_to_string API which it internally uses?

fn read_into_string(&mut self, buf: &mut String) -> Result<String> {
    let mut buf = String::new();
    let res = self.read_to_string(&mut buf);
    res.map(|_| buf)
}


Reply to this email directly or view it on GitHub
#970 (comment).

@cburgdorf
Copy link
Author

Ah, sure. I see it. How about a special error type that will include the String. Then even if there was an error one could get the bits that were read successfully up to that point.

@alexcrichton
Copy link
Member

@cburgdorf

Ah, sure. I see it. How about a special error type that will include the String. Then even if there was an error one could get the bits that were read successfully up to that point.

It is plausible to create a ReadIntoStringError which has a FromError implementation translating it to io::Error, but this is a lot of extra API surface for what doesn't seem like much gain. Almost all one-liners previously earn at most one extra line with a take-the-mutable-buffer approach and it allows us to knock out even more use cases in one stroke.

@cburgdorf
Copy link
Author

but this is a lot of extra API surface for what doesn't seem like much gain

I understand the stance you take on that. We probably look at this from too different perspectives.

For me, it just feels strange not to have such methods on the Read trait. People coming from .NET, Java, Node.js or Go may be quite surprised not to have this.

The way I see it, we should follow the principle of least astonishment here.

@aturon aturon self-assigned this Mar 19, 2015
@rkjnsn
Copy link
Contributor

rkjnsn commented Mar 19, 2015

I like this. Getting the contents as a newly-created String or Vec is a common enough operation that I think there should be a convenient way to do it. I also like that makes chaining easier.

The APIs for read_into_{vec,string} can read data but then discard it if an error occurs.

I don't think this should be a concern. Even with the current read_to_* methods, I'm going to be using try! most of the time, so my stack-allocated String or Vec is going to be immediately discarded, anyway. Any function that needs the partially-read data can continue to use the existing methods.

I definitely don't think it's worth adding a ReadIntoStringError, as it makes the interface more complicated for very little gain.

I think combined with read_all (#980), and maybe a write_str (write_all for string slices), this would yield a nicely flushed out pair of Read and Write traits.

@reem
Copy link

reem commented Mar 19, 2015

I think we should wait to see how much of a problem this is once we have a larger body of rust code and add this later. This is a very easy API to add later but quite an awkward one to keep around if we decide we don't want any data-losing APIs .

@Diggsey
Copy link
Contributor

Diggsey commented Mar 24, 2015

Instead of specifically a ReadIntoStringError, there could be a PartialReadError or similar, which can be used by any read operation which might fail partway through. The error would allow access to the partially read data.

@cburgdorf
Copy link
Author

Ok, for now I created a read-ext that aims to bring those APIs as extension methods. It's available on crates.io.

I didn't manage to get the tests working yet, so I'm not actually sure if it works.

@cburgdorf
Copy link
Author

Never mind, it's working now with version 0.1.0

@archer884
Copy link

Commenting just because I'm an example of one of those .NET guys who was surprised (astonished! :P) to find there was no trivial way to do this. I guessed why it wasn't there (the reasons mentioned above) and figured the justification was that it's so easy to add that, if I want it, I'm expected to do it myself... But I'd be totally cool with this being a thing I didn't have to implement, too. :)

@cburgdorf
Copy link
Author

because I'm an example of one of those .NET guys

Haha, awesome. You are validating my theory ;)

I'd be totally cool with this being a thing I didn't have to implement

For now just use my readext crate

@jnicklas
Copy link

jnicklas commented Apr 2, 2015

Imagine the case of reading a configuration file. This will be done once at the start of the program, so is hardly performance critical, and there isn't much use in loading half a configuration file, so it's really all or nothing. That kind of thing is a lot less convenient with the revised IO APIs than it was before.

@aturon
Copy link
Member

aturon commented Apr 9, 2015

Thanks for the RFC!

The goal of the recent IO reform was to provide high-performance, low-level APIs that map as directly to system APIs as possible, and can be used to build various higher-level abstractions and conveniences.

While additional conveniences as proposed here may eventually be prudent to add to std, we would prefer to give the new IO system some time to bake, so that we can gain broader experience with the pain points. (We've been working hard to pare down the commitments in std to those we feel very confident about.) So for the time being, having this kind of functionality live in the crates.io ecosystem seems like the right step.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.