How to use a std::io::Read as a function parameter?

Hello, I am converting some of my Go code to Rust as a learning exercise. In Go, I often used an io.Reader parameter in functions so anything that implemented it, could be processed by my function. I am trying to figure out how to do the same in Rust, but I seem to be going in circles. I'd appreciate your suggestions. Here is the Go code and my stab at the Rust code... Thank you for your time and interest!

GO CODE

package main

import (
	"fmt"
	"io"
	"os"
	"strings"
)

func main() {
	// Use the count_bytes function with a string.
	sr := strings.NewReader("Howdy!")
	if n, err := count_bytes(sr); err == nil {
		fmt.Printf("\nCount: %d", n)
	} else {
		fmt.Printf("failed to count bytes from a string: %v", err)
	}

	// Use the count_bytes function with a file.
	if fr, err := os.Open("foo.txt"); err == nil {
		if n, err := count_bytes(fr); err == nil {
			fmt.Printf("\nCount: %d", n)
		} else {
			fmt.Printf("failed to count bytes from a file: %v", err)
		}
	} else {
		fmt.Printf("failed to open the input file: %v", err)
	}
}

func count_bytes(src io.Reader) (int, error) {

	buf := make([]byte, 256)

	if n, err := src.Read(buf); err == nil {
		return n, nil
	} else {
		return -1, fmt.Errorf("\nfailed to read from src: % w", err)
	}
}

RUST CODE

use std::fs::File;
use std::io::{Read};

fn count_bytes<R: Read>(mut src: Box<dyn Read>) -> Result<usize, Err>{
    let mut buf = Vec::new();

    let cnt = src.read_to_end(&mut buf).unwrap_or(Result(0, Err("failed to read the content")));
    return Result::Ok(cnt);
}

fn main() {
    let mut f = File::open("foo.txt")?;
    count_bytes(f);
}

Your options are:

fn count_bytes<R: Read>(reader: R)...

fn count_bytes(reader: impl Read)... 

fn count_bytes(&mut dyn Read)... 

The first two are mostly equivalent, only distinction being that the caller of the function using impl Trait argument can't explicity specify the desired type.
The last one uses dynamic dispatch instead of monomorphising the function for each type it's used with and so most similliar to what Go does for interface args.

3 Likes

The src argument should have type R. That's why you introduced the R type parameter in the first place.

3 Likes

Here's a cleaned up version with some comments that explain some of the things your code seemed unclear on

use std::fs::File;
use std::io::{
    // Import the module `std::io` as it's name `io` so we can use it more succinctly
    self,
    Read,
};

/// You can use a generic function to accept any reader, use the type parameter `R` instead of the interface name in the argument position.
/// You can take a mutable reference to the reader instead of taking the reader by value. This is usually more flexible
fn count_bytes<R: Read>(src: &mut R) -> Result<usize, io::Error> {
    let mut buf = Vec::new();

    let cnt = src.read_to_end(&mut buf)?;

    Ok(cnt)
}

/// You can use a trait object instead of a generic function. This is technically closer to the Go version, since it doesn't use generics. Generally using a generic function has fewer limitations though.
/// Using a mutable reference is even more advantageous here, because it allows us to skip the `Box` while still using a trait object.
fn count_bytes_dyn(src: &mut dyn Read) -> Result<usize, io::Error> {
    let mut buf = Vec::new();

    let cnt = src.read_to_end(&mut buf)?;

    Ok(cnt)
}

/// You can make `main` return a `Result` to allow using the `?` operation in `main`. The program will exit unsuccessfully if an error is returned from `main`
fn main() -> Result<(), io::Error> {
    let mut f = File::open("foo.txt")?;
    count_bytes(&mut f)?;

    Ok(())
}

/// You can use `expect` (or `unwrap`) to cause the function to panic instead of returning an error. In the case of `expect` this lets you add detail to the error message.
/// Most of the time returning `Result`s is a better idea though.
fn main_expect() {
    let mut f = File::open("foo.txt").expect("Failed to open file");
    count_bytes_dyn(&mut f).expect("Failed to count bytes");
}
1 Like

I am experimenting with your responses and finding more clarity in the code.
Thank you for helping a newbie make more progress in his learning!
I am certain that other Go developers will benefit, too! :slight_smile:

Mike

I want to thank you, especially for marking up my code so I could see how the alternatives would be implemented. It was extremely helpful! :slight_smile:

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.