Search code examples
genericsrustbounds

Rust: generic must be implement &xx[...]


I'm trying to implement a generic struct where it's clear from the beginnging that it's element 'provider' must be of a type that supports &myvar.provider[..] later on. But I'm unable to figure the correct Bound for this.

pub struct MyStruct<T: ??> {  // T must support &x.provider[..]
   pub provider: T,
}

Thanks a lot for your help

UPDATE: extended example. What I'm trying to achieve: data blocks can be up to 10GB. They can be provided as static b"aa" mostly for testing, read the file content into memory, or mmap the file content. No matter the provider, when processing the data we only use &[u8].

//
// A minimal example for my problem
//

use std::str;
use std::error::Error;
use std::fs::File;
use std::io::prelude::*;
use std::boxed::Box;
use memmap::{MmapOptions, Mmap};

#[derive(Debug)]
pub struct DBFileBuilder<T> {  // what is the correct bound? T must support &x[..]
    pub filename: String,
    pub provider: Option<T>,
    pub startpos: usize,
    // ... several more 
}

pub struct DBFile<'a, T> {  // what is the correct bound? T must support &x[..]
    pub filename: String,
    pub provider: T,
    pub data: &'a [u8],
    // ... several more 
}

impl<T> DBFileBuilder<T> {
    fn default() -> Self {
        Self { 
            filename: String::default(), 
            provider: None,
            startpos: 0,
        }
    }

    pub fn from_bytes(data: &[u8]) -> DBFileBuilder<&[u8]> {
        DBFileBuilder { 
            provider: Some(&data),
            ..DBFileBuilder::default() 
        }
    }

    pub fn read_file(filename: &str) -> Result<DBFileBuilder<Box<[u8]>>, Box<dyn Error>> {
        let mut file = File::open(&filename)?;
        let fsize = file.metadata()?.len();

        let mut provider = vec![0_u8; fsize as usize].into_boxed_slice();
        let n = file.read(&mut provider)?;

        Ok(DBFileBuilder {
            filename: filename.to_string(),
            provider: Some(provider),
            ..DBFileBuilder::default()
        })
    }

    pub fn mmap_file(filename: &str) -> Result<DBFileBuilder<Mmap>, Box<dyn Error>> {
        let file = File::open(&filename)?;
        let provider = unsafe { MmapOptions::new().map(&file)? };

        Ok(DBFileBuilder {
            filename: filename.to_string(),
            provider: Some(provider),
            ..DBFileBuilder::default()
        })
    }

    pub fn init(&mut self) {
    }

    pub fn build<'a>(self) -> DBFile<'a, T> {
        let provider = self.provider.expect("Provider not initialized");
        self.init();
        let data = &provider[self.startpos ..];

        DBFile {
            filename: self.filename,
            provider,
            data,
        }
    }
}

impl<'a, T> DBFile<'a, T> {
    pub fn run(&self) { 
        return self.process(self.data)
    } 

    pub fn process(&self, data: &[u8]) { 
        println!("data: {:?}", &data); 
    }
}

Solution

  • The necessary trait bound to index by usize closed Ranges to get bytes is Index<Range<usize>, Output=[u8]>. You'll probably also want to index by infinite ranges:

    pub struct MyStruct<T>
    where T: Index<Range<usize>, Output=[u8]>
       + Index<RangeTo<usize>, Output=[u8]>
       + Index<RangeFrom<usize>, Output=[u8]>
       + Index<RangeFull, Output=[u8]>
    {
        pub provider: T,
    }
    

    Unforunately, these bounds currently aren't inferred in signatures where MyStruct is used (although I believe this is in the works), which can get very unwieldy. If you use Nightly, you can define a trait alias (RFC1733) to make this less verbose.

    That said, you'll need to rethink the example as it wouldn't work: E.g. in build() the data can't be part of the returned DBFile because it points to a local variable which doesn't live long enough.