Why the File can not match Read+Seek in the code?

use std::collections::HashMap;
use std::io::{self, Cursor, Read, Seek, SeekFrom};

pub struct Table<T: Read + Seek> {
    content: T,
}

pub struct Cache<T: Read + Seek> {
    map: HashMap<u64, Table<T>>,
}

impl<T: Read + Seek> Cache<T> {
    fn new() -> Self {
        Self {
            map: HashMap::new(),
        }
    }

    fn get_table(&self, file_num: u64) -> &Table<T> {
        match self.map.get(&file_num) {
            Some(table) => table,
            None => {
                let mut file = std::fs::OpenOptions::new()
                    .read(true)
                    .open(format!("{}.txt", file_num))
                    .unwrap();
                let table = Table { content: file };
                self.map.insert(file_num, table);
                self.map.get(&file_num).unwrap()
            }
        }
    }
}

fn main() {
    let mut file = std::fs::OpenOptions::new()
        .read(true)
        .open("example.txt")
        .unwrap();
    file.seek(SeekFrom::Start(0));
    file.read(&mut [0; 10]);
}

playground

I put your code into Rust Playground to see the error for myself - quoting the error is often helpful, and a Rust Playground link is even more helpful.

The issue is that T: Read + Seek means "for a caller-chosen value of T, which must implement both Read and Seek"; in Cache::get_table, however, you choose the value of T to be std::fs::File, which doesn't have to be the thing the caller chose (I could choose T to be Cursor<Vec<u8>> instead, for example, which would mean that the return from open would have to be a Cursor<Vec<u8>>, not a File).

If I rewrite Cache to not have a type parameter, then it works:

pub struct Cache {
    map: HashMap<u64, Table<File>>,
}

impl Cache {
    fn new() -> Self {
        Self {
            map: HashMap::new(),
        }
    }

    fn get_table(&mut self, file_num: u64) -> &Table<File> {
        self.map.entry(file_num).or_insert_with(|| {
            let file = std::fs::OpenOptions::new()
                .read(true)
                .open(format!("{}.txt", file_num))
                .unwrap();
            Table { content: file }
        })
    }
}

Note the use of the Entry API to deal with borrow-checking issues around having insert inside the match.

4 Likes