Why I have to import traits from std::io::prelude to use read from TcpStream

Hello,
given the following code from the Rust book.

use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;

...

fn handle_connection(mut stream: TcpStream) {
    let mut buffer = [0; 1024];

    stream.read(&mut buffer).unwrap();

    println!("Request: {}", String::from_utf8_lossy(&buffer[..]))
}

Why do I need to import "use std::io::prelude::*;" so I can use stream.read? I mean that the read method is already implemented in TcpStream. I don't understand why I need to import the traits from std::io::prelude.

Because another trait could also have a read method. Forcing you to import traits explicitly allows you to ignore conflicts in method names when they aren't relevant.

The fully qualified form of the method call syntax for that read call looks something like

<std::net::TcpStream as std::io::Read>::read(&mut stream, &mut buffer).unwrap();

The compiler needs to know which trait name to fill in there in the expanded form.

It gives you a bunch of extra flexibility, at the cost of needing some extra imports[1] you wouldn't need in languages that don't allow those kinds of name collisions on methods


  1. or use of the fully qualified syntax ↩ī¸Ž

1 Like

Trait methods can be added by anyone to any type. Rust even supports "blanket implementations" that implement a trait method for all types in existence (e.g. everything has From::from for itself).

Someone else could implement a different read method on the Stream type you're using. Stream doesn't just have the read method. It has potentially infinite number of different read methods. If someone else made a trait with read, it wouldn't be clear whether you want read from io::Read, or read from somerandomdependency::Reader.

In other languages this is done via "monkey patching" and due to being global and affecting unrelated code, is generally considered a bad practice. To avoid such problem, Rust's traits aren't global and automatically applied to types, but explicitly brought into scope.

I think that's the root of confusion. read isn't implemented in TcpStream. It is defined in the std::io::Read trait. That trait has an implementation for TcpStream, allowing you to use TcpStream::read(&mut self, &mut buf) as if it were Read::read(&mut self, &mut buf), but it is not inherent to TcpStream. Someone somewhere could very well make a new trait CustomRead with a method CustomRead::read and an implementation for TcpStream. Without an explicit trait import at use site, the desired implementation would be impossible to determine both for the reader and the compiler.

If read was really implemented on TcpStream, i.e. there were the following code:

impl TcpStream {
    pub fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { ... }
}

then you could indeed use it without importing any traits. You wouldn't even need to import TcpStream explicitly, if the variable were known to be of this type (e.g. some function from a different module claimed to return a TcpStream).

This is because inherent methods (i.e. as above) are always prioritized over trait methods during the method name resolution. If we had an inherent method as above, and you wanted to call a trait method instead, you would have to provide disambiguation using the fully qualified method call syntax:

<TcpStream as io::Read>::read(&mut stream, &mut buf)
1 Like