Any difference in taking ownership over borrowing in that code?

A question from the guy who just reached the final chapter of the official tutorial :crab: :tada:
In the example of building a single-threaded web server handle_connection function is defined as following:

fn handle_connection(mut stream: TcpStream)

that is, taking ownership of TcpStream and performing mutual binding.

The questions is, would it be any difference in rewriting the code using borrowing instead?

fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

    for stream in listener.incoming() {
        let mut stream = stream.unwrap();
        handle_connection(&mut stream);
    }
}

fn handle_connection(stream: &mut TcpStream) {
    ...
}

I mean, functionally I believe both variants are the same and for me it doesn't matter where the memory allocated for stream is freed - after handle_connection ends or after the loop iteration; but perhaps there are some style guidelines or recommendations for choosing one approach over another?

Thanks a lot

This sounds like you're correctly understanding the situation.

I think this comes down to whether you want handle_connection to support the connection being kept open and used further after handle_connection or not. I guess in this case, terminating the TCP stream (by dropping the TcpStream value) is supposed to be part of the behavior of handle_connection so taking ownership of the parameter and dropping it in the end makes sense.

Furthermore, in the next chapter, when multi threading comes into play, the version with a &mut TcpStream would no longer work (well, at least when the move keyword isn't used), so there might also be some foreshadowing for that in the signature of handle_connection.

Good question.

What I liked about the original approach is the "processing" of the memory allocation more is complete. I like the idea of transferring ownership when the current scope has no further use for it; it will drop the memory when it's done with it, not when my scope is over (so better memory management).

It also keeps my mental model of what memory I have "going on" in the current scope a little more explicit and accounted for.

Finally, the approach is more flexible in that I (the current scope) truly don't care if the function wants to subsequently mutate the memory or not. The function can worry about recasting it as a mut as needed without muddying up my current scope. So, it's in a sense "more generic".

All in all, I try to keep ownership where it needs to be. If I don't have a reason to re-use it in the current scope, I give up ownership. Don't hoard ownership! :))

1 Like