Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't Read::read_to_string() return the string?

In the Read trait, many functions/methods take a buf: &mut XXX as (one of the) parameter(s) and return Result<usize>. For example, read_to_string() takes buf: &mut String as one of the parameters and returns Result<usize>.

As I came from many languages, the so-called modern ones usually return the string for a read_string() function call. The design of the Read trait in Rust kind of shocks me because it doesn't return the string, but rather passes it as a parameter (similar to C or other more primitive languages).

I'm aware the return value (Result) is used to indicate whether the read succeeded or not, and it can be passed to match expressions. (I've learned Lisp and Go previously, so I'm not questioning this kind of design.)

Why didn't the core developers design this trait as "returning the string together with the error message"?

For example, why not design it like this:

fn read(&mut self) -> Result<String> { ... }

The String also contains the length, so the user can access the length by calling .len() when needed. The user can concatenate it to any other String if (s)he wishes, but the key point is that there is no need to create / have one before calling this function.

Is there any particular reasons to design this trait like what it is now?

p.s. I'm still learning Rust (by following The Rust Programming Language), and am reading the second edition after reading most of the first edition. Please correct me if the code above contains any errors (especially when dealing with lifetimes).

like image 445
renyuneyun Avatar asked Apr 07 '18 12:04

renyuneyun


2 Answers

I do not know the real reason but there are some advantages:

  • You control how the String is allocated. You can take it from a pool, reuse it, etc.
  • You can read to the same string from multiple read_to_string (or similar) calls. There's no need to concatenate strings
  • The size of the resulting string (in the case of passing as parameter) does not tell you how many bytes you have read (because the String can be non-empty), this is why usize is returned in a Result

The signature

fn read_to_string(&mut self) -> Result<String> { ... }

might look more natural if coming from higher level languages but it gives no control over the String and its allocation.

like image 58
Arjan Avatar answered Sep 21 '22 02:09

Arjan


RFC 517 (IO reform) states (emphasis mine):

The read_to_end and read_to_string methods now take explicit buffers as input. This has multiple benefits:

  • Performance. When it is known that reading will involve some large number of bytes, the buffer can be preallocated in advance.

  • "Atomicity" concerns. For read_to_end, it's possible to use this API to retain data collected so far even when a read fails in the middle. For read_to_string, this is not the case, because UTF-8 validity cannot be ensured in such cases; but if intermediate results are wanted, one can use read_to_end and convert to a String only at the end.

Before Rust 1.0, Reader::read_to_string did return a String. It was a deliberate decision to move away from that.

A read_and_create_string function can be created "on top" of Read::read_to_string, but the opposite is not true. Code in the standard library needs to be very flexible.

like image 20
Shepmaster Avatar answered Sep 21 '22 02:09

Shepmaster