简体   繁体   English

Rust:从 function 返回一个通用结构,其中(仅)<t> 是不同的</t>

[英]Rust: return a generic struct from a function where (only) <T> is different

I'm looking for help with the correct syntax or Rust approach.我正在寻求有关正确语法或 Rust 方法的帮助。 My use case: I have a generic struct FileData , which has a variable called provider .我的用例:我有一个通用的 struct FileData ,它有一个名为provider的变量。 Provider must implement AsRef<[u8]> so that data may come from static bytes, heap allocated memory, memory mapped, and possibly others.提供者必须实现AsRef<[u8]>以便数据可能来自 static 字节、堆分配的 memory、memory 映射,以及可能的其他数据。 I have a couple methods to create FileData and they seem to be working well.我有几种创建FileData的方法,它们似乎运行良好。 But there is one但是有一个

// ERROR: This is the line that I do not get right 
pub fn from_file<P: AsRef<Path>>(filename: P, mmap: bool) -> Result<FileData<T>, Box<dyn Error>> {
    if mmap == true {
        return FileData::mmap_file(filename)
    } else {
        return FileData::read_file(filename)
    }
}

which I don't get right.我不明白。 The method always returns a FileData, back depending on the 'mmap' argument, <T> is different.该方法总是返回一个 FileData,具体取决于 'mmap' 参数, <T>不同。 It can either be <Box<[u8]> or <Mmap> .它可以是<Box<[u8]><Mmap>

I searched for similar questions and articles, but could find one that matches my situation, eg (1) , (2) , (3) .我搜索了类似的问题和文章,但可以找到与我的情况相匹配的文章,例如(1)(2)(3)

#[derive(Debug)]
pub struct FileData<T: AsRef<[u8]>> {
    pub filename: String,              
    pub provider: T,                   // data block, file read, mmap, and potentially more
    pub fsize: u64,                    
    pub mmap: bool,                    
}

impl FileData<&[u8]> {
    /// Useful for testing. Create a FileData builder based on some bytes. 
    #[allow(dead_code)]
    pub fn from_bytes(data: &'static [u8]) -> Self {
        FileData {
            filename: String::new(),
            provider: data,
            fsize: data.len() as _,
            mmap: false,
        }
    }
}

pub fn path_to_string<P: AsRef<Path>>(filename: P) -> String {
    return String::from(filename.as_ref().to_str().unwrap_or_default());
}

pub fn file_size(file: &File) -> Result<u64, Box<dyn Error>> {
    Ok(file.metadata()?.len())
}

impl FileData<Box<[u8]>> {
    /// Read the full file content into memory, which will be allocated on the heap.
    #[allow(dead_code)]
    pub fn read_file<P: AsRef<Path>>(filename: P) -> Result<Self, Box<dyn Error>> {
        let mut file = File::open(&filename)?;
        let fsize = file_size(&file)?;

        let mut provider = vec![0_u8; fsize as usize].into_boxed_slice();
        let n = file.read(&mut provider)? as u64;
        assert!(fsize == n, "Failed to read all data from file: {} vs {}", n, fsize);

        Ok(FileData {
            filename: path_to_string(&filename),
            provider: provider,
            fsize: fsize,
            mmap: false,
        })
    }
}

impl FileData<Mmap> {
    /// Memory Map the file content
    #[allow(dead_code)]
    pub fn mmap_file<P: AsRef<Path>>(filename: P) -> Result<Self, Box<dyn Error>> {
        let file = File::open(&filename)?;
        let fsize = file_size(&file)?;
        let provider = unsafe { MmapOptions::new().map(&file)? };

        Ok(FileData {
            filename: path_to_string(&filename),
            provider: provider,
            fsize: fsize,
            mmap: true,
        })
    }
}

impl<T: AsRef<[u8]>> FileData<T> {
    #[allow(dead_code)]
    pub fn from_file<P: AsRef<Path>>(filename: P, mmap: bool) -> Result<FileData<_>, Box<dyn Error>> {
        if mmap == true {
            return FileData::mmap_file(filename)
        } else {
            return FileData::read_file(filename)
        }
    }

    pub fn as_ref(&self) -> &[u8] {
        return self.provider.as_ref()
    }
}

The error message is:错误信息是:

error[E0308]: mismatched types
  --> src\data_files\file_data.rs:87:20
   |
