Using a generic function pointer with a trait bound

Hi all,

I'm trying to use a generic function pointer with a trait bound, and pass it to a function:

use std::fs::File;
use std::io::{BufRead, BufReader};

type ReaderFn<R: BufRead> = fn(R);

fn read_file<R: BufRead>(rdr: R) {
    for line in rdr.lines() {
        println!("{}", line.unwrap());
    }
}

fn call<R: BufRead>(func: ReaderFn<R>) {
    let f = File::open("/tmp").unwrap();
    let rdr = BufReader::new(f);

    // this works
    read_file(rdr);
    
    // this doesn't
    func(rdr);
}

But I've got this error:

error[E0308]: mismatched types
  --> src/main.rs:20:10
   |
12 | fn call<R: BufRead>(func: ReaderFn<R>) {
   |         - this type parameter
...
20 |     func(rdr);
   |          ^^^ expected type parameter `R`, found struct `BufReader`
   |
   = note: expected type parameter `R`
                      found struct `BufReader<File>`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground`

To learn more, run the command again with --verbose.

I don't know why it works calling a simple func but not with the function pointer. I tried with a

F: Fn ->

kind of construct but no success.

Any idea ?

Thanks a lot.

This means that the caller can pick any type R that implements BufRead and provide a func that takes R as an argument. In the body of the function, you try to pass a BufReader to func, which might not be the R that the caller picked.

This is the syntax you need if you're always going to be passing a BufReader to func:

fn call(func: ReaderFn<BufReader>) { /* ... */ }

It fails to compile because your function, semantically, isn't really generic:

fn call<R: BufRead>(func: ReaderFn<R>) {
    let f = File::open("/tmp").unwrap();
    let rdr = BufReader::new(f);

    // this works
    read_file(rdr);
    
    // this doesn't
    func(rdr);
}

R would always be BufReader, so there's no point in making it generic.
There's also this problematic usage of your function:

fn foo() {
    let data: &[u8] = &[1, 2, 3, 7, 8];
    call::<&[u8]>(|r| {});
}

What should R be? Should it be BufReader because that's what is used in the call to func in the function call? Or should R be &[u8] because that's what the caller chose?


It seems that what you want is a function that wraps a Reader in a BufReader and then performs some action with it. The following might be what you want:

// other imports here...
use std::io::Read;

fn call2<R: Read>(func: ReaderFn<BufReader<R>>, r: R) {
    let rdr = BufReader::new(r);
    func(rdr);
}

@2e71828 @ArifRoktim Thanks for your suggestions. Is it something I can achieve with a F: Fn -> like Fn type ?

Using an Fn trait bound:

fn call<F: Fn(R), R: BufRead>(func: F) {
    let f = File::open("/tmp").unwrap();
    let rdr = BufReader::new(f);

    // this works
    read_file(rdr);
    
    // this doesn't
    func(rdr);
}

gives the same error. The error isn't related to the function you're taking as a parameter. The error is in the generic type R.
If you'll only ever take a BufReader, I think you should either remove the generic type completely and use

Or add another argument to the function to pass in the BufRead:

fn call<R: BufRead>(func: ReaderFn<R>, r: R) {
    func(r)
}

I'm using any BufReader<T>, T being either File or GzDecoder<File> or another type implementing BufRead

That T has to be specified as a concrete type somewhere in your program. Is that inside the implementation of call() or somewhere else? If it's somewhere else, how is that information given to call()?

Maybe you have multiple call() implementations, and they all use different T's?

Let me elaborate a little bit. Here is my function:

    ///Just a wrapper function for a file.
    pub fn lookup(&mut self, wrapper: &mut Wrapper) -> Result<Vec<ChildData>, AppError> {
        // open target file
        let file = File::open(&self.path)?;

        // if file is compressed, we need to call a specific reader
        // create a specific reader for each compression scheme
        match self.compression {
            CompressionScheme::Gzip => {
                let decoder = GzDecoder::new(file);
                let reader = BufReader::new(decoder);
                self.lookup_from_reader(reader, wrapper)
            }
            CompressionScheme::Bzip2 => {
                let decoder = BzDecoder::new(file);
                let reader = BufReader::new(decoder);
                self.lookup_from_reader(reader, wrapper)
            }
            CompressionScheme::Xz => {
                let decoder = XzDecoder::new(file);
                let reader = BufReader::new(decoder);
                self.lookup_from_reader(reader, wrapper)
            }
            CompressionScheme::Uncompressed => {
                let reader = BufReader::new(file);
                self.lookup_from_reader(reader, wrapper)
            }
        }
    }

where

   fn lookup_from_reader<R: BufRead >(
        &mut self,
        mut reader: R,
        wrapper: &mut Wrapper,
    ) -> Result<Vec<ChildData>, AppError> {}

What I want is to call lookup with other private functions (like lookup_from_reader) without rewriting the lookup one.

All these fn are belonging to the same struct.

That's a bit tricky; the simplest solution is dynamic dispatch, but someone else may have a clever static-dispatch solution to offer. (The + '_ annotation on dyn is to allow temporary references. If you don't specify a lifetime, it defaults to 'static)

(Untested)

fn lookup_from_reader(
        &mut self,
        reader: &mut dyn BufRead + '_,
        wrapper: &mut Wrapper,
    ) -> Result<Vec<ChildData>, AppError> {}

///Just a wrapper function for a file.
    pub fn lookup<F>(&mut self, wrapper: &mut Wrapper, from_reader: F) -> Result<Vec<ChildData>, AppError>
    where F: for<'a> FnOnce(&mut dyn BufRead+'a, &mut Wrapper)->Result<Vec<ChildData>, AppError> {
        // open target file
        let file = File::open(&self.path)?;

        // if file is compressed, we need to call a specific reader
        // create a specific reader for each compression scheme
        match self.compression {
            CompressionScheme::Gzip => {
                let decoder = GzDecoder::new(file);
                let reader = BufReader::new(decoder);
                from_reader(&mut reader, wrapper)
            }
            CompressionScheme::Bzip2 => {
                let decoder = BzDecoder::new(file);
                let reader = BufReader::new(decoder);
                from_reader(&mut reader, wrapper)
            }
            CompressionScheme::Xz => {
                let decoder = XzDecoder::new(file);
                let reader = BufReader::new(decoder);
                from_reader(&mut reader, wrapper)
            }
            CompressionScheme::Uncompressed => {
                let reader = BufReader::new(file);
                from_reader(&mut reader, wrapper)
            }
        }
    }

@2e71828 This is really tricky !

I tried another solution with a generic trait with no success either (I'll create a topic soon). I succeded using an Enum by I don't like too much this solution.

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.