Rust:从(仅)<T> 不同的函数返回通用结构

Posted

技术标签:

【中文标题】Rust:从(仅)<T> 不同的函数返回通用结构【英文标题】:Rust: return a generic struct from a function where (only) <T> is different 【发布时间】:2021-01-28 12:07:51 【问题描述】:

我正在寻求有关正确语法或 Rust 方法的帮助。我的用例:我有一个通用结构FileData,它有一个名为provider 的变量。提供者必须实现AsRef&lt;[u8]&gt;,以便数据可能来自静态字节、堆分配内存、内存映射以及可能的其他。我有几种创建FileData 的方法,它们似乎运行良好。但是有一个

// 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)
    

我不明白。该方法总是返回一个 FileData,具体取决于 'mmap' 参数,&lt;T&gt; 不同。它可以是&lt;Box&lt;[u8]&gt;&lt;Mmap&gt;

我搜索了类似的问题和文章,但可以找到与我的情况相匹配的文章,例如(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()
    

错误信息是:

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>, _>`

【问题讨论】:

“他们似乎工作得很好。但是有一个……我不正确。” -- 你得到什么错误?你没有真正解释什么是行不通的。 谢谢,我稍微更新了一下。编译器错误消息是不同的,这取决于我如何尝试修复 mehtod 签名。更准确地说,是方法的返回类型。 【参考方案1】:

泛型让调用者有权决定函数的返回类型应该是什么。现在你的函数,被调用者,正在决定返回类型,这就是你得到编译器错误的原因。

您可以通过实现一个附加特征IntoFileData,然后将其作为绑定到您的通用FileData&lt;T&gt; 实现的特征添加,重构代码以将权利还给调用者。简化注释示例:

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


如果你想赋予被调用者决定返回类型的权利,你必须返回一个特征对象。简化注释示例:

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

【讨论】:

非常感谢关于泛型的澄清并赋予调用者权力。不确定它是否解决了我的问题。假设我有一个文件名,并且取决于某些文件特征(例如,需要解压缩;低于某个大小等),文件应该通过 mmap(首选)加载或读取内容(可能即时解压缩)。库用户(调用者)不需要担心这个内部逻辑。如果我理解你是正确的,那么我需要找到一种没有泛型的方法。任何想法如何实现这一点?可能是一种特质。 @Juergen 您可以使用特征对象来实现这一点。我已经用一个使用特征对象的示例解决方案更新了我的答案。

以上是关于Rust:从(仅)<T> 不同的函数返回通用结构的主要内容,如果未能解决你的问题,请参考以下文章

Rust中的channel

Rust 泛型

在 Rust 库中仅公开泛型私有类型的具体变体

Rust Deref与自动解引用

如何避免 Rust 中重复的长泛型约束

如何使我的 Rust 函数更通用和高效?