83 | impl<T: AsRef<[u8]>> FileData<T> {
   |      - this type parameter
84 |     #[allow(dead_code)]
85 |     pub fn from_file<P: AsRef<Path>>(filename: P, mmap: bool) -> Result<FileData<T>, Box<dyn Error>> {
   |                                                                  ----------------------------------- expected `std::result::Result<file_data::FileData<T>, 
std::boxed::Box<(dyn std::error::Error + 'static)>>` because of return type
86 |         if mmap == true {
87 |             return FileData::mmap_file(filename)
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `T`, found struct `Mmap`
   |
   = note: expected enum `std::result::Result<file_data::FileData<T>, _>`
              found enum `std::result::Result<file_data::FileData<Mmap>, _>`

Generics give the caller the right to decide what the return type of the function should be. Generics 赋予调用者决定 function 的返回类型应该是什么的权利。 Right now your function, the callee , is deciding the return type, which is why you're getting compiler errors.现在你的 function ,被调用者,正在决定返回类型,这就是你得到编译器错误的原因。

You can refactor the code to give the right back to the caller by implementing an additional trait, IntoFileData , and then adding that as a trait bound to your generic FileData<T> implementation.您可以通过实现附加特征IntoFileData重构代码以将权利还给调用者,然后将其作为绑定到通用FileData<T>实现的特征添加。 Simplified commented example:简化注释示例:

use memmap::Mmap;
use memmap::MmapOptions;
use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::path::Path;

// simplified FileData for brevity
struct FileData<T: AsRef<[u8]>> {
    provider: T,
}

// new trait for converting types into FileData
trait IntoFileData<T: AsRef<[u8]>> {
    fn from_path(path: &Path) -> Result<FileData<T>, Box<dyn Error>>;
}

impl IntoFileData<Box<[u8]>> for Box<[u8]> {
    fn from_path(path: &Path) -> Result<FileData<Box<[u8]>>, Box<dyn Error>> {
        let mut file = File::open(path)?;
        let size = file.metadata()?.len();

        let mut provider = vec![0_u8; size as usize].into_boxed_slice();
        let read = file.read(&mut provider)? as u64;
        assert!(
            size == read,
            "Failed to read all data from file: {} vs {}",
            read,
            size
        );

        Ok(FileData { provider })
    }
}

impl IntoFileData<Mmap> for Mmap {
    fn from_path(path: &Path) -> Result<FileData<Mmap>, Box<dyn Error>> {
        let file = File::open(path)?;
        let provider = unsafe { MmapOptions::new().map(&file)? };

        Ok(FileData { provider })
    }
}

// this signature gives the caller the right to choose the type of FileData
impl<T: AsRef<[u8]> + IntoFileData<T>> FileData<T> {
    fn from_path(path: &Path) -> Result<FileData<T>, Box<dyn Error>> {
        T::from_path(path)
    }
}

fn example(path: &Path) {
    // caller asks for and gets file data as Box<[u8]>
    let file_data: FileData<Box<[u8]>> = FileData::from_path(path).unwrap();

    // caller asks for and gets file data as Mmap
    let file_data: FileData<Mmap> = FileData::from_path(path).unwrap();
}

playground 操场


If you want to give the callee the right to decide the return type you must return a trait object.如果你想赋予被调用者决定返回类型的权利,你必须返回一个特征 object。 Simplified commented example:简化注释示例:

use memmap::Mmap;
use memmap::MmapOptions;
use std::error::Error;
use std::fs::File;
use std::io::Read;
use std::path::Path;

// simplified FileData for brevity
struct FileData {
    provider: Box<dyn AsRef<[u8]>>,
}

fn vec_from_path(path: &Path) -> Result<FileData, Box<dyn Error>> {
    let mut file = File::open(path)?;
    let size = file.metadata()?.len();

    let mut provider = vec![0_u8; size as usize];
    let read = file.read(&mut provider)? as u64;
    assert!(
        size == read,
        "Failed to read all data from file: {} vs {}",
        read,
        size
    );

    Ok(FileData {
        provider: Box::new(provider),
    })
}

fn mmap_from_path(path: &Path) -> Result<FileData, Box<dyn Error>> {
    let file = File::open(path)?;
    let provider = unsafe { MmapOptions::new().map(&file)? };

    Ok(FileData {
        provider: Box::new(provider),
    })
}

impl FileData {
    fn from_path(path: &Path, mmap: bool) -> Result<FileData, Box<dyn Error>> {
        if mmap {
            mmap_from_path(path)
        } else {
            vec_from_path(path)
        }
    }
}

fn example(path: &Path) {
    // file data could be vec or mmap, callee decides
    let file_data = FileData::from_path(path, true).unwrap();
    let file_data = FileData::from_path(path, false).unwrap();
}

playground 操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